use mod.prelude.widgets_internal.* // Pop art. Every part is printed on a comic-book page: a flat shape in one // primary ink with a thick black ink outline, and under it a solid black // shadow the same shape moved down and to the right -- never blurred. Pressed, // a part drops onto its shadow. Shading is a Ben-Day dot screen in the ink's // own deeper tone whose dots grow toward the lower right, and a short white // glint runs inside the upper left of each outline, as a letterer's white // highlight stroke does. Every face is drawn in true points (one device pixel // is `px`); shadows and focus rings that fall outside a layout box are drawn // in a quad grown in `vertex`, and no padding is spent on them. // ---------------------------------------------------------------- helpers // One device pixel in points. let pa_px = fn(dpi: float) -> float { return 1.0 / max(dpi, 0.5) } // An edge rounded to a whole device pixel in window space, handed back in // box space. let pa_snap = fn(v: float, o: float, px: float) -> float { return Finish.snap(o + v, px) - o } // Distance from p to the segment a..b, in points. let pa_seg = fn(p: vec2, a: vec2, b: vec2) -> float { let pa = p - a let ba = b - a let t = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0) return length(pa - ba * t) } // The inks: 0 cadmium yellow, 1 cyan-blue, 2 red, 3 white paper, 4 green, // 5 purple, 6 magenta, 7 night blue, 8 warm grey, 9 the bright red black // words read on, 10 orange. let pa_col = fn(k: float) -> vec3 { if k < 0.5 { return vec3(0.992, 0.835, 0.012) } if k < 1.5 { return vec3(0.071, 0.549, 0.941) } if k < 2.5 { return vec3(0.910, 0.125, 0.165) } if k < 3.5 { return vec3(0.969, 0.953, 0.910) } if k < 4.5 { return vec3(0.071, 0.722, 0.361) } if k < 5.5 { return vec3(0.518, 0.208, 0.741) } if k < 6.5 { return vec3(0.898, 0.145, 0.561) } if k < 7.5 { return vec3(0.055, 0.106, 0.180) } if k < 8.5 { return vec3(0.660, 0.647, 0.608) } if k < 9.5 { return vec3(1.000, 0.333, 0.271) } return vec3(1.0, 0.541, 0.110) } // Black printing ink. let pa_ink = fn() -> vec3 { return vec3(0.067, 0.067, 0.071) } // The deeper tone of an ink a dot screen shades it with: saturated and // darker, as a second pass of the same ink is. let pa_deep = fn(c: vec3) -> vec3 { return c * c * 0.8 } // THE DOT SCREEN. The centre of the Ben-Day dot nearest `q` on a lattice // `pitch` points apart, turned 45 degrees as a printer's screen is. let pa_cell = fn(q: vec2, pitch: float) -> vec2 { let u = vec2(q.x + q.y, q.y - q.x) * (0.70710678 / pitch) let g = floor(u) + vec2(0.5, 0.5) return vec2(g.x - g.y, g.x + g.y) * (0.70710678 * pitch) } // A dot of radius `r` round `c`: its coverage at `q`. A dot smaller than a // device pixel is drawn a pixel and a half across with its ink thinned to // its area, so a screen fading out never sparkles. let pa_dot = fn(q: vec2, c: vec2, r: float, px: float) -> float { let rr = max(r, px * 0.75) return clamp((rr - length(q - c)) / px + 0.5, 0.0, 1.0) * clamp(r * r / (rr * rr), 0.0, 1.0) } // A screen of dots `pitch` apart whose ink covers `cov` of the area round // `q` (0..0.75; at 0.785 the dots touch). `cov` may change across a shape // (a Ben-Day gradient); every dot is round, its size what the coverage asks. let pa_screen = fn(q: vec2, pitch: float, cov: float, px: float) -> float { let c = pa_cell(q, pitch) let r = pitch * sqrt(clamp(cov, 0.0, 0.75) * 0.3183) return pa_dot(q, c, r, px) * step(0.002, cov) } // The size of the ink outline and of the shadow offset for a part whose // smaller side is `sz` points, each a whole number of device pixels. let pa_ow = fn(sz: float, px: float) -> float { return max(Finish.snap(clamp(0.075 * sz, 1.0, 2.5), px), px) } let pa_so = fn(sz: float, px: float) -> float { return max(Finish.snap(clamp(0.09 * sz, 1.3, 3.0), px), px) } // A signed distance to the triangle a, b, c. let pa_sd_tri = fn(p: vec2, a: vec2, b: vec2, c: vec2) -> float { let e0 = b - a let e1 = c - b let e2 = a - c let v0 = p - a let v1 = p - b let v2 = p - c let pq0 = v0 - e0 * clamp(dot(v0, e0) / max(dot(e0, e0), 0.0001), 0.0, 1.0) let pq1 = v1 - e1 * clamp(dot(v1, e1) / max(dot(e1, e1), 0.0001), 0.0, 1.0) let pq2 = v2 - e2 * clamp(dot(v2, e2) / max(dot(e2, e2), 0.0001), 0.0, 1.0) let s = sign(e0.x * e2.y - e0.y * e2.x) let d0 = vec2(dot(pq0, pq0), s * (v0.x * e0.y - v0.y * e0.x)) let d1 = vec2(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x)) let d2 = vec2(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x)) let dd = min(min(d0, d1), d2) return 0.0 - sqrt(dd.x) * sign(dd.y) } // Two distances joined with a fillet `k` points round, as an inker joins a // speech bubble's tail to its body. let pa_smin = fn(a: float, b: float, k: float) -> float { let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0) return mix(b, a, h) - k * h * (1.0 - h) } // ---------------------------------------------------------------- the print // A printed shape laid on the page, premultiplied, from its distance field: // `d` at the face (moved by the press already), `ds` at its shadow. The fill // `base` shaded by a dot screen toward the lower right (`uv` the point in the // face, 0..1 each way, `q` the point from the face's corner, for the // screen), a black outline `ow` wide inside the edge, a white glint inside // the outline along the upper left, and the solid black shadow. Hover lifts // the ink toward white and thins the screen; disabled, the shape is printed // grey and its shadow goes pale. let pa_print = fn(d: float, ds: float, uv: vec2, q: vec2, px: float, ow: float, base: vec3, hover: float, disabled: float, shade: float) -> vec4 { let live = 1.0 - disabled let sa = Finish.cover(ds, px) * mix(1.0, 0.3, disabled) var res = vec4(pa_ink() * sa, sa) var b = mix(base, mix(base, pa_col(8.0), 0.85), disabled) b = b + (vec3(1.0, 1.0, 1.0) - b) * 0.16 * hover * live let t = uv.x * 0.42 + uv.y * 0.58 let cov = (0.03 + 0.34 * smoothstep(0.38, 1.02, t)) * (1.0 - 0.55 * hover) * shade var col = mix(b, pa_deep(b), pa_screen(q, 2.6, cov, px)) // The glint: a white stroke a point wide, a point and a half inside the // outline, round the upper left corner and along the top, thinning out. let gd = abs(d + ow + 1.3) - 0.6 let gm = (1.0 - smoothstep(0.42, 0.78, uv.x)) * (1.0 - smoothstep(0.32, 0.6, uv.y)) let gl = Finish.cover(gd, px) * gm * (0.95 - 0.55 * Finish.lum(b)) * live col = mix(col, vec3(1.0, 1.0, 1.0), gl) let ink = mix(pa_ink(), vec3(0.30, 0.30, 0.31), disabled) col = mix(ink, col, Finish.cover(d + ow, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(col * a, a)) } // A rounded box `lo`..`hi` printed with its shadow. `lift` scales the // shadow (0 prints the shape flat on the page); pressed, the shape drops by // the shadow's offset onto it. Focus: a cyan-blue ring standing off the // edge. let pa_block = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, base: vec3, hover: float, down: float, disabled: float, focus: float, lift: float) -> vec4 { let live = 1.0 - disabled let sz = min(hi.y - lo.y, hi.x - lo.x) let ow = pa_ow(sz, px) let so = pa_so(sz, px) * lift let dn = clamp(down, 0.0, 1.0) * live let off = vec2(so, so) * dn let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c + off, h, r) let ds = Material.sd_box(p, c + vec2(so, so), h, r) + 1000.0 * step(so, 0.001) let uv = clamp((p - lo - off) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) var res = pa_print(d, ds, uv, p - lo - off, px, ow, base, hover, disabled, 1.0) let fr = Finish.ring_out(d, 1.0, 2.6, px) return Finish.over(res, vec4(pa_col(1.0), 1.0) * fr * focus * live) } // A disc of radius `rad` round `c`, printed the same way. let pa_disc = fn(p: vec2, c: vec2, rad: float, px: float, base: vec3, hover: float, down: float, disabled: float, focus: float, lift: float) -> vec4 { let live = 1.0 - disabled let ow = pa_ow(rad * 2.0, px) let so = pa_so(rad * 2.0, px) * lift let off = vec2(so, so) * clamp(down, 0.0, 1.0) * live let d = length(p - c - off) - rad let ds = length(p - c - vec2(so, so)) - rad + 1000.0 * step(so, 0.001) let uv = clamp((p - c - off) / (rad * 2.0) + vec2(0.5, 0.5), vec2(0.0, 0.0), vec2(1.0, 1.0)) var res = pa_print(d, ds, uv, p - c - off, px, ow, base, hover, disabled, 1.0) let fr = Finish.ring_out(d, 1.0, 2.6, px) return Finish.over(res, vec4(pa_col(1.0), 1.0) * fr * focus * live) } // A speech bubble: a rounded box with a short tail at its lower left // pointing down and out, joined to it with a fillet, printed with its shadow. let pa_bubble = fn(p: vec2, lo: vec2, hi: vec2, r: float, px: float, base: vec3, hover: float, down: float, disabled: float, focus: float) -> vec4 { let live = 1.0 - disabled let sz = min(hi.y - lo.y, hi.x - lo.x) let ow = pa_ow(sz, px) let so = pa_so(sz, px) let off = vec2(so, so) * clamp(down, 0.0, 1.0) * live let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let ta = vec2(lo.x + 7.0, hi.y - 3.0) let tb = vec2(lo.x + 15.0, hi.y - 3.0) let tc = vec2(lo.x + 4.0, hi.y + 4.5) let d = pa_smin(Material.sd_box(p - off, c, h, r), pa_sd_tri(p - off, ta, tb, tc), 1.5) let ds = pa_smin(Material.sd_box(p - vec2(so, so), c, h, r), pa_sd_tri(p - vec2(so, so), ta, tb, tc), 1.5) let uv = clamp((p - lo - off) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) var res = pa_print(d, ds, uv, p - lo - off, px, ow, base, hover, disabled, 0.6) let fr = Finish.ring_out(d, 1.0, 2.6, px) return Finish.over(res, vec4(pa_col(1.0), 1.0) * fr * focus * live) } // A ball: a printed disc lit from the upper left, its lower right shaded by // a coarser dot screen, a white glint high on its left, with its shadow. // `held` drops it onto the shadow. let pa_ball = fn(dst: vec4, p: vec2, c: vec2, rad: float, px: float, base: vec3, hover: float, held: float, disabled: float, lift: float) -> vec4 { let live = 1.0 - disabled let ow = max(Finish.snap(clamp(0.16 * rad, 1.0, 2.0), px), px) let so = max(Finish.snap(clamp(0.17 * rad, 1.0, 2.5), px), px) * lift let off = vec2(so, so) * clamp(held, 0.0, 1.0) * live let q = p - c - off let d = length(q) - rad let sa = Finish.cover(length(p - c - vec2(so, so)) - rad, px) * step(0.001, so) * mix(1.0, 0.3, disabled) var res = Finish.over(dst, vec4(pa_ink() * sa, sa)) var b = mix(base, mix(base, pa_col(8.0), 0.85), disabled) b = b + (vec3(1.0, 1.0, 1.0) - b) * 0.16 * hover * live // The shade: a crescent toward the lower right, screened. let qn = q / max(rad, 0.5) let sh = smoothstep(-0.1, 0.9, dot(qn, vec2(0.55, 0.83)) + 0.25 * length(qn)) let cov = 0.05 + 0.5 * sh * sh var col = mix(b, pa_deep(b), pa_screen(q, max(2.0, rad * 0.32), cov, px)) // The glint: a small white oval up on the left. let gq = (qn - vec2(-0.36, -0.40)) let gr = vec2(gq.x * 0.8 + gq.y * 0.6, gq.y * 0.8 - gq.x * 0.6) / vec2(0.26, 0.15) col = mix(col, vec3(1.0, 1.0, 1.0), Finish.cover((length(gr) - 1.0) * rad * 0.15, px) * (0.95 - 0.4 * Finish.lum(b)) * live) col = mix(mix(pa_ink(), vec3(0.3, 0.3, 0.31), disabled), col, Finish.cover(d + ow, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(col * a, a)) } // A lightning bolt standing on `c`, `rad` its half height: two triangles of // yellow ink, outlined, with their shadow. `held` drops it. let pa_bolt_d = fn(q: vec2, rad: float) -> float { let s = rad let u = pa_sd_tri(q, vec2(-0.02, -1.0) * s, vec2(0.66, -1.0) * s, vec2(-0.44, 0.20) * s) let l = pa_sd_tri(q, vec2(-0.34, -0.12) * s, vec2(0.52, -0.12) * s, vec2(-0.30, 1.06) * s) return min(u, l) } let pa_bolt = fn(dst: vec4, p: vec2, c: vec2, rad: float, px: float, base: vec3, hover: float, held: float, disabled: float) -> vec4 { let live = 1.0 - disabled let ow = max(Finish.snap(1.2, px), px) let so = max(Finish.snap(1.6, px), px) let off = vec2(so, so) * clamp(held, 0.0, 1.0) * live let d = pa_bolt_d(p - c - off, rad) let ds = pa_bolt_d(p - c - vec2(so, so), rad) let uv = clamp((p - c - off) / (rad * 2.0) + vec2(0.5, 0.5), vec2(0.0, 0.0), vec2(1.0, 1.0)) let res = pa_print(d, ds, uv, p - c - off, px, ow, base, hover, disabled, 0.7) return Finish.over(dst, res) } // A chunky track: a pill outlined in ink on its own short shadow, `fill` // over the box `flo`..`fhi` with a white highlight stroke along its upper // edge (its left edge when the track stands, `vert` 1), night blue // elsewhere. let pa_track_box = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, flo: vec2, fhi: vec2, px: float, fill: vec3, disabled: float, vert: float) -> vec4 { let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(h.x, h.y) let ow = max(Finish.snap(1.3, px), px) let so = max(Finish.snap(1.3, px), px) let d = Material.sd_box(p, c, h, r) let sa = Finish.cover(Material.sd_box(p, c + vec2(so, so), h, r), px) * mix(1.0, 0.3, disabled) var res = Finish.over(dst, vec4(pa_ink() * sa, sa)) let fc = mix(fill, mix(fill, pa_col(8.0), 0.85), disabled) let rest = mix(pa_col(7.0), vec3(0.42, 0.42, 0.43), disabled) let inf = step(flo.x, p.x) * step(p.x, fhi.x) * step(flo.y, p.y) * step(p.y, fhi.y) var col = mix(rest, fc, inf) let across = mix(p.y - lo.y, p.x - lo.x, vert) let wide = mix(hi.y - lo.y, hi.x - lo.x, vert) let hl = Finish.cover(abs(across - (ow + 0.9)) - 0.45, px) * inf col = mix(col, vec3(1.0, 1.0, 1.0), hl * 0.75 * (1.0 - disabled)) col = mix(col, pa_deep(fc), pa_screen(p - lo, 2.2, 0.22 * smoothstep(0.45, 1.0, across / max(wide, 1.0)), px) * inf) col = mix(pa_ink(), col, Finish.cover(d + ow, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(col * a, a)) } let pa_track = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, f0: float, f1: float, px: float, fill: vec3, disabled: float) -> vec4 { return pa_track_box(dst, p, lo, hi, vec2(f0, lo.y - 1.0), vec2(f1, hi.y + 1.0), px, fill, disabled, 0.0) } // The focus ring of a shape at distance `d`: a cyan-blue line standing a // point off its edge. let pa_focus = fn(dst: vec4, d: float, px: float, focus: float) -> vec4 { return Finish.over(dst, vec4(pa_col(1.0), 1.0) * Finish.ring_out(d, 1.0, 2.6, px) * focus) } // The three-quarter arc a waiting button turns in place of its label. let pa_spinner = fn(dst: vec4, p: vec2, c: vec2, rad: float, t: float, amt: float, stroke: float) -> vec4 { let sdf = Sdf2d.viewport(p) let a0 = t * 2.0 * PI sdf.arc_round_caps(c.x, c.y, rad, a0, a0 + 1.5 * PI, stroke) sdf.fill(vec4(0.067, 0.067, 0.071, amt)) return Finish.over(dst, sdf.result) } // ---------------------------------------------------------------- buttons // Every button is a block of flat ink with a black outline on its solid // shadow: the plain button yellow, the primary and the flat one blue, the // secondary and the danger red, the small one blue, the large one yellow, // the icon key yellow; the outline button is white paper. Under the hand // the ink lifts toward white; pressed, the block drops onto its shadow and // its legend drops with it. mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonFlat.min_height = 24.0 mod.widgets.ButtonFlat.icon_walk.width = 14.0 mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonFlatIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonFlatterIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonXs.min_height = 0.0 mod.widgets.ButtonSm.min_height = 0.0 mod.widgets.ButtonSm.padding = mod.turtle.Inset{left: 9.0 right: 9.0 top: 1.0 bottom: 1.0} mod.widgets.ButtonLg.padding = mod.turtle.Inset{left: 15.0 right: 15.0 top: 2.0 bottom: 2.0} mod.widgets.ButtonFlat.draw_bg.border_radius = uniform(2.5) mod.widgets.ButtonFlat.draw_bg.border_size = uniform(1.5) mod.widgets.ButtonFlat.clip_x = false mod.widgets.ButtonFlat.clip_y = false mod.widgets.ButtonFlat.draw_bg.vertex = fn() { let m = 8.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)) } // The ink of a button `kind`: 0 flat (blue), 1 plain (yellow), 2 primary // (blue), 3 secondary (red), 4 tertiary (yellow), 5 danger (red), 8 icon // (yellow), 9 small (blue). let pa_key_col = fn(kind: float) -> vec3 { if kind < 0.5 { return pa_col(1.0) } if kind < 1.5 { return pa_col(0.0) } if kind < 2.5 { return pa_col(1.0) } if kind < 3.5 { return pa_col(2.0) } if kind < 4.5 { return pa_col(0.0) } if kind < 5.5 { return pa_col(2.0) } if kind < 8.5 { return pa_col(0.0) } return pa_col(1.0) } // The block and its states, shared by every button template; 6 is the // outline button (white paper, its outline dashed when asked) and 7 the // flatter button, printed only under the hand. let pa_button = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, down: float, disabled: float, focus: float, loading: float, anim: float, kind: float, dash: float) -> vec4 { let m = 8.0 let px = pa_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(size.x, o.x, px), pa_snap(size.y, o.y, px)) let hh = hi.y - lo.y let r = clamp(0.22 * hh, 3.0, 6.5) var res = vec4(0.0, 0.0, 0.0, 0.0) if kind > 6.5 && kind < 7.5 { if hover + down + focus > 0.001 { res = pa_block(p, lo, hi, r, px, pa_col(3.0), hover, down, disabled, focus, 0.6) * clamp(max(max(hover, down), focus), 0.0, 1.0) } } else { if kind > 5.5 && kind < 6.5 { res = pa_block(p, lo, hi, r, px, pa_col(3.0), hover, down, disabled, focus, 1.0) if dash > 0.0 { // A dashed outline: the paper shows through the gaps. let so = pa_so(min(hh, hi.x - lo.x), px) let off = vec2(so, so) * clamp(down, 0.0, 1.0) * (1.0 - disabled) let c = (lo + hi) * 0.5 let d = Material.sd_box(p, c + off, (hi - lo) * 0.5, r) let ow = pa_ow(min(hh, hi.x - lo.x), px) let gap = step(0.5, fract((p.x + p.y) / (dash * 2.0))) let band = Finish.cover(d, px) * (1.0 - Finish.cover(d + ow, px)) res = Finish.over(res, vec4(pa_col(3.0), 1.0) * band * gap) } } else { res = pa_block(p, lo, hi, r, px, pa_key_col(kind), hover, down, disabled, focus, 1.0) } } if loading > 0.001 { let rad = max(min(hi.x - lo.x, hh) * 0.5 - 6.0, 2.0) res = pa_spinner(res, p, (lo + hi) * 0.5, rad, anim, loading, 1.6) } return res } mod.widgets.ButtonFlat.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 0.0, 0.0), 8.0) } mod.widgets.Button.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0) } mod.widgets.ButtonSm.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 9.0, 0.0), 8.0) } mod.widgets.ButtonLg.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0) } mod.widgets.ButtonXs.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0) } mod.widgets.ButtonXl.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 1.0, 0.0), 8.0) } mod.widgets.ButtonIcon.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 8.0, 0.0), 8.0) } mod.widgets.ButtonPrimary.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 2.0, 0.0), 8.0) } mod.widgets.ButtonSecondary.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 3.0, 0.0), 8.0) } mod.widgets.ButtonTertiary.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 4.0, 0.0), 8.0) } mod.widgets.ButtonDanger.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 5.0, 0.0), 8.0) } mod.widgets.ButtonOutline.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 6.0, self.border_dash), 8.0) } mod.widgets.ButtonFlatter.draw_bg.pixel = fn() { return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.down, self.disabled, self.focus, self.loading, self.anim_time, 7.0, 0.0), 8.0) } // Legend ink per block: black on the yellow and the paper, white on the // blue and the red (lettered with a black outline); disabled, a dark grey // on the grey block. mod.widgets.ButtonFlat.draw_text.color = #ffffff mod.widgets.ButtonFlat.draw_text.color_hover = #ffffff mod.widgets.ButtonFlat.draw_text.color_down = #ffffff mod.widgets.ButtonFlat.draw_text.color_focus = #ffffff mod.widgets.ButtonFlat.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonFlat.draw_icon.color = #ffffff mod.widgets.Button.draw_text.color = #141414 mod.widgets.Button.draw_text.color_hover = #000000 mod.widgets.Button.draw_text.color_down = #000000 mod.widgets.Button.draw_text.color_focus = #141414 mod.widgets.Button.draw_text.color_disabled = #3a3a3a mod.widgets.Button.draw_icon.color = #141414 mod.widgets.ButtonSm.draw_text.color = #ffffff mod.widgets.ButtonSm.draw_text.color_hover = #ffffff mod.widgets.ButtonSm.draw_text.color_down = #ffffff mod.widgets.ButtonSm.draw_text.color_focus = #ffffff mod.widgets.ButtonSm.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonLg.draw_text.color = #141414 mod.widgets.ButtonLg.draw_text.color_hover = #000000 mod.widgets.ButtonLg.draw_text.color_down = #000000 mod.widgets.ButtonLg.draw_text.color_focus = #141414 mod.widgets.ButtonLg.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonPrimary.draw_text.color = #ffffff mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff mod.widgets.ButtonPrimary.draw_text.color_down = #ffffff mod.widgets.ButtonPrimary.draw_text.color_focus = #ffffff mod.widgets.ButtonPrimary.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonPrimary.draw_icon.color = #ffffff mod.widgets.ButtonSecondary.draw_text.color = #ffffff mod.widgets.ButtonSecondary.draw_text.color_hover = #ffffff mod.widgets.ButtonSecondary.draw_text.color_down = #ffffff mod.widgets.ButtonSecondary.draw_text.color_focus = #ffffff mod.widgets.ButtonSecondary.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonSecondary.draw_icon.color = #ffffff mod.widgets.ButtonTertiary.draw_text.color = #141414 mod.widgets.ButtonTertiary.draw_text.color_hover = #000000 mod.widgets.ButtonTertiary.draw_text.color_down = #000000 mod.widgets.ButtonTertiary.draw_text.color_focus = #141414 mod.widgets.ButtonTertiary.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonTertiary.draw_icon.color = #141414 mod.widgets.ButtonDanger.draw_text.color = #ffffff mod.widgets.ButtonDanger.draw_text.color_hover = #ffffff mod.widgets.ButtonDanger.draw_text.color_down = #ffffff mod.widgets.ButtonDanger.draw_text.color_focus = #ffffff mod.widgets.ButtonDanger.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonDanger.draw_icon.color = #ffffff mod.widgets.ButtonOutline.draw_text.color = #141414 mod.widgets.ButtonOutline.draw_text.color_hover = #000000 mod.widgets.ButtonOutline.draw_text.color_down = #000000 mod.widgets.ButtonOutline.draw_text.color_focus = #141414 mod.widgets.ButtonOutline.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonOutline.draw_icon.color = #141414 mod.widgets.ButtonFlatter.draw_text.color = #141414 mod.widgets.ButtonFlatter.draw_text.color_hover = #000000 mod.widgets.ButtonFlatter.draw_text.color_down = #000000 mod.widgets.ButtonFlatter.draw_text.color_disabled = #4a4a4a mod.widgets.ButtonXs.draw_text.color = #141414 mod.widgets.ButtonXs.draw_text.color_hover = #000000 mod.widgets.ButtonXs.draw_text.color_down = #000000 mod.widgets.ButtonXs.draw_text.color_focus = #141414 mod.widgets.ButtonXs.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonXl.draw_text.color = #141414 mod.widgets.ButtonXl.draw_text.color_hover = #000000 mod.widgets.ButtonXl.draw_text.color_down = #000000 mod.widgets.ButtonXl.draw_text.color_focus = #141414 mod.widgets.ButtonXl.draw_text.color_disabled = #3a3a3a mod.widgets.ButtonFlat.draw_icon_end.color = #ffffff // A compound button's description line: white on its blue block, lettered. mod.widgets.ButtonFlat.draw_description.color = #ffffff mod.widgets.ButtonCompound.draw_description.color = #ffffff // The legends are lettered in Anton, the poster capitals. mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.Button.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.ButtonOutline.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.ButtonFlatter.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.ButtonSm.draw_text.text_style = mod.theme.font_bold{font_size: 9.0} mod.widgets.ButtonLg.draw_text.text_style = mod.theme.font_bold{font_size: 12.0} mod.widgets.ButtonXs.draw_text.text_style = mod.theme.font_bold{font_size: 7.5} mod.widgets.ButtonXl.draw_text.text_style = mod.theme.font_bold{font_size: 14.0} // ---------------------------------------------------------------- selection // The quad of a small control grows by this much so its shadow and focus // ring can fall outside the box. mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() { let m = 6.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.CheckBoxFlat.clip_x = false mod.widgets.CheckBoxFlat.clip_y = false mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0} mod.widgets.CheckBoxFlat.draw_bg.size = uniform(15.0) mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(2.0) mod.widgets.CheckBoxFlat.label_walk.margin.left = 15.0 // The check box: a square of white paper, screened grey toward its foot, on // its shadow. Checked, it is printed blue with a fat black tick; mixed, blue // with a black dash. Pressed, it drops onto its shadow and the mark with it. mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() { let m = 6.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let sz = self.size let x0 = (self.rect_size.x - sz) * self.mark_at_end let y0 = self.rect_size.y * 0.5 - sz * 0.5 let lo = vec2(pa_snap(x0, o.x, px), pa_snap(y0, o.y, px)) let hi = vec2(pa_snap(x0 + sz, o.x, px), pa_snap(y0 + sz, o.y, px)) let live = 1.0 - self.disabled let fill = mix(pa_col(3.0), pa_col(1.0), max(self.active, self.mixed)) var res = pa_block(p, lo, hi, 3.0, px, fill, self.hover, self.down, self.disabled, self.focus, 1.0) let so = pa_so(sz, px) let c = (lo + hi) * 0.5 + vec2(so, so) * clamp(self.down, 0.0, 1.0) * live let ink = mix(pa_ink(), vec3(0.3, 0.3, 0.31), self.disabled) // Mixed: a black dash. let dd = pa_seg(p, vec2(c.x - 3.4, c.y), vec2(c.x + 3.4, c.y)) - 1.2 res = Finish.over(res, vec4(ink, 1.0) * Finish.cover(dd, px) * self.mixed) // Checked: the tick. let on = self.active * (1.0 - self.mixed) let cm = c + vec2(0.0, 0.3) let dm = min(pa_seg(p, vec2(cm.x - 4.2, cm.y - 0.4), vec2(cm.x - 1.3, cm.y + 2.9)), pa_seg(p, vec2(cm.x - 1.3, cm.y + 2.9), vec2(cm.x + 4.4, cm.y - 3.6))) - 1.35 res = Finish.over(res, vec4(ink, 1.0) * Finish.cover(dm, px) * on) // Disabled and empty: a small padlock printed in the box. let lk = self.disabled * (1.0 - max(self.active, self.mixed)) let bd = Material.sd_box(p, c + vec2(0.0, 1.4), vec2(3.3, 2.5), 0.7) let sk = abs(length(vec2(p.x - c.x, min(p.y - (c.y - 1.0), 0.0))) - 2.1) - 0.65 let lock = max(Finish.cover(bd, px), Finish.cover(max(sk, p.y - (c.y + 0.2)), px)) res = Finish.over(res, vec4(0.27, 0.27, 0.28, 1.0) * lock * lk) // The error: the outline turns red. let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 3.0) return Finish.over(res, vec4(pa_col(2.0), 1.0) * Finish.ring_out(d, 0.0, 1.6, px) * self.error) } // The toggle: a pill of grey paper on its shadow with a square key of white // paper riding in it; on, the pill is printed in the toggle's ink and the key // stands at its right end. The key is screened grey like newsprint. mod.widgets.ToggleFlat.draw_bg.size = uniform(18.0) mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(1.89) mod.widgets.ToggleFlat.label_walk.margin.left = 34.0 let pa_toggle = fn(p: vec2, lo: vec2, hi: vec2, o: vec2, px: float, kt: float, on: float, hover: float, down: float, disabled: float, focus: float, tint: vec3, round: float) -> vec4 { let th = hi.y - lo.y let tw = hi.x - lo.x let r = th * 0.5 let off_col = vec3(0.74, 0.72, 0.66) var res = pa_block(p, lo, hi, r, px, mix(off_col, tint, on), hover * 0.5, 0.0, disabled, focus, 1.0) let ow = pa_ow(th, px) let ks = th - 2.0 * ow - 1.4 let kx = pa_snap(lo.x + ow + 0.7 + (tw - 2.0 * ow - 1.4 - ks) * kt, o.x, px) let klo = vec2(kx, lo.y + ow + 0.7) let khi = vec2(kx + ks, lo.y + ow + 0.7 + ks) let kr = mix(ks * 0.28, ks * 0.5, round) res = Finish.over(res, pa_block(p, klo, khi, kr, px, pa_col(3.0), hover, down * 0.0, disabled, 0.0, 0.0)) return res } mod.widgets.ToggleFlat.draw_bg.pixel = fn() { let m = 5.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let tw = self.size * self.pill_aspect let th = self.size let x0 = (self.rect_size.x - tw) * self.mark_at_end let y0 = self.rect_size.y * 0.5 - th * 0.5 let lo = vec2(pa_snap(x0, o.x, px), pa_snap(y0, o.y, px)) let hi = vec2(pa_snap(x0 + tw, o.x, px), pa_snap(y0 + th, o.y, px)) let kt = mix(self.active, self.drag_pos, self.drag) return pa_toggle(p, lo, hi, o, px, kt, self.active, self.hover, max(self.pressed, self.down), self.disabled, self.focus, pa_col(1.0), 0.0) } // The radio: a disc of white paper on its shadow. Chosen, it is printed red // with a ring of white paper round a red bead in its middle, and stands // dropped onto its shadow. mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() { let m = 6.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.RadioButtonFlat.clip_x = false mod.widgets.RadioButtonFlat.clip_y = false mod.widgets.RadioButtonFlat.draw_bg.size = uniform(15.0) mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0} mod.widgets.RadioButtonFlat.label_walk.margin.left = 15.0 mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() { let m = 6.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let sz = self.size let y0 = self.rect_size.y * 0.5 - sz * 0.5 let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(y0, o.y, px)) let hi = vec2(pa_snap(sz, o.x, px), pa_snap(y0 + sz, o.y, px)) let rad = (hi.x - lo.x) * 0.5 let live = 1.0 - self.disabled let on = self.active let pushed = max(self.down, on) var res = pa_disc(p, (lo + hi) * 0.5, rad, px, mix(pa_col(3.0), pa_col(2.0), on), self.hover, pushed, self.disabled, self.focus, 1.0) let so = pa_so(sz, px) let c = (lo + hi) * 0.5 + vec2(so, so) * clamp(pushed, 0.0, 1.0) * live let rl = length(p - c) let ring = Finish.cover(abs(rl - rad * 0.48) - rad * 0.13, px) * on res = Finish.over(res, vec4(pa_col(3.0), 1.0) * ring) return res } // The radio tab (the catalogue's panel tabs): a blue block with a black // word; the tab in use is printed white and dropped onto its shadow. mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() { let m = 8.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.RadioButtonTabFlat.clip_x = false mod.widgets.RadioButtonTabFlat.clip_y = false mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 0.0 right: 8.0 top: 3.0 bottom: 3.0} mod.widgets.RadioButtonTabFlat.min_height = 24.0 mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() { let m = 8.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), pa_snap(self.rect_size.y, o.y, px)) let r = clamp(0.22 * (hi.y - lo.y), 3.0, 6.0) return pa_block(p, lo, hi, r, px, mix(pa_col(1.0), pa_col(3.0), self.active), self.hover, max(self.down, self.active), self.disabled, self.focus, 1.0) } mod.widgets.RadioButtonTabFlat.draw_text.color = #141414 mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #000000 mod.widgets.RadioButtonTabFlat.draw_text.color_active = #000000 mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #3a3a3a mod.widgets.RadioButtonTabFlat.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} // ---------------------------------------------------------------- lamps // LAMPS: a real LED, a glossy bead of coloured glass, set in a thick bezel // of glossy black outlined in ink. 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. The bead sits in a socket // in the bezel, the socket's lower rim catching the light. Over the bead // one crisp highlight of a dome. Its light is added, never painted: a tight // hot halo and a short bloom in its own hue on the black bezel, gone in // about two radii. let pa_led_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 { let est = res.rgb + ground * (1.0 - res.a) let add = (vec3(1.0, 1.0, 1.0) - exp(-light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0)) return vec4(res.rgb + add, res.a) } // An LED's colour at full strength: its brightest channel at one. let pa_led_full = fn(ink: vec3) -> vec3 { return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01) } // The same colour at the emitter's own chroma: the hue kept and its // saturation raised by `vivid` (a colour with next to no hue, a white or a // plain lamp, stays neutral). let pa_led_hue = fn(ink: vec3, vivid: float) -> vec3 { let f = pa_led_full(ink) let mn = min(f.x, min(f.y, f.z)) let ch = 1.0 - mn let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001) return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch)) } // The lamp: its lens a rounded box `hb` either side of `c` (round when the // two are equal), `lit` its current 0..1, `ink` its colour and `vivid` how // far its chroma is raised, `ground` the bezel's dark under the light, // `room` how far its light may reach, `bez` the bezel's black, `raised` 1 // for a bezel standing on the page. Premultiplied over `dst`. let pa_led = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float, bez: vec3, raised: float) -> vec4 { let rt = max(min(hb.x, hb.y), 0.4) let d = Material.sd_box(p, c, hb, rt) let bw = clamp(0.62 * rt, 1.4, 3.6) let ds = d - bw let L = clamp(lit, 0.0, 1.0) let b = pa_led_full(ink) let hue = pa_led_hue(ink, vivid) let pw = L * sqrt(L) let white = vec3(1.0, 1.0, 1.0) var res = dst let x = max(ds, 0.0) let reach = max(room, 1.0) let win = 1.0 - smoothstep(0.55 * reach, reach, x) // The lens's own frame: `q` in lens radii from its centre line, `t` its // distance out, `nrm` the way its outline faces. let ex = max(hb.x - hb.y, 0.0) let ey = max(hb.y - hb.x, 0.0) let qx = p.x - c.x let qy = p.y - c.y let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt let t = clamp(length(q), 0.0, 1.0) let nrm = q / max(length(q), 0.001) // The bezel's solid shadow on the page, down and to the right. let so = max(Finish.snap(clamp(0.3 * rt, 0.7, 1.4), px), px) * raised let shd = Finish.cover(Material.sd_box(p, c + vec2(so, so), hb + vec2(bw, bw), rt + bw), px) * step(0.001, so) res = Finish.over(res, vec4(pa_ink() * shd, shd)) // The bezel: glossy black, rounded over, a grey sheen along its upper // left; its outer edge an ink line; the socket's lower rim a catch light. let a_s = Finish.cover(ds, px) let u = clamp(d / max(bw, 0.01), 0.0, 1.0) let facing = dot(nrm * (u - 0.5) * 2.0, vec2(-0.45, -0.89)) var rc = bez * (0.94 + 0.12 * facing) rc = mix(rc, min(bez + vec3(0.42, 0.42, 0.42), white), smoothstep(0.35, 0.95, facing) * 0.75 * smoothstep(0.15, 0.5, u) * (1.0 - smoothstep(0.75, 1.0, u))) rc = mix(rc, min(bez + vec3(0.55, 0.55, 0.55), white), Finish.ring_out(d, 0.0, max(px * 1.2, 0.6), px) * smoothstep(0.2, 0.95, nrm.y) * 0.9) rc = mix(rc, pa_ink() * 0.6, Finish.band(ds, 0.0, max(px, 0.6), px)) res = Finish.over(res, vec4(rc * a_s, a_s)) // The light: a tight hot halo and a short bloom in its hue, gone in // about two radii, falling on the bezel. if L > 0.001 { let xl = max(d, 0.0) let halo = exp(-xl / max(0.30 * rt, 0.4)) * 1.5 + exp(-xl / max(0.65 * rt, 0.7)) * 0.32 let hwin = 1.0 - smoothstep(1.1 * rt, 2.0 * rt + 1.0, xl) res = pa_led_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground) } // THE BEAD: a glossy dome of coloured glass. Its colour deepens toward // the top; light pools low inside, where it comes through the dome; lit, // its middle burns toward white at full current and its rim is its most // saturated colour. let top = smoothstep(-1.0, 0.6, q.y) let pool = exp(-(q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30) var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top) on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5) on = mix(on, vec3(1.0, 0.99, 0.975), exp(-t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85) on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t)) on = on * (0.42 + 0.58 * L) // Out: dark tinted glass, its colour barely there, the same dome, a catch // light along its lower rim. let offc = mix(vec3(0.085, 0.09, 0.115), b * 0.4, 0.14) 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)) // One crisp highlight: a white oval across its upper third. let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30)) let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0)) col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt)) col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L))) col = min(col, white) let a_l = Finish.cover(d, px) return Finish.over(res, vec4(col * a_l, a_l)) } // The Lamp widget and the bar lamp: the bead in its black bezel on its // shadow. The halo's room is held in the lamp's own box (the library // reserves a lens thickness either side once a halo is asked for), so the // light is never cut at its edge. mod.widgets.Lamp.halo = 0.3 mod.widgets.Lamp.halo_reach = 1.0 mod.widgets.Lamp.draw_bg.color_accent = uniform(#2a9cff) mod.widgets.Lamp.draw_bg.color_success = uniform(#x3ee05a) mod.widgets.Lamp.draw_bg.color_warning = uniform(#ffc21a) mod.widgets.Lamp.draw_bg.color_error = uniform(#ff3a2a) 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) // The bead takes about two fifths of its box, the rest is room for its // bezel and light. let t = min(self.rect_size.y / (1.0 + 2.0 * reach) * 1.12, self.rect_size.y * 0.38) let room = (self.rect_size.y - t) * 0.5 let c = self.rect_size * 0.5 // A round lamp: the bead the lamp's size in the middle of its box. A // bar: a short light pipe three fifths of a bead thick and at most 11 // points long, set from the start of its box with its light gone // within a point and a half, so in a row it never reaches toward what // stands after it. let tb = t * 0.6 let blen = min(max(self.rect_size.x - 2.0 * room, tb), min(2.0 * tb, 10.0)) let cb = vec2(2.5 + 1.2 + blen * 0.5, self.rect_size.y * 0.5) let cc = mix(c, cb, self.bar) let hb = mix(vec2(t * 0.5, t * 0.5), vec2(blen * 0.5, tb * 0.5), self.bar) let reach_px = mix(max(room - 1.0, 2.0), 1.5, self.bar) let res = pa_led(vec4(0.0, 0.0, 0.0, 0.0), p, cc, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, vec3(0.07, 0.07, 0.08), reach_px, vec3(0.09, 0.09, 0.1), 1.0) return res * self.opacity } mod.widgets.LampBar.draw_bg.color_accent = uniform(#2a9cff) mod.widgets.LampBar.draw_bg.color_success = uniform(#x3ee05a) mod.widgets.LampBar.draw_bg.color_warning = uniform(#ffc21a) mod.widgets.LampBar.draw_bg.color_error = uniform(#ff3a2a) // ---------------------------------------------------------------- sliders // A slider is a chunky track outlined in ink: printed in the slider's ink up // to the value, night blue past it, a white highlight stroke along the ink, // and a yellow ball riding it on its own shadow. Each kind has its ink: the // slider red, the minimal one magenta, the round one green with a lightning // bolt for a thumb, the band blue. Its value is set in the slanted hand // beside its name, on the page; under the hand or keyed, a tag of white // paper stands behind it. let pa_slider = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, f0: float, f1: float, hx: vec2, px: float, rad: float, base: vec3, hover: float, held: float, disabled: float) -> vec4 { var res = pa_track(dst, p, lo, hi, f0, f1, px, base, disabled) if rad > 0.0 { res = pa_ball(res, p, hx, rad, px, pa_col(0.0), hover, held, disabled, 1.0) } return res } // The value tag of a slider kind: nothing at rest; white paper outlined in // ink under the hand or while it is being typed in. let pa_valbox = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, focus: float, disabled: float) -> vec4 { let px = pa_px(dpi) let p = pos * size if size.x < 1.0 || size.y < 1.0 { return vec4(0.0, 0.0, 0.0, 0.0) } let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(size.x - px * 0.49, o.x, px), pa_snap(size.y - px * 0.49, o.y, px)) let c = (lo + hi) * 0.5 let d = Material.sd_box(p, c, (hi - lo) * 0.5, 3.0) let ow = max(Finish.snap(1.2, px), px) var col = mix(pa_ink(), mix(pa_col(3.0), vec3(1.0, 1.0, 1.0), focus), Finish.cover(d + ow, px)) let a = Finish.cover(d, px) * clamp(max(hover * 0.85, focus), 0.0, 1.0) * (1.0 - disabled) return vec4(col * a, a) } mod.widgets.SliderFlat.clip_x = false mod.widgets.SliderFlat.clip_y = false mod.widgets.SliderFlat.draw_bg.vertex = fn() { let m = 12.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.SliderFlat.draw_bg.pixel = fn() { let m = 12.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let rad = 8.5 let cy = pa_snap(20.0 + (self.rect_size.y - 20.0) * 0.5, o.y, px) let lo = vec2(pa_snap(0.0, o.x, px), cy - 4.0) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), cy + 4.0) let span = self.rect_size.x - rad * 2.0 let hx = rad + self.slide_pos * span let ox = rad + self.origin_pos * span let f0 = mix(lo.x, min(ox, hx), self.arc_origin) let f1 = mix(hx, max(ox, hx), self.arc_origin) var res = pa_slider(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, f0, f1, vec2(hx, cy), px, rad, pa_col(2.0), self.hover, self.drag, self.disabled) return pa_focus(res, length(p - vec2(hx, cy)) - rad, px, self.focus * (1.0 - self.disabled)) } // The value field rides in the label row. mod.widgets.SliderMinimal.text_input.min_height = 0.0 mod.widgets.SliderMinimal.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 1.0 bottom: 1.0} mod.widgets.SliderFlat.text_input.min_height = 0.0 mod.widgets.SliderFlat.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 1.0 bottom: 1.0} mod.widgets.Slider.text_input.min_height = 0.0 mod.widgets.Slider.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 1.0 bottom: 1.0} mod.widgets.SliderRoundFlat.text_input.min_height = 0.0 mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 1.0 bottom: 1.0} mod.widgets.SliderRound.text_input.min_height = 0.0 mod.widgets.SliderRound.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 1.0 bottom: 1.0} mod.widgets.SliderFlat.text_input.draw_bg.pixel = fn() { return pa_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.focus, self.disabled) } mod.widgets.Slider.text_input.draw_bg.pixel = fn() { return pa_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.focus, self.disabled) } mod.widgets.SliderMinimal.text_input.draw_bg.pixel = fn() { return pa_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.focus, self.disabled) } mod.widgets.SliderRoundFlat.text_input.draw_bg.pixel = fn() { return pa_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.focus, self.disabled) } mod.widgets.SliderRound.text_input.draw_bg.pixel = fn() { return pa_valbox(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.focus, self.disabled) } // The minimal slider: a slimmer magenta track and a smaller ball. mod.widgets.SliderMinimal.clip_x = false mod.widgets.SliderMinimal.clip_y = false mod.widgets.SliderMinimal.draw_bg.vertex = fn() { let m = 12.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.SliderMinimal.draw_bg.pixel = fn() { let m = 12.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let rad = 7.0 let cy = pa_snap(self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, o.y, px) let lo = vec2(pa_snap(0.0, o.x, px), cy - 3.0) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), cy + 3.0) let span = self.rect_size.x - rad * 2.0 let hx = rad + self.slide_pos * span let ox = rad + self.origin_pos * span let f0 = mix(lo.x, min(ox, hx), self.arc_origin) let f1 = mix(hx, max(ox, hx), self.arc_origin) var res = pa_slider(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, f0, f1, vec2(hx, cy), px, rad, pa_col(6.0), self.hover, self.drag, self.disabled) return pa_focus(res, length(p - vec2(hx, cy)) - rad, px, self.focus * (1.0 - self.disabled)) } // The round slider: a label column, then the green track with a lightning // bolt riding it; the track ends short of the right edge, where its value // stands on the page. mod.widgets.SliderRoundFlat.clip_x = false mod.widgets.SliderRoundFlat.clip_y = false mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() { let m = 12.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.SliderRoundFlat.draw_bg.pixel = fn() { let m = 12.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let rad = 11.0 let cy = pa_snap(self.rect_size.y * 0.5, o.y, px) let x0 = pa_snap(self.label_size, o.x, px) let lo = vec2(x0, cy - 3.5) let hi = vec2(pa_snap(self.rect_size.x - 46.0, o.x, px), cy + 3.5) let span = hi.x - lo.x - 12.0 let hx = lo.x + 6.0 + self.slide_pos * span var res = pa_track(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, lo.x, hx, px, pa_col(4.0), self.disabled) res = pa_bolt(res, p, vec2(hx, cy), rad, px, pa_col(0.0), self.hover, self.drag, self.disabled) return pa_focus(res, pa_bolt_d(p - vec2(hx, cy), rad), px, self.focus * (1.0 - self.disabled)) } // THE BAND is the slider with two yellow balls, printed blue between them. // The widget's grab geometry is the balls' own. mod.widgets.RangeSliderFlat.clip_x = false mod.widgets.RangeSliderFlat.clip_y = false mod.widgets.RangeSliderFlat.track_inset = 0.0 mod.widgets.RangeSliderFlat.handle_size = 17.0 mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() { let m = 12.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.RangeSliderFlat.draw_bg.pixel = fn() { let m = 12.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let rad = 8.5 let cy = pa_snap(self.label_px + (self.rect_size.y - self.label_px) * 0.5, o.y, px) let lo = vec2(pa_snap(0.0, o.x, px), cy - 4.0) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), cy + 4.0) let span = self.rect_size.x - rad * 2.0 let h0 = rad + self.start_pos * span let h1 = rad + self.end_pos * span var res = pa_track(vec4(0.0, 0.0, 0.0, 0.0), p, lo, hi, h0, h1, px, pa_col(1.0), self.disabled) res = pa_ball(res, p, vec2(h0, cy), rad, px, pa_col(0.0), self.hot_start, self.drag * self.hot_start, self.disabled, 1.0) res = pa_ball(res, p, vec2(h1, cy), rad, px, pa_col(0.0), self.hot_end, self.drag * self.hot_end, self.disabled, 1.0) let d = min(length(p - vec2(h0, cy)), length(p - vec2(h1, cy))) - rad return pa_focus(res, d, px, self.focus * (1.0 - self.disabled)) } // THE FADER: a black slot with an ink scale either side, yellow ink in the // slot up to the value, and a chunky red cap outlined in ink on its shadow, // a white index line across its middle. mod.widgets.SliderFader.cap_size = 22.0 // A standing fader is 34 points wide, so a bank of four keeps to its column. mod.widgets.SliderFaderY.width = 34.0 mod.widgets.SliderFader.track_inset = 8.0 mod.widgets.SliderFader.draw_bg.track_size = uniform(6.0) mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.66) mod.widgets.SliderFader.clip_x = false mod.widgets.SliderFader.clip_y = false mod.widgets.SliderFader.draw_bg.vertex = fn() { let m = 12.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.SliderFader.draw_bg.pixel = fn() { let m = 12.0 let px = pa_px(self.draw_pass.dpi_factor) let sz = self.rect_size let o = self.rect_pos let p = self.pos * (sz + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let v = self.is_vertical let along = mix(sz.x, sz.y, v) let across = mix(sz.y, sz.x, v) let inset = self.inset_px let travel = max(along - inset * 2.0 - self.cap_px, 1.0) let foot = inset + self.cap_px * 0.5 let dis = self.disabled // Track space: `a` along the track from the value's zero (up the screen // when the fader stands), `b` across it. let a = mix(p.x, sz.y - p.y, v) let b = mix(p.y, p.x, v) let mid = pa_snap(across * 0.5, mix(o.y, o.x, v), px) let tw = min(self.track_size, across) // The scale: eleven short ink dashes either side of the slot. let pitch = travel / 10.0 let k = (a - foot) / max(pitch, 1.0) let ka = abs(fract(k + 0.5) - 0.5) * pitch let inr = step(-0.5, k) * step(k, 10.5) let side = abs(abs(b - mid) - (tw * 0.5 + 5.0)) let dash = Finish.cover(max(ka - 0.6, side - 2.0), px) * inr var res = vec4(pa_ink(), 1.0) * dash * mix(0.9, 0.4, dis) // The slot and the value in it, laid out in track space and drawn in // screen space through the swap. var lo = foot + self.fill_lo * travel var hi = foot + self.fill_hi * travel if self.fill_lo < 0.0005 { lo = inset } if self.fill_hi > 0.9995 { hi = along - inset } let slo = mix(vec2(inset, mid - tw * 0.5), vec2(mid - tw * 0.5, inset), v) let shi = mix(vec2(along - inset, mid + tw * 0.5), vec2(mid + tw * 0.5, along - inset), v) let flo = mix(vec2(lo, mid - tw), vec2(mid - tw, sz.y - hi), v) let fhi = mix(vec2(hi, mid + tw), vec2(mid + tw, sz.y - lo), v) res = pa_track_box(res, p, slo, shi, flo, fhi, px, pa_col(0.0), dis, v) // The cap. let at = foot + self.slide_pos * travel let cw = min(max(across * self.cap_cross, 8.0), across) let cc = mix(vec2(at, mid), vec2(mid, sz.y - at), v) let ch = mix(vec2(self.cap_px * 0.5, cw * 0.5), vec2(cw * 0.5, self.cap_px * 0.5), v) let clo = vec2(Finish.snap(o.x + cc.x - ch.x, px), Finish.snap(o.y + cc.y - ch.y, px)) - o let chi = clo + ch * 2.0 let held = self.drag * (1.0 - dis) var cap = pa_block(p, clo, chi, 3.0, px, pa_col(2.0), self.hover, held, dis, self.focus, 1.0) let so = pa_so(min(chi.x - clo.x, chi.y - clo.y), px) * held let cm = (clo + chi) * 0.5 + vec2(so, so) let lw = max(Finish.snap(1.4, px), px) let line = Finish.cover(abs(mix(p.y - cm.y, p.x - cm.x, 1.0 - v)) - lw * 0.5, px) * Finish.cover(abs(mix(p.x - cm.x, p.y - cm.y, 1.0 - v)) - (mix(ch.x, ch.y, 1.0 - v) - 3.0), px) cap = Finish.over(cap, vec4(1.0, 1.0, 1.0, 1.0) * line * mix(1.0, 0.5, dis)) return Finish.over(res, cap) } // ---------------------------------------------------------------- knobs // The angle of `q` from straight down, clockwise, 0..2 pi. let pa_ang = fn(q: vec2) -> float { var ang = atan2(0.0 - q.x, q.y) if ang < 0.0 { ang = ang + 2.0 * PI } return ang } // A printed knob: a disc of flat ink on its solid shadow, outlined in ink, // its lower right shaded by a dot screen, a white glint stroke inside its // upper left, and a fat white pointer outlined in ink from near its middle // to its rim. `col` its ink. let pa_knob_cap = fn(dst: vec4, p: vec2, c: vec2, rk: float, px: float, col: vec3, ve: float, hover: float, held: float, disabled: float) -> vec4 { let live = 1.0 - disabled let ow = max(Finish.snap(clamp(0.085 * rk * 2.0, 1.2, 2.6), px), px) let so = max(Finish.snap(clamp(0.07 * rk * 2.0, 1.3, 3.5), px), px) let off = vec2(so, so) * clamp(held, 0.0, 1.0) * 0.5 * live let q = p - c - off let d = length(q) - rk let ds = length(p - c - vec2(so, so)) - rk let uv = clamp(q / (rk * 2.0) + vec2(0.5, 0.5), vec2(0.0, 0.0), vec2(1.0, 1.0)) // A coarser screen on a big knob, finer on a small one. let pitch = clamp(rk * 0.16, 2.2, 4.5) let qn = q / max(rk, 0.5) let sh = smoothstep(-0.15, 0.95, dot(qn, vec2(0.5, 0.86)) + 0.3 * length(qn)) let live_hover = hover * live var b = mix(col, mix(col, pa_col(8.0), 0.85), disabled) b = b + (vec3(1.0, 1.0, 1.0) - b) * 0.14 * live_hover let sa = Finish.cover(ds, px) * mix(1.0, 0.3, disabled) var res = Finish.over(dst, vec4(pa_ink() * sa, sa)) var fc = mix(b, pa_deep(b) * 0.85, pa_screen(q, pitch, 0.06 + 0.52 * sh * sh, px)) // The glint: a white arc stroke inside the outline, upper left. let ang = atan2(q.y, q.x) let ga = smoothstep(0.55, 0.0, abs(ang + 2.25)) fc = mix(fc, vec3(1.0, 1.0, 1.0), Finish.cover(abs(length(q) - (rk - ow - 1.8)) - 0.6, px) * ga * 0.9 * live) fc = mix(mix(pa_ink(), vec3(0.3, 0.3, 0.31), disabled), fc, Finish.cover(d + ow, px)) let a = Finish.cover(d, px) res = Finish.over(res, vec4(fc * a, a)) // The pointer. let dir = vec2(0.0 - sin(ve), cos(ve)) let pa = c + off + dir * rk * 0.22 let pb = c + off + dir * (rk - ow - 1.2) let pw = clamp(0.11 * rk, 1.3, 3.2) let dp = pa_seg(p, pa, pb) - pw var pc = mix(pa_ink(), vec3(1.0, 1.0, 1.0), Finish.cover(dp + max(Finish.snap(1.0, px), px), px)) res = Finish.over(res, vec4(pc, 1.0) * Finish.cover(dp, px) * a) return res } // A small knob: round its disc, eleven short ink ticks over the sweep, the // ticks up to the value printed red; the disc red. Angles run from straight // down, clockwise, the gap at the bottom. let pa_knob = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 { let q = p - c let rl = length(q) let ang = pa_ang(q) let one = PI / 180.0 let sweep = 2.0 * PI - gap * one let start = gap * one * 0.5 let ve = start + sweep * slide let oe = start + sweep * origin let a0 = mix(start, min(oe, ve), arc_origin) let a1 = mix(ve, max(oe, ve), arc_origin) let live = 1.0 - disabled var res = vec4(0.0, 0.0, 0.0, 0.0) // The ticks. let n = 11.0 let tl = clamp(0.16 * rr, 2.5, 6.0) let kf = clamp(floor((ang - start) / sweep * (n - 1.0) + 0.5), 0.0, n - 1.0) let ta = start + kf / (n - 1.0) * sweep let tdir = vec2(0.0 - sin(ta), cos(ta)) let tw = clamp(0.045 * rr, 0.9, 1.6) let dt = pa_seg(p, c + tdir * (rr - tl + tw), c + tdir * (rr - tw)) - tw let lit = step(a0 - 0.001, ta) * step(ta, a1 + 0.001) * step(0.002, a1 - a0) let tcol = mix(pa_ink(), pa_col(2.0), lit * live) let tdo = Finish.cover(dt - max(Finish.snap(0.8, px), px) * lit, px) res = Finish.over(res, vec4(pa_ink(), 1.0) * tdo) res = Finish.over(res, vec4(tcol, 1.0) * Finish.cover(dt, px)) // The disc. let rk = rr - tl - clamp(0.06 * rr, 1.2, 3.0) res = pa_knob_cap(res, p, c, rk, px, pa_col(2.0), ve, hover, held, disabled) return pa_focus(res, rl - rr, px, focus * live) } mod.widgets.RotaryKnob.clip_x = false mod.widgets.RotaryKnob.clip_y = false mod.widgets.RotaryKnob.draw_bg.vertex = fn() { let m = 6.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.RotaryKnob.draw_bg.pixel = fn() { let m = 6.0 let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let dia = min(self.rect_size.x, self.rect_size.y) let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x, Finish.snap(self.rect_pos.y + self.rect_size.y - dia * 0.5, px) - self.rect_pos.y) return pa_knob(p, c, dia * 0.5 - 1.5, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled) } // THE DIAL: a big purple disc printed like the small knobs, in a black // scale ring of its own: a thick ink band round it with nineteen white ticks // over the sweep, the ticks past the value grey and the last fifth of the // scale red, the band itself on its solid shadow. let pa_dial = fn(p: vec2, c: vec2, rr: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, held: float, focus: float, disabled: float) -> vec4 { let q = p - c let rl = length(q) let ang = pa_ang(q) let one = PI / 180.0 let sweep = 2.0 * PI - gap * one let start = gap * one * 0.5 let ve = start + sweep * slide let oe = start + sweep * origin let a0 = mix(start, min(oe, ve), arc_origin) let a1 = mix(ve, max(oe, ve), arc_origin) let live = 1.0 - disabled var res = vec4(0.0, 0.0, 0.0, 0.0) // The scale band: a ring of ink, its own shadow under it. let bw = clamp(0.15 * rr, 4.0, 10.0) let so = max(Finish.snap(clamp(0.035 * rr, 1.3, 3.0), px), px) let rm = rr - bw * 0.5 - so let db = abs(rl - rm) - bw * 0.5 let dbs = abs(length(q - vec2(so, so)) - rm) - bw * 0.5 let sa = Finish.cover(dbs, px) res = Finish.over(res, vec4(pa_ink() * sa, sa)) let ba = Finish.cover(db, px) res = Finish.over(res, vec4(mix(pa_ink(), vec3(0.3, 0.3, 0.31), disabled) * ba, ba)) // The ticks on it. let n = 19.0 let kf = clamp(floor((ang - start) / sweep * (n - 1.0) + 0.5), 0.0, n - 1.0) let tt = kf / (n - 1.0) let ta = start + tt * sweep let tdir = vec2(0.0 - sin(ta), cos(ta)) let tw = clamp(0.022 * rr, 0.7, 1.4) let major = 1.0 - step(0.5, abs(kf - 2.0 * floor(kf * 0.5 + 0.01))) let tin = mix(bw * 0.28, bw * 0.12, major) let dt = pa_seg(p, c + tdir * (rm - bw * 0.5 + tin + tw), c + tdir * (rm + bw * 0.5 - tin - tw)) - tw let lit = step(a0 - 0.001, ta) * step(ta, a1 + 0.001) * step(0.002, a1 - a0) let zone = step(0.79, tt) var tcol = mix(mix(vec3(0.42, 0.42, 0.44), vec3(0.55, 0.16, 0.14), zone), mix(vec3(1.0, 1.0, 1.0), pa_col(9.0), zone), lit) tcol = mix(tcol, vec3(0.55, 0.55, 0.56), disabled) res = Finish.over(res, vec4(tcol, 1.0) * Finish.cover(dt, px)) // The disc. let rk = rm - bw * 0.5 + 0.5 res = pa_knob_cap(res, p, c, rk, px, pa_col(5.0), ve, hover, held, disabled) return pa_focus(res, rl - rr, px, focus * live) } mod.widgets.RotaryFlat.clip_x = false mod.widgets.RotaryFlat.clip_y = false mod.widgets.RotaryFlat.draw_bg.vertex = fn() { let m = 12.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.RotaryFlat.draw_bg.pixel = fn() { let m = 12.0 let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let dia = min(self.rect_size.x, self.rect_size.y - 22.0) let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x, Finish.snap(self.rect_pos.y + 20.0 + dia * 0.5, px) - self.rect_pos.y) return pa_dial(p, c, dia * 0.5 - 1.0, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.drag, self.focus, self.disabled) } // A rotary's value sits in a small blue block outlined in ink, black // figures on it. let pa_tag = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, focus: float, disabled: float) -> vec4 { let px = pa_px(dpi) let p = pos * size if size.x < 1.0 || size.y < 1.0 { return vec4(0.0, 0.0, 0.0, 0.0) } let so = max(Finish.snap(1.3, px), px) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(size.x - px * 0.49, o.x, px), pa_snap(size.y - px * 0.49, o.y, px)) - vec2(so, so) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(h.y, 6.0) let d = Material.sd_box(p, c, h, r) let ds = Material.sd_box(p, c + vec2(so, so), h, r) let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) var res = pa_print(d, ds, uv, p - lo, px, max(Finish.snap(1.3, px), px), mix(pa_col(1.0), vec3(1.0, 1.0, 1.0), focus * 0.85), hover, disabled, 0.6) return res } mod.widgets.RotaryFlat.text_input.min_height = 0.0 mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 7.0 right: 8.0 top: 2.0 bottom: 3.0} mod.widgets.Rotary.text_input.min_height = 0.0 mod.widgets.Rotary.text_input.padding = mod.turtle.Inset{left: 7.0 right: 8.0 top: 2.0 bottom: 3.0} mod.widgets.RotaryGradientY.text_input.min_height = 0.0 mod.widgets.RotaryGradientY.text_input.padding = mod.turtle.Inset{left: 7.0 right: 8.0 top: 2.0 bottom: 3.0} mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() { return pa_tag(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.focus, self.disabled) } mod.widgets.Rotary.text_input.draw_bg.pixel = fn() { return pa_tag(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.focus, self.disabled) } mod.widgets.RotaryGradientY.text_input.draw_bg.pixel = fn() { return pa_tag(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, self.hover, self.focus, self.disabled) } // ---------------------------------------------------------------- fields // A text field: a night-blue well outlined in ink on its solid shadow, all // inside its own box (a field clips what it holds), the last points to the // right and below left for the shadow. Under the hand the blue lifts a // little; focused, the outline is printed in cyan-blue. Read only, the // well is grey. let pa_field = fn(p: vec2, size: vec2, o: vec2, px: float, fill: vec3, hover: float, focus: float, disabled: float) -> vec4 { let so = max(Finish.snap(2.0, px), px) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(size.x - px * 0.49, o.x, px), pa_snap(size.y - px * 0.49, o.y, px)) - vec2(so, so) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = 4.0 let ow = max(Finish.snap(1.8, px), px) let d = Material.sd_box(p, c, h, r) let ds = Material.sd_box(p, c + vec2(so, so), h, r) let sa = Finish.cover(ds, px) * mix(1.0, 0.45, disabled) var res = vec4(pa_ink() * sa, sa) var f = mix(fill + vec3(0.03, 0.035, 0.045) * hover, vec3(0.53, 0.52, 0.50), disabled) // A faint screen in the well's own deeper blue toward its foot. let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) f = mix(f, f * 0.55, pa_screen(p - lo, 2.4, 0.16 * smoothstep(0.4, 1.0, t), px)) let fo = focus * (1.0 - disabled) let ink = mix(mix(pa_ink(), vec3(0.28, 0.28, 0.29), disabled), pa_col(1.0), fo) var col = mix(ink, f, Finish.cover(d + ow, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(col * a, a)) } // Text fields; an input with no fill of its own (the one a slider, a combo // box or a well holds) draws nothing. mod.widgets.TextInputFlat.draw_bg.pixel = fn() { if self.color.a < 0.01 { return vec4(0.0, 0.0, 0.0, 0.0) } let px = pa_px(self.draw_pass.dpi_factor) return pa_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, pa_col(7.0), self.hover, self.focus, self.disabled) } // A field well (a form's field, a search box with icons) wears the same // well and carries its input's state into it. mod.widgets.FieldWell.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) return pa_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, pa_col(7.0), self.hover, self.focus, self.disabled) } mod.widgets.FormField.input.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) return pa_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, pa_col(7.0), self.hover, self.focus, self.disabled) } mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 9.0 right: 11.0 top: 3.0 bottom: 5.0} mod.widgets.TextInputFlat.min_height = 24.0 mod.widgets.TextInputFlat.draw_cursor.color = #ffffff mod.widgets.TextInputFlat.draw_text.color = #xf7f3e8 mod.widgets.TextInputFlat.draw_text.color_hover = uniform(#ffffff) mod.widgets.TextInputFlat.draw_text.color_focus = uniform(#ffffff) mod.widgets.TextInputFlat.draw_text.color_down = uniform(#ffffff) mod.widgets.TextInputFlat.draw_text.color_empty = uniform(#a9b8cc) mod.widgets.TextInputFlat.draw_text.color_empty_hover = uniform(#c2cede) mod.widgets.TextInputFlat.draw_text.color_empty_focus = uniform(#c2cede) mod.widgets.TextInputFlat.draw_text.color_disabled = uniform(#x1e1e1e) mod.widgets.TextInputFlat.draw_selection.color = uniform(#128cf080) mod.widgets.TextInputFlat.draw_selection.color_hover = uniform(#128cf080) mod.widgets.TextInputFlat.draw_selection.color_focus = uniform(#128cf0a6) mod.widgets.TextInputFlat.draw_selection.color_down = uniform(#128cf0a6) // The number field: the same well; its stepper stands inside the well's // right end behind an ink rule, two white arrowheads, the one under the // hand lit blue. mod.widgets.NumberField.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) return pa_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, pa_col(7.0), self.hover, self.focus, self.disabled) } mod.widgets.NumberField.spin_width = 16.0 mod.widgets.NumberSpin.width = 16.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 = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let so = max(Finish.snap(2.0, px), px) let ow = max(Finish.snap(1.8, px), px) let x0 = pa_snap(0.0, o.x, px) let xr = pa_snap(self.rect_size.x - px * 0.49, o.x, px) - so - ow let yt = pa_snap(0.0, o.y, px) + ow let yb = pa_snap(self.rect_size.y - px * 0.49, o.y, px) - so - ow let mid = (yt + yb) * 0.5 let live = 1.0 - self.disabled var res = vec4(0.0, 0.0, 0.0, 0.0) // The rule: ink down the stepper's left side. let rule = step(x0, p.x) * step(p.x, x0 + ow) * step(yt, p.y) * step(p.y, yb) res = Finish.over(res, vec4(pa_ink(), 1.0) * rule) // The halves under the hand. let up = max(0.0, self.hot) let dn = max(0.0, 0.0 - self.hot) let hu = step(x0 + ow, p.x) * step(p.x, xr) * step(yt, p.y) * step(p.y, mid) let hd = step(x0 + ow, p.x) * step(p.x, xr) * step(mid, p.y) * step(p.y, yb) res = Finish.over(res, vec4(pa_col(1.0), 1.0) * (hu * up + hd * dn) * live) let cx = (x0 + ow + xr) * 0.5 let cu = (yt + mid) * 0.5 + 0.4 let cd = (mid + yb) * 0.5 - 0.4 let tu = pa_sd_tri(p, vec2(cx - 3.0, cu + 1.6), vec2(cx + 3.0, cu + 1.6), vec2(cx, cu - 1.8)) let td = pa_sd_tri(p, vec2(cx - 3.0, cd - 1.6), vec2(cx, cd + 1.8), vec2(cx + 3.0, cd - 1.6)) let ink = mix(vec3(0.6, 0.6, 0.6), vec3(1.0, 1.0, 1.0), live) return Finish.over(res, vec4(ink, 1.0) * Finish.cover(min(tu, td), px)) } // The drop-down: a yellow block outlined in ink on its shadow, a black // arrowhead at its right behind an ink rule. mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 28.0 top: 3.0 bottom: 3.0} mod.widgets.DropDownFlat.clip_x = false mod.widgets.DropDownFlat.clip_y = false mod.widgets.DropDownFlat.draw_bg.vertex = fn() { let m = 8.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)) } // The rule and the arrowhead, laid on a block or a field at its right end. let pa_chevron = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, px: float, ink: vec3, disabled: float) -> vec4 { let ow = max(Finish.snap(1.3, px), px) let dx = Finish.snap(hi.x - 20.0, px) let inside = step(lo.y + 4.0, p.y) * step(p.y, hi.y - 4.0) let rule = step(dx - ow, p.x) * step(p.x, dx) * inside let k = mix(ink, vec3(0.35, 0.35, 0.36), disabled) var res = Finish.over(dst, vec4(k, 1.0) * rule) let cc = vec2(dx + 10.0, (lo.y + hi.y) * 0.5) let dt = pa_sd_tri(p, vec2(cc.x - 4.2, cc.y - 2.2), vec2(cc.x + 4.2, cc.y - 2.2), vec2(cc.x, cc.y + 2.8)) return Finish.over(res, vec4(k, 1.0) * Finish.cover(dt, px)) } mod.widgets.DropDownFlat.draw_bg.pixel = fn() { let m = 8.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), pa_snap(self.rect_size.y, o.y, px)) let r = clamp(0.22 * (hi.y - lo.y), 3.0, 6.5) let dn = max(self.down, self.active) var res = pa_block(p, lo, hi, r, px, pa_col(0.0), self.hover, dn, self.disabled, self.focus, 1.0) let so = pa_so(min(hi.y - lo.y, hi.x - lo.x), px) * clamp(dn, 0.0, 1.0) * (1.0 - self.disabled) return pa_chevron(res, p, lo + vec2(so, so), hi + vec2(so, so), px, pa_ink(), self.disabled) } mod.widgets.DropDownFlat.draw_text.color = #141414 mod.widgets.DropDownFlat.draw_text.color_hover = #000000 mod.widgets.DropDownFlat.draw_text.color_focus = #141414 mod.widgets.DropDownFlat.draw_text.color_disabled = #3a3a3a mod.widgets.DropDownFlat.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} // The combo box: a bar of bright red ink outlined on its shadow, its words // black, its list opened from the arrowhead at its right. mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 10.0 right: 28.0 top: 3.0 bottom: 5.0} mod.widgets.ComboBoxFlat.input.min_height = 0.0 mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let o = self.rect_pos let so = max(Finish.snap(2.0, px), px) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x - px * 0.49, o.x, px), pa_snap(self.rect_size.y - px * 0.49, o.y, px)) - vec2(so, so) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = 5.0 let d = Material.sd_box(p, c, h, r) let ds = Material.sd_box(p, c + vec2(so, so), h, r) let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) var res = pa_print(d, ds, uv, p - lo, px, max(Finish.snap(1.8, px), px), pa_col(9.0), self.hover, self.disabled, 0.8) res = Finish.over(res, vec4(pa_col(1.0), 1.0) * Finish.band(d, max(Finish.snap(1.8, px), px), max(Finish.snap(1.8, px), px) + 1.4, px) * self.focus * (1.0 - self.disabled)) return pa_chevron(res, p, lo, hi, px, pa_ink(), self.disabled) } mod.widgets.ComboBoxFlat.input.draw_text.color = #141414 mod.widgets.ComboBoxFlat.input.draw_text.color_hover = #000000 mod.widgets.ComboBoxFlat.input.draw_text.color_focus = #000000 mod.widgets.ComboBoxFlat.input.draw_text.color_empty = #5a1a14 mod.widgets.ComboBoxFlat.input.draw_text.color_disabled = #3a3a3a mod.widgets.ComboBoxFlat.input.draw_cursor.color = #141414 // Selected words on the red bar: a dark wash that leaves the black words // black. mod.widgets.ComboBoxFlat.input.draw_selection.color = uniform(#00000000) mod.widgets.ComboBoxFlat.input.draw_selection.color_hover = uniform(#x0000002e) mod.widgets.ComboBoxFlat.input.draw_selection.color_focus = uniform(#00000038) mod.widgets.ComboBoxFlat.input.draw_selection.color_down = uniform(#00000038) // Open lists: a sheet of white paper outlined in ink with its solid shadow // inside its own box; the row under the hand printed yellow, the chosen row // a blue strip outlined in ink. let pa_menu = fn(p: vec2, size: vec2, px: float) -> vec4 { let so = max(Finish.snap(3.0, px), px) let lo = vec2(0.0, 0.0) let hi = size - vec2(so, so) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = 5.0 let d = Material.sd_box(p, c, h, r) let ds = Material.sd_box(p, c + vec2(so, so), h, r) let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) return pa_print(d, ds, uv, p - lo, px, max(Finish.snap(2.0, px), px), pa_col(3.0), 0.0, 0.0, 0.35) } let pa_row = fn(p: vec2, size: vec2, px: float, hover: float, active: float, disabled: float) -> vec4 { let lo = vec2(4.0, 1.0) let hi = size - vec2(7.0, 1.0) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c, h, 3.0) let ow = max(Finish.snap(1.3, px), px) let hv = vec4(pa_col(0.0), 1.0) * Finish.cover(d, px) * hover * (1.0 - disabled) * (1.0 - active) let col = mix(pa_ink(), mix(pa_col(1.0), vec3(1.0, 1.0, 1.0), 0.12 * hover), Finish.cover(d + ow, px)) let key = vec4(col, 1.0) * Finish.cover(d, px) return mix(hv, key, active) } mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) return pa_menu(self.pos * self.rect_size, self.rect_size, px) } mod.widgets.PopupMenuItem.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) return pa_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, self.disabled) } mod.widgets.PopupMenuItem.draw_text.color = #141414 mod.widgets.PopupMenuItem.draw_text.color_hover = #000000 mod.widgets.PopupMenuItem.draw_text.color_active = #000000 mod.widgets.ComboBoxFlat.draw_popup_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) return pa_menu(self.pos * self.rect_size, self.rect_size, px) } mod.widgets.ComboBoxFlat.draw_item.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) return pa_row(self.pos * self.rect_size, self.rect_size, px, self.hover, self.active, 0.0) } mod.widgets.ComboBoxFlat.draw_item_text.color = #141414 mod.widgets.ComboBoxFlat.draw_item_text.color_active = #000000 // ---------------------------------------------------------------- tabs // The segmented control: the whole strip one long blue block on its shadow, // black words on it; the chosen answer is printed on white paper and // dropped onto the shadow -- pushed in -- its word going down with it. mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() { let m = 8.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.SegmentedControlFlat.draw_bg.pixel = fn() { let m = 8.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), pa_snap(self.rect_size.y, o.y, px)) let r = clamp(0.22 * (hi.y - lo.y), 3.0, 6.5) return pa_block(p, lo, hi, r, px, pa_col(1.0), 0.0, 0.0, 0.0, 0.0, 1.0) } // The chosen answer stands a point and a half in from the strip's edge. mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() { let m = 9.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.SegmentedControlFlat.draw_pill.pixel = fn() { let m = 9.0 let v = 1.0 - step(0.0, self.radius) let hz = 1.0 - v let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(1.0 * hz - 0.5 * v, o.x, px), pa_snap(-1.0 * hz, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x - 1.0 * hz + 0.5 * v, o.x, px), pa_snap(self.rect_size.y + 1.0 * hz, o.y, px)) let r = clamp(0.22 * (hi.y - lo.y), 3.0, 6.0) return pa_block(p, lo, hi, r, px, pa_col(3.0), 0.0, 1.0, 0.0, 0.0, 1.0) } mod.widgets.SegmentedControlFlat.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.SegmentedControlFlat.draw_text.color = #141414 mod.widgets.SegmentedControlFlat.draw_text_selected.color = #000000 mod.widgets.SegmentedControlVertical.draw_pill.radius = -1.0 // Tabs: each tab is a red block of its own with a small gap to the next; // the tab in use is printed on white paper and dropped onto its shadow like // the chosen segment. mod.widgets.Tabs.height = 26.0 mod.widgets.Tabs.clip_x = false mod.widgets.Tabs.clip_y = false let pa_tab = fn(pos: vec2, size: vec2, o: vec2, dpi: float, hover: float, active: float) -> vec4 { let m = 8.0 let px = pa_px(dpi) let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(1.5, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(size.x - 2.5, o.x, px), pa_snap(size.y - 1.0, o.y, px)) let r = clamp(0.22 * (hi.y - lo.y), 3.0, 6.0) return pa_block(p, lo, hi, r, px, mix(pa_col(9.0), pa_col(3.0), active), hover, active, 0.0, 0.0, 1.0) } mod.widgets.Tabs.draw_tab.vertex = fn() { let m = 8.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.Tabs.draw_tab_hover.vertex = fn() { let m = 8.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.Tabs.draw_tab_active.vertex = fn() { let m = 8.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.Tabs.draw_bg.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_tab.pixel = fn() { return pa_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 0.0) } mod.widgets.Tabs.draw_tab_hover.pixel = fn() { return pa_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 1.0, 0.0) } mod.widgets.Tabs.draw_tab_active.pixel = fn() { return pa_tab(self.pos, self.rect_size, self.rect_pos, self.draw_pass.dpi_factor, 0.0, 1.0) } mod.widgets.Tabs.draw_sep.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_mark.color = #141414 mod.widgets.Tabs.draw_text.color = #141414 mod.widgets.Tabs.draw_text_active.color = #000000 mod.widgets.Tabs.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_bold{font_size: 10.0} // ---------------------------------------------------------------- feedback // The progress bar: a chunky track, night blue, printed in its ink up to // the value; still and unknown, a block of red and yellow racing stripes in // its middle third. mod.widgets.ProgressBarFlat.height = 9.0 mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let so = max(Finish.snap(1.3, px), px) let th = min(max(self.thickness, 7.0), self.rect_size.y - so) let y0 = pa_snap((self.rect_size.y - so - th) * 0.5, o.y, px) let x0 = pa_snap(self.rect_size.x * self.start, o.x, px) let x1 = pa_snap(self.rect_size.x * self.end, o.x, px) - so let lo = vec2(x0, y0) let hi = vec2(x1, y0 + Finish.snap(th, px)) let span = hi.x - lo.x var f0 = lo.x var f1 = f0 + span * clamp((self.value - self.start) / max(self.end - self.start, 0.001), 0.0, 1.0) let fill = self.fill_color() var res = vec4(0.0, 0.0, 0.0, 0.0) if self.value < 0.0 { f0 = lo.x + span * 0.3 f1 = lo.x + span * 0.7 res = pa_track(res, p, lo, hi, f0, f1, px, pa_col(0.0), 0.0) // Red stripes across the block. let st = step(0.5, fract((p.x - p.y * 0.8) / 7.0)) * step(f0, p.x) * step(p.x, f1) let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, (hi.y - lo.y) * 0.5) let ow = max(Finish.snap(1.3, px), px) res = Finish.over(res, vec4(pa_col(2.0), 1.0) * st * Finish.cover(d + ow, px)) } else { if self.track > 0.5 { res = pa_track(res, p, lo, hi, f0, f1, px, fill.rgb, 0.0) } } return res * self.opacity } mod.widgets.ProgressBarFlat.draw_bg.color_primary = #128cf0 // The level meter: a night-blue channel outlined in ink holding square // cells of ink, blue at its foot, then green, yellow toward the top, // orange, red at the end, // each lit cell screened in its deeper tone; unlit cells are dark glass of // their own colour. The clip lamp is a small LED. mod.widgets.LevelMeter.height = 9.0 mod.widgets.LevelMeterColumn.width = 12.0 mod.widgets.LevelMeter.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let sz = self.rect_size let p = self.pos * sz let v = self.vertical let along = mix(sz.x, sz.y, v) let across = mix(sz.y, sz.x, v) let a = mix(p.x, sz.y - p.y, v) let b = mix(p.y, p.x, v) let lamp_room = self.lamp * (across + 3.0) let bar = max(4.0, along - lamp_room) let wlo = mix(vec2(0.0, 0.0), vec2(0.0, sz.y - bar), v) let whi = mix(vec2(bar, sz.y), vec2(sz.x, sz.y), v) let lo = vec2(pa_snap(wlo.x, o.x, px), pa_snap(wlo.y, o.y, px)) let hi = vec2(pa_snap(whi.x, o.x, px), pa_snap(whi.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(min(h.x, h.y), 3.0) let ow = max(Finish.snap(clamp(across * 0.14, 0.8, 1.4), px), px) let d = Material.sd_box(p, c, h, r) var res = vec4(pa_ink(), 1.0) * Finish.cover(d, px) res = Finish.over(res, vec4(pa_col(7.0), 1.0) * Finish.cover(d + ow, px)) let pad = ow + 0.8 let span = max(1.0, bar - pad * 2.0) // The cells: about a cell and a half of the meter's thickness long. let pitch = span / max(floor(span / max(across * 1.4, 4.0) + 0.5), 1.0) let k = floor((a - pad) / pitch) let ca = pad + (k + 0.5) * pitch let share = clamp((ca - pad) / span, 0.0, 1.0) var lc = mix(pa_col(1.0), pa_col(4.0), step(self.hot_at * 0.4, share)) lc = mix(lc, pa_col(0.0), step(self.hot_at, share)) lc = mix(lc, pa_col(10.0), step(mix(self.hot_at, self.hot_to, 0.6), share)) lc = mix(lc, pa_col(2.0), step(self.hot_to, share)) let on = step(share - 0.5 / max(span / pitch, 1.0), self.level) * step(0.001, self.level) let held = step(abs(ca - (pad + span * self.hold)), pitch * 0.5) * step(0.002, self.hold) let lit = max(on, held) let cw = max(pitch - 1.2, 1.0) let cb = across - pad * 2.0 let cq = vec2(a - ca, b - across * 0.5) let cd = Material.sd_box(cq, vec2(0.0, 0.0), vec2(cw * 0.5, cb * 0.5), min(0.8, cb * 0.5)) let inb = step(pad, a) * step(a, bar - pad) var cc = mix(lc, pa_deep(lc), pa_screen(cq + vec2(10.0, 10.0), 1.9, 0.18, px)) let unl = mix(vec3(0.05, 0.08, 0.13), lc, 0.16) cc = mix(unl, cc, lit) res = Finish.over(res, vec4(cc, 1.0) * Finish.cover(cd, px) * inb) res = res * self.opacity if self.lamp > 0.5 { let lcen = mix(vec2(bar + 3.0 + across * 0.5, sz.y * 0.5), vec2(sz.x * 0.5, across * 0.5), v) let rl = clamp(across * 0.5 - 1.0, 1.5, 3.5) res = pa_led(res, p, lcen, vec2(rl, rl), step(0.5, self.over), pa_col(2.0), 1.0, px, vec3(0.07, 0.07, 0.08), rl * 1.0, vec3(0.08, 0.08, 0.09), 1.0) * self.opacity } return res } // The spinner: a little gauge -- a yellow disc outlined in ink on its // shadow, short ink ticks round it and a black needle. A sheet may not read // the pass clock, so it stands still. mod.widgets.SpinnerFlat.size = 20.0 mod.widgets.SpinnerFlat.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let o = self.rect_pos let size = min(self.rect_size.x, self.rect_size.y) let so = max(Finish.snap(1.3, px), px) let rad = size * 0.5 - so - 0.5 let c = vec2(Finish.snap(o.x + rad + 0.5, px), Finish.snap(o.y + rad + 0.5, px)) - o var res = pa_disc(p, c, rad, px, pa_col(0.0), 0.0, 0.0, 0.0, 0.0, 1.0) let q = p - c let ang = pa_ang(q) let k = floor(ang / (PI / 4.0) + 0.5) let ta = k * PI / 4.0 let tdir = vec2(0.0 - sin(ta), cos(ta)) let dt = pa_seg(p, c + tdir * rad * 0.62, c + tdir * rad * 0.8) - 0.55 res = Finish.over(res, vec4(pa_ink(), 1.0) * Finish.cover(dt, px) * step(0.5, abs(k - 0.0)) * step(abs(k - 8.0), 7.5)) let nd = vec2(0.72, -0.69) let dn = pa_seg(p, c - nd * rad * 0.15, c + nd * rad * 0.72) - 0.9 res = Finish.over(res, vec4(pa_ink(), 1.0) * Finish.cover(dn, px)) res = Finish.over(res, vec4(pa_ink(), 1.0) * Finish.cover(length(q) - 1.8, px)) return res * self.alpha } // A count is a small blue block outlined in ink on its shadow, its figures // black. mod.widgets.BadgePalette.neutral = #128cf0 mod.widgets.BadgePalette.on_neutral = #111111 mod.widgets.BadgeFlat.shape = mod.widgets.BadgeShape.Rounded mod.widgets.BadgeFlat.radius = 4.0 mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let so = max(Finish.snap(1.3, px), px) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x - px * 0.49, o.x, px), pa_snap(self.rect_size.y - px * 0.49, o.y, px)) - vec2(so, so) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = min(4.0, min(h.x, h.y)) let d = Material.sd_box(p, c, h, r) let ds = Material.sd_box(p, c + vec2(so, so), h, r) let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) return pa_print(d, ds, uv, p - lo, px, max(Finish.snap(1.3, px), px), self.color.rgb, 0.0, 0.0, 0.6) * max(self.color.a, 0.0) } // A chip is a speech bubble of white paper outlined in ink, its tail at its // lower left, its word black; a keyed chip wears the focus ring. mod.widgets.ChipFlat.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.ChipFlat.draw_text.color = #141414 mod.widgets.ChipFlat.draw_bg.vertex = fn() { let m = 8.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.ChipFlat.draw_bg.pixel = fn() { let m = 8.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), pa_snap(self.rect_size.y, o.y, px)) let r = (hi.y - lo.y) * 0.5 return pa_bubble(p, lo, hi, r, px, pa_col(3.0), self.hover, self.down, 0.0, self.keyed) } // ---------------------------------------------------------------- surfaces // The scroll bar: a channel of white paper outlined in ink, the thumb a // blue strip outlined in ink in it. mod.widgets.ScrollBar.bar_size = 10.0 mod.widgets.ScrollBar.bar_side_margin = 1.0 mod.widgets.ScrollBar.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let sz = self.rect_size let p = self.pos * sz let v = self.is_vertical let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(sz.x, o.x, px), pa_snap(sz.y, o.y, px)) let ow = max(Finish.snap(1.2, px), px) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c, h, min(h.x, h.y)) var res = vec4(mix(pa_ink(), pa_col(3.0), Finish.cover(d + ow, px)), 1.0) * Finish.cover(d, px) let t0 = mix(sz.x * self.norm_scroll, sz.y * self.norm_scroll, v) let t1 = t0 + mix(sz.x * self.norm_handle, sz.y * self.norm_handle, v) let tlo = mix(vec2(t0 + 1.0, 1.5), vec2(1.5, t0 + 1.0), v) let thi = mix(vec2(t1 - 1.0, sz.y - 1.5), vec2(sz.x - 1.5, t1 - 1.0), v) let slo = vec2(pa_snap(tlo.x, o.x, px), pa_snap(tlo.y, o.y, px)) let shi = vec2(pa_snap(thi.x, o.x, px), pa_snap(thi.y, o.y, px)) let sc = (slo + shi) * 0.5 let shh = max((shi - slo) * 0.5, vec2(0.5, 0.5)) let dt = Material.sd_box(p, sc, shh, min(shh.x, shh.y)) let col = pa_col(1.0) + (vec3(1.0, 1.0, 1.0) - pa_col(1.0)) * 0.18 * max(self.hover, self.drag) let tc = mix(pa_ink(), col, Finish.cover(dt + ow, px)) res = Finish.over(res, vec4(tc, 1.0) * Finish.cover(dt, px)) return res * self.opacity } // A comic panel: a big flat shape of ink outlined thick in black on its // solid shadow, its shadow held within half the gap to its neighbours, its // fill screened toward the lower right in its own deeper tone. A raised // panel is blue; a sunken panel is red with the page's shadow falling into // it along its upper and left walls, a hole printed in the page. let pa_slab = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, base: vec3, hover: float, sunk: float) -> vec4 { let so = max(Finish.snap(3.0, px), px) * (1.0 - sunk) let ow = max(Finish.snap(2.6, px), px) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(size.x, o.x, px), pa_snap(size.y, o.y, px)) let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = Material.sd_box(p, c, h, r) let ds = Material.sd_box(p, c + vec2(so, so), h, r) + 1000.0 * sunk let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) // A coarser screen than a control's, about four points apart. let sa = Finish.cover(ds, px) var res = vec4(pa_ink() * sa, sa) var b = base + (vec3(1.0, 1.0, 1.0) - base) * 0.06 * hover let t = uv.x * 0.42 + uv.y * 0.58 let cov = 0.04 + 0.30 * smoothstep(0.30, 1.05, t) var col = mix(b, pa_deep(b), pa_screen(p - lo, 3.6, cov, px)) // Sunk: the shadow of the page falls in from the upper left, solid. let sw = max(Finish.snap(6.0, px), px) let din = Material.sd_box(p, c + vec2(sw, sw), h, r) col = mix(col, pa_ink(), (1.0 - Finish.cover(din, px)) * sunk) // The glint, a white stroke inside the upper left of the outline. let gd = abs(d + ow + 2.0) - 0.75 let gm = (1.0 - smoothstep(0.10, 0.28, uv.x)) * (1.0 - smoothstep(0.12, 0.45, uv.y)) * (1.0 - sunk) col = mix(col, vec3(1.0, 1.0, 1.0), Finish.cover(gd, px) * gm * (0.9 - 0.5 * Finish.lum(b))) col = mix(pa_ink(), col, Finish.cover(d + ow, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(col * a, a)) } mod.widgets.PanelView.clip_x = false mod.widgets.PanelView.clip_y = false mod.widgets.PanelView.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0} mod.widgets.PanelView.draw_bg.vertex = fn() { let m = 8.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.PanelView.draw_bg.pixel = fn() { let m = 8.0 let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) if self.sunken > 0.5 { return pa_slab(p, self.rect_size, self.rect_pos, px, 9.0, pa_col(9.0), 0.0, 1.0) } return pa_slab(p, self.rect_size, self.rect_pos, px, 9.0, pa_col(1.0), 0.0, 0.0) } // The rounded view keeps the colour its caller gives it: opaque, it is a // panel of that ink outlined in black on its shadow; translucent, it stays a // plain tint with no outline. mod.widgets.RoundedView.clip_x = false mod.widgets.RoundedView.clip_y = false mod.widgets.RoundedView.draw_bg.vertex = fn() { let m = 6.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.RoundedView.draw_bg.pixel = fn() { let m = 6.0 let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) 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 c = (lo + hi) * 0.5 let h = max((hi - lo) * 0.5, vec2(0.5, 0.5)) let r = min(2.0 * max(1.0, self.border_radius), min(h.x, h.y)) let d = Material.sd_box(p, c, h, r) var fill = self.color if self.color_2.x > -0.5 { fill = mix(self.color, self.color_2, mix(clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0), clamp((p.x - lo.x) / max(hi.x - lo.x, 1.0), 0.0, 1.0), self.gradient_fill_horizontal)) } let solid = step(0.98, fill.a) let big = step(30.0, min(h.x, h.y) * 2.0) let so = max(Finish.snap(2.6, px), px) * solid * big let ow = max(Finish.snap(mix(1.3, 2.4, big), px), px) * solid let ds = Material.sd_box(p, c + vec2(so, so), h, r) + 1000.0 * step(so, 0.001) let sa = Finish.cover(ds, px) var res = vec4(pa_ink() * sa, sa) let uv = clamp((p - lo) / max(hi - lo, vec2(1.0, 1.0)), vec2(0.0, 0.0), vec2(1.0, 1.0)) let t = uv.x * 0.42 + uv.y * 0.58 var col = mix(fill.rgb, pa_deep(fill.rgb), pa_screen(p - lo, 3.0, (0.03 + 0.22 * smoothstep(0.4, 1.05, t)) * solid * big, px)) var ink = self.border_color if self.border_size > 0.01 { col = mix(col, ink.rgb, Finish.band(d, 0.0, max(self.border_size, px), px) * ink.a) } col = mix(pa_ink(), col, mix(1.0, Finish.cover(d + ow, px), solid)) let a = Finish.cover(d, px) res = Finish.over(res * solid, vec4(col * a * fill.a, a * fill.a)) return res + vec4(ink.rgb * ink.a, ink.a) * Finish.band(d, 0.0, max(self.border_size, px), px) * a * (1.0 - fill.a) * step(0.01, self.border_size) } // The card: a yellow panel screened heavier than the page, outlined in // black on its shadow; a pressable card lifts toward white under the hand. mod.widgets.Card.clip_x = false mod.widgets.Card.clip_y = false mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0} mod.widgets.Card.draw_bg.vertex = fn() { let m = 8.0 return self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Card.draw_bg.pixel = fn() { let m = 8.0 let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return pa_slab(p, self.rect_size, self.rect_pos, px, 9.0, vec3(1.0, 0.80, 0.0), self.hover, 0.0) } // List rows: 22 points, the words 20 points in. Each row carries a small // ink arrowhead at its left; the chosen row is a blue strip outlined in ink // (no shadow: the rows stand edge to edge) with its arrowhead filled white. mod.widgets.ListItem.height = 22.0 mod.widgets.ListItemOne.height = 22.0 mod.widgets.ListItem.padding = mod.turtle.Inset{left: 20.0 right: 8.0 top: 0.0 bottom: 2.0} mod.widgets.ListItemOne.padding = mod.turtle.Inset{left: 20.0 right: 8.0 top: 0.0 bottom: 2.0} mod.widgets.ListItem.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * self.rect_size let live = 1.0 - self.disabled let lo = vec2(pa_snap(1.0, o.x, px), pa_snap(1.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x - 2.0, o.x, px), pa_snap(self.rect_size.y - 1.0, o.y, px)) let c = (lo + hi) * 0.5 let d = Material.sd_box(p, c, (hi - lo) * 0.5, 4.0) let ow = max(Finish.snap(1.3, px), px) var res = vec4(1.0, 1.0, 1.0, 1.0) * (0.22 * self.hover + 0.1 * self.down) * live * Finish.cover(d, px) * (1.0 - self.selected) let sel = vec4(mix(pa_ink(), pa_col(1.0), Finish.cover(d + ow, px)), 1.0) * Finish.cover(d, px) res = mix(res, sel, self.selected) let tc = vec2(pa_snap(10.0, o.x, px), (lo.y + hi.y) * 0.5) let dt = pa_sd_tri(p, vec2(tc.x - 2.6, tc.y - 3.6), vec2(tc.x - 2.6, tc.y + 3.6), vec2(tc.x + 3.4, tc.y)) res = Finish.over(res, vec4(pa_ink(), 1.0) * Finish.cover(dt, px)) res = Finish.over(res, vec4(1.0, 1.0, 1.0, 1.0) * Finish.cover(dt + max(Finish.snap(1.1, px), px), px) * mix(0.0, 1.0, self.selected)) return res } mod.widgets.ListItem.draw_text.color = #141414 mod.widgets.ListItem.draw_text_2.color = #3b3418 // The tree's rows stand on the page, clear; the open row is a blue strip // outlined in ink. mod.widgets.FileTreeNode.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let lo = vec2(1.0, 1.0) let hi = self.rect_size - vec2(2.0, 1.0) let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 4.0) let ow = max(Finish.snap(1.3, px), px) let key = vec4(mix(pa_ink(), pa_col(1.0), Finish.cover(d + ow, px)), 1.0) * Finish.cover(d, px) return key * self.active } mod.widgets.FileTreeNode.draw_text.color_active = #000000 mod.widgets.FileTreeNode.draw_icon.color_active = #000000 // ---------------------------------------------------------------- lettering // WORDS. Dark words are printed plain. A pale word (white on a blue or a red // block, a heading's white capitals) is lettered as a comic letterer does // it: the letters ringed by a black ink outline a device pixel and a half // wide, and, on the page, a solid black shadow of the outlined letters a // little down and to the right. A word given the comic's red (a caption over // a group) is printed in black with a solid red copy of its letters a point // and a quarter down and to the right, a misregistered second ink, nothing // round the letters. A block's legend drops with the block when it is // pressed, and the chosen segment's and tab's word stays dropped. The glyph // quads are grown (aa_pad_px) so the outline and the shadows are never cut // at their edge. let pa_words = fn() { let c = self.get_color() let red = step(0.6, c.r) * step(c.g, 0.25) * step(c.b, 0.25) let pale = smoothstep(0.42, 0.62, dot(c.rgb, vec3(0.2126, 0.7152, 0.0722))) * (1.0 - red) if self.texture_index > 2.5 { if self.slug_curve_count() < 0.5 { return vec4(0.0, 0.0, 0.0, 0.0) } let ex = dFdx(self.pos) let ey = dFdy(self.pos) let sample = self.pos let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001) let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001) let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0} let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0} var alpha_sum = 0.0 var sample_index = 0.0 loop { if sample_index >= grid * grid { break } let row = floor(sample_index * inv_grid) let col = sample_index - row * grid let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y) alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y) sample_index = sample_index + 1.0 } let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0) let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max) let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0) let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0) let fc = mix(c.rgb, vec3(0.067, 0.067, 0.071), red) let main = vec4(fc * c.a * alpha, c.a * alpha) if red > 0.5 { let s = 1.25 * max(self.draw_pass.dpi_factor, 0.5) let sh = clamp(self.alpha_at(sample - (ex + ey) * s, px_x, px_y), 0.0, 1.0) return Finish.over(vec4(0.910, 0.125, 0.165, 1.0) * sh * c.a, main) } if pale < 0.01 { return main } let r = 1.5 let k = r * 0.7071 var o = alpha o = max(o, self.alpha_at(sample + ex * r, px_x, px_y)) o = max(o, self.alpha_at(sample - ex * r, px_x, px_y)) o = max(o, self.alpha_at(sample + ey * r, px_x, px_y)) o = max(o, self.alpha_at(sample - ey * r, px_x, px_y)) o = max(o, self.alpha_at(sample + (ex + ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample - (ex + ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample + (ex - ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample - (ex - ey) * k, px_x, px_y)) let s = 2.0 let sq = sample - (ex + ey) * s var sh = self.alpha_at(sq, px_x, px_y) sh = max(sh, self.alpha_at(sq + ex * r, px_x, px_y)) sh = max(sh, self.alpha_at(sq - ex * r, px_x, px_y)) sh = max(sh, self.alpha_at(sq + ey * r, px_x, px_y)) sh = max(sh, self.alpha_at(sq - ey * r, px_x, px_y)) let ink = vec4(0.067, 0.067, 0.071, 1.0) let w = c.a * pale var res = ink * clamp(sh, 0.0, 1.0) * w res = Finish.over(res, ink * clamp(o, 0.0, 1.0) * w) return Finish.over(res, main) } return self.sample_text_pixel() } // The same lettering for a block's legend: no shadow of its own (the block // carries one), and it drops with the block, `push` device pixels down and // to the right. let pa_legend = fn() { let c = self.get_color() let pale = smoothstep(0.42, 0.62, dot(c.rgb, vec3(0.2126, 0.7152, 0.0722))) let push = 2.0 * max(self.draw_pass.dpi_factor, 0.5) * step(0.5, self.down) if self.texture_index > 2.5 { if self.slug_curve_count() < 0.5 { return vec4(0.0, 0.0, 0.0, 0.0) } let ex = dFdx(self.pos) let ey = dFdy(self.pos) let sample = self.pos - (ex + ey) * push let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001) let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001) let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0} let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0} var alpha_sum = 0.0 var sample_index = 0.0 loop { if sample_index >= grid * grid { break } let row = floor(sample_index * inv_grid) let col = sample_index - row * grid let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y) alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y) sample_index = sample_index + 1.0 } let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0) let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max) let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0) let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0) let main = vec4(c.rgb * c.a * alpha, c.a * alpha) if pale < 0.01 { return main } let r = 1.5 let k = r * 0.7071 var o = alpha o = max(o, self.alpha_at(sample + ex * r, px_x, px_y)) o = max(o, self.alpha_at(sample - ex * r, px_x, px_y)) o = max(o, self.alpha_at(sample + ey * r, px_x, px_y)) o = max(o, self.alpha_at(sample - ey * r, px_x, px_y)) o = max(o, self.alpha_at(sample + (ex + ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample - (ex + ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample + (ex - ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample - (ex - ey) * k, px_x, px_y)) let ink = vec4(0.067, 0.067, 0.071, 1.0) return Finish.over(ink * clamp(o, 0.0, 1.0) * c.a * pale, main) } return self.sample_text_pixel() } // The word of a chosen segment or tab: plain black, dropped with its block. let pa_pushed = fn() { let c = self.get_color() if self.texture_index > 2.5 { if self.slug_curve_count() < 0.5 { return vec4(0.0, 0.0, 0.0, 0.0) } let push = 2.0 * max(self.draw_pass.dpi_factor, 0.5) let ex = dFdx(self.pos) let ey = dFdy(self.pos) let sample = self.pos - (ex + ey) * push let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001) let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001) let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0} let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0} var alpha_sum = 0.0 var sample_index = 0.0 loop { if sample_index >= grid * grid { break } let row = floor(sample_index * inv_grid) let col = sample_index - row * grid let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y) alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y) sample_index = sample_index + 1.0 } let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0) let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max) let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0) let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0) return vec4(c.rgb * c.a * alpha, c.a * alpha) } return self.sample_text_pixel() } // The radio tab's word drops with its tab when the tab is in use. let pa_tab_words = fn() { let c = self.get_color() if self.texture_index > 2.5 { if self.slug_curve_count() < 0.5 { return vec4(0.0, 0.0, 0.0, 0.0) } let push = 2.0 * max(self.draw_pass.dpi_factor, 0.5) * step(0.5, max(self.active, self.down)) let ex = dFdx(self.pos) let ey = dFdy(self.pos) let sample = self.pos - (ex + ey) * push let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001) let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001) let grid = if self.aa_4x4 > 0.5 {4.0} else if self.aa_2x2 > 0.5 {2.0} else {1.0} let inv_grid = if self.aa_4x4 > 0.5 {0.25} else if self.aa_2x2 > 0.5 {0.5} else {1.0} var alpha_sum = 0.0 var sample_index = 0.0 loop { if sample_index >= grid * grid { break } let row = floor(sample_index * inv_grid) let col = sample_index - row * grid let offset = (vec2(col, row) + 0.5 - grid * 0.5) * inv_grid * vec2(px_x, px_y) alpha_sum = alpha_sum + self.alpha_at(sample + offset, px_x, px_y) sample_index = sample_index + 1.0 } let alpha_base = clamp(alpha_sum * inv_grid * inv_grid, 0.0, 1.0) let darken = clamp(max(px_x, px_y) * self.stem_darken, 0.0, self.stem_darken_max) let edge_weight = clamp(1.0 - abs(alpha_base * 2.0 - 1.0), 0.0, 1.0) let alpha = clamp(alpha_base + darken * edge_weight, 0.0, 1.0) return vec4(c.rgb * c.a * alpha, c.a * alpha) } return self.sample_text_pixel() } // HEADINGS are lettered: white capitals ringed in ink on their shadow, // whatever ink the heading was given. let pa_heading = fn() { let c0 = self.get_color() let c = vec4(1.0, 1.0, 1.0, c0.a) if self.texture_index > 2.5 { if self.slug_curve_count() < 0.5 { return vec4(0.0, 0.0, 0.0, 0.0) } let ex = dFdx(self.pos) let ey = dFdy(self.pos) let sample = self.pos let px_x = max(abs(dFdx(self.pos.x)) + abs(dFdy(self.pos.x)), 0.00001) let px_y = max(abs(dFdx(self.pos.y)) + abs(dFdy(self.pos.y)), 0.00001) let alpha = clamp(self.alpha_at(sample, px_x, px_y), 0.0, 1.0) let main = vec4(c.rgb * c.a * alpha, c.a * alpha) let r = 1.6 let k = r * 0.7071 var o = alpha o = max(o, self.alpha_at(sample + ex * r, px_x, px_y)) o = max(o, self.alpha_at(sample - ex * r, px_x, px_y)) o = max(o, self.alpha_at(sample + ey * r, px_x, px_y)) o = max(o, self.alpha_at(sample - ey * r, px_x, px_y)) o = max(o, self.alpha_at(sample + (ex + ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample - (ex + ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample + (ex - ey) * k, px_x, px_y)) o = max(o, self.alpha_at(sample - (ex - ey) * k, px_x, px_y)) let s = 2.2 let sq = sample - (ex + ey) * s var sh = self.alpha_at(sq, px_x, px_y) sh = max(sh, self.alpha_at(sq + ex * r, px_x, px_y)) sh = max(sh, self.alpha_at(sq - ex * r, px_x, px_y)) sh = max(sh, self.alpha_at(sq + ey * r, px_x, px_y)) sh = max(sh, self.alpha_at(sq - ey * r, px_x, px_y)) let ink = vec4(0.067, 0.067, 0.071, 1.0) var res = ink * clamp(sh, 0.0, 1.0) * c.a res = Finish.over(res, ink * clamp(o, 0.0, 1.0) * c.a) return Finish.over(res, main) } return self.sample_text_pixel() } mod.widgets.Label.draw_text.pixel = pa_words mod.widgets.Label.draw_text.aa_pad_px = uniform(5.0) mod.widgets.H1.draw_text.pixel = pa_heading mod.widgets.H2.draw_text.pixel = pa_heading mod.widgets.H3.draw_text.pixel = pa_heading mod.widgets.H4.draw_text.pixel = pa_heading mod.widgets.H5.draw_text.pixel = pa_heading mod.widgets.H6.draw_text.pixel = pa_heading mod.widgets.H1.draw_text.aa_pad_px = uniform(5.0) mod.widgets.H2.draw_text.aa_pad_px = uniform(5.0) mod.widgets.H3.draw_text.aa_pad_px = uniform(5.0) mod.widgets.H4.draw_text.aa_pad_px = uniform(5.0) mod.widgets.H5.draw_text.aa_pad_px = uniform(5.0) mod.widgets.H6.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonFlat.draw_text.pixel = pa_legend mod.widgets.Button.draw_text.pixel = pa_legend mod.widgets.ButtonPrimary.draw_text.pixel = pa_legend mod.widgets.ButtonSecondary.draw_text.pixel = pa_legend mod.widgets.ButtonTertiary.draw_text.pixel = pa_legend mod.widgets.ButtonDanger.draw_text.pixel = pa_legend mod.widgets.ButtonOutline.draw_text.pixel = pa_legend mod.widgets.ButtonFlatter.draw_text.pixel = pa_legend mod.widgets.ButtonXs.draw_text.pixel = pa_legend mod.widgets.ButtonSm.draw_text.pixel = pa_legend mod.widgets.ButtonLg.draw_text.pixel = pa_legend mod.widgets.ButtonXl.draw_text.pixel = pa_legend mod.widgets.ButtonFlat.draw_text.aa_pad_px = uniform(5.0) mod.widgets.Button.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonPrimary.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonSecondary.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonTertiary.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonDanger.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonOutline.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonFlatter.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonXs.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonSm.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonLg.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonXl.draw_text.aa_pad_px = uniform(5.0) mod.widgets.ButtonFlat.draw_description.pixel = pa_words mod.widgets.ButtonFlat.draw_description.aa_pad_px = uniform(5.0) mod.widgets.ButtonCompound.draw_description.pixel = pa_words mod.widgets.ButtonCompound.draw_description.aa_pad_px = uniform(5.0) mod.widgets.DropDownFlat.draw_text.pixel = pa_legend mod.widgets.DropDownFlat.draw_text.aa_pad_px = uniform(5.0) mod.widgets.RadioButtonTabFlat.draw_text.pixel = pa_tab_words mod.widgets.RadioButtonTabFlat.draw_text.aa_pad_px = uniform(5.0) mod.widgets.SegmentedControlFlat.draw_text_selected.pixel = pa_pushed mod.widgets.SegmentedControlFlat.draw_text_selected.aa_pad_px = uniform(5.0) mod.widgets.Tabs.draw_text_active.pixel = pa_pushed mod.widgets.Tabs.draw_text_active.aa_pad_px = uniform(5.0) // The words beside the controls -- a check box's, a toggle's, a radio's, a // slider's name and value, a knob's -- are set in the slanted bold hand. mod.widgets.CheckBoxFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.ToggleFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.RadioButtonFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.SliderFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.SliderMinimal.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.SliderRoundFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.RangeSliderFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.SliderFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.RotaryFlat.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} mod.widgets.SliderFlat.text_input.draw_text.color = #141414 mod.widgets.SliderMinimal.text_input.draw_text.color = #141414 mod.widgets.SliderRoundFlat.text_input.draw_text.color = #141414 mod.widgets.RotaryFlat.text_input.draw_text.color = #111111 mod.widgets.RotaryFlat.text_input.draw_text.color_hover = uniform(#000000) mod.widgets.RotaryFlat.text_input.draw_text.color_focus = uniform(#000000) mod.widgets.RotaryFlat.text_input.draw_text.color_drag = uniform(#000000) mod.widgets.RotaryFlat.text_input.draw_text.color_empty = uniform(#2a2a2a) mod.widgets.RotaryFlat.text_input.draw_text.color_empty_hover = uniform(#1a1a1a) mod.widgets.RotaryFlat.text_input.draw_text.color_empty_focus = uniform(#000000) mod.widgets.RotaryFlat.text_input.draw_text.color_disabled = uniform(#3a3a3a) mod.widgets.Rotary.text_input.draw_text.color = #111111 mod.widgets.Rotary.text_input.draw_text.color_hover = uniform(#000000) mod.widgets.Rotary.text_input.draw_text.color_focus = uniform(#000000) mod.widgets.Rotary.text_input.draw_text.color_drag = uniform(#000000) mod.widgets.Rotary.text_input.draw_text.color_empty = uniform(#2a2a2a) mod.widgets.Rotary.text_input.draw_text.color_empty_hover = uniform(#1a1a1a) mod.widgets.Rotary.text_input.draw_text.color_empty_focus = uniform(#000000) mod.widgets.Rotary.text_input.draw_text.color_disabled = uniform(#3a3a3a) mod.widgets.Rotary.text_input.draw_text.text_style = mod.theme.font_bold{font_size: 10.0} // The value a slider or a rotary shows is held selected for typing over; // unfocused, the selection is not shown. mod.widgets.RotaryFlat.text_input.draw_selection.color = uniform(#00000000) mod.widgets.RotaryFlat.text_input.draw_selection.color_hover = uniform(#00000000) mod.widgets.RotaryFlat.text_input.draw_selection.color_focus = uniform(#ffffff73) mod.widgets.Rotary.text_input.draw_selection.color = uniform(#00000000) mod.widgets.Rotary.text_input.draw_selection.color_hover = uniform(#00000000) mod.widgets.Rotary.text_input.draw_selection.color_focus = uniform(#ffffff73) mod.widgets.SliderFlat.text_input.draw_selection.color = uniform(#00000000) mod.widgets.SliderFlat.text_input.draw_selection.color_hover = uniform(#00000000) mod.widgets.SliderFlat.text_input.draw_selection.color_focus = uniform(#ffffff73) mod.widgets.Slider.text_input.draw_selection.color = uniform(#00000000) mod.widgets.Slider.text_input.draw_selection.color_hover = uniform(#00000000) mod.widgets.Slider.text_input.draw_selection.color_focus = uniform(#ffffff73) mod.widgets.SliderMinimal.text_input.draw_selection.color = uniform(#00000000) mod.widgets.SliderMinimal.text_input.draw_selection.color_hover = uniform(#00000000) mod.widgets.SliderMinimal.text_input.draw_selection.color_focus = uniform(#ffffff73) mod.widgets.SliderRoundFlat.text_input.draw_selection.color = uniform(#00000000) mod.widgets.SliderRoundFlat.text_input.draw_selection.color_hover = uniform(#00000000) mod.widgets.SliderRoundFlat.text_input.draw_selection.color_focus = uniform(#ffffff73) mod.widgets.SliderRound.text_input.draw_selection.color = uniform(#00000000) mod.widgets.SliderRound.text_input.draw_selection.color_hover = uniform(#00000000) mod.widgets.SliderRound.text_input.draw_selection.color_focus = uniform(#ffffff73) // ---------------------------------------------------------------- field row // 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 added to the library later joins // the row by being named here. let field_height = 24.0 let field_pad_v = 3.0 let field_radius = 2.5 let field_border = 0.67 // The room a Fit field takes: a minimum height, and the padding with it. let field_room = fn(w) { w.min_height = field_height w.padding.top = field_pad_v w.padding.bottom = field_pad_v + 1.0 } // The box a field draws, given the draw object. 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. let field_frame = fn(w) { w.min_height = field_height } field_room(mod.widgets.TextInput) field_face(mod.widgets.TextInput.draw_bg) field_room(mod.widgets.ComboBox) field_face(mod.widgets.ComboBox.draw_bg) field_room(mod.widgets.DropDown) field_face(mod.widgets.DropDown.draw_bg) field_room(mod.widgets.DropDown2) field_face(mod.widgets.DropDown2.draw_bg) field_room(mod.widgets.FieldWell) field_face(mod.widgets.FieldWell.draw_bg) field_room(mod.widgets.TagField) field_face(mod.widgets.TagField.draw_bg) field_frame(mod.widgets.NumberField) mod.widgets.NumberField.draw_bg.border_radius = field_radius field_frame(mod.widgets.ValueInput) field_frame(mod.widgets.DateField) field_frame(mod.widgets.TimeField) field_frame(mod.widgets.DatePicker) field_frame(mod.widgets.DateRangePicker) mod.widgets.WellInput.min_height = 0.0 // The drop-down stands in the field row: the text box's padding, so the // two are one height (sheet_checks::field_row). mod.widgets.DropDown.padding.top = 3.0 mod.widgets.DropDown.padding.bottom = 4.0 // ---------------------------------------------------------------- rules // A rule is a line of black ink. mod.widgets.Hr.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let lw = max(Finish.snap(1.4, px), px) let y0 = pa_snap(self.rect_size.y * 0.5 - lw * 0.5, self.rect_pos.y, px) return vec4(pa_ink(), 1.0) * step(y0, p.y) * step(p.y, y0 + lw) } mod.widgets.Vr.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let lw = max(Finish.snap(1.4, px), px) let x0 = pa_snap(self.rect_size.x * 0.5 - lw * 0.5, self.rect_pos.x, px) return vec4(pa_ink(), 1.0) * step(x0, p.x) * step(p.x, x0 + lw) } // ---------------------------------------------------------------- ground // Plain views stop clipping so a shadow can fall past their edge; scroll // views keep clipping their content. mod.widgets.View.clip_x = false mod.widgets.View.clip_y = false mod.widgets.Window.pass.clear_color = #fdd503 // A splitter is the gutter between two comic panels: a strip of the // frame's blue sky under its dot screen, inked along both sides and rounded // off at its ends, so the panes either side read as panels of one page. // Under the hand or held, a grip of white paper outlined in ink stands in // its middle. mod.widgets.Splitter.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let p = self.pos * self.rect_size let w = p + self.rect_pos let v = self.is_vertical let across = mix(p.y, p.x, v) let along = mix(p.x, p.y, v) let wide = mix(self.rect_size.y, self.rect_size.x, v) let long = mix(self.rect_size.x, self.rect_size.y, v) let r = wide * 0.5 let q = vec2(along - long * 0.5, across - r) let d = Material.sd_box(q, vec2(0.0, 0.0), vec2(long * 0.5, r), r) let lw = max(Finish.snap(1.3, px), px) let act = clamp(max(max(self.hover, self.drag), self.focus), 0.0, 1.0) var sky = mix(pa_col(1.0), vec3(0.02, 0.32, 0.68), pa_screen(w, 3.6, 0.30, px)) sky = sky + (vec3(1.0, 1.0, 1.0) - sky) * 0.15 * act let col = mix(pa_ink(), sky, Finish.cover(d + lw, px)) var res = vec4(col, 1.0) * Finish.cover(d, px) let gq = vec2(along - long * 0.5, across - r) let gh = vec2(min(self.bar_size, long) * 0.25, max(r - lw - 0.2, 0.8)) let dg = Material.sd_box(gq, vec2(0.0, 0.0), gh, gh.y) let grip = vec4(mix(pa_ink(), pa_col(3.0), Finish.cover(dg + max(Finish.snap(0.8, px), px), px)), 1.0) * Finish.cover(dg, px) return Finish.over(res, grip * act) } // The page: yellow newsprint under a light dot screen, its dots a deeper // orange-yellow, growing slowly toward the window's edges and drifting a // little over the page, so it is never one flat field and never busy under // words. Screened in window points: it does not move with a scroll. let pa_page = fn(w: vec2, sz: vec2, px: float) -> vec3 { let e = min(min(w.x, sz.x - w.x), min(w.y, sz.y - w.y)) let edge = 1.0 - smoothstep(0.0, 240.0, e) let drift = Finish.noise(w * 0.0035 + vec2(3.1, 7.7)) let cov = 0.07 + 0.09 * drift * drift + 0.14 * edge * edge return mix(pa_col(0.0), vec3(0.95, 0.64, 0.0), pa_screen(w, 4.6, cov, px)) } // A burst: a jagged star round `c`, `n` spikes reaching from `r0` toward // `r1`, each a length of its own. Its distance in points. let pa_burst_d = fn(w: vec2, c: vec2, r0: float, r1: float, n: float, seed: float, px: float) -> float { let q = w - c let ang = atan2(q.y, q.x) + PI + seed let f = ang / (2.0 * PI) * n let k = floor(f) let fr = f - k let tri = 1.0 - abs(fr * 2.0 - 1.0) let kk = k - n * floor(k / n) let len = 0.5 + 0.5 * Finish.hash(vec2(kk * 1.7, seed * 13.0 + 2.0)) let rr = r0 + (r1 - r0) * len * pow(tri, 1.4) let dd = length(q) - rr let g = max(length(vec2(dFdx(dd), dFdy(dd))) / px, 0.25) return dd / g } // The burst printed over `col`: `fill` screened toward its tips, an ink // outline, at strength `amt`. let pa_burst = fn(col: vec3, w: vec2, c: vec2, r0: float, r1: float, n: float, seed: float, fill: vec3, px: float, amt: float) -> vec3 { let d = pa_burst_d(w, c, r0, r1, n, seed, px) var f = mix(fill, pa_deep(fill), pa_screen(w - c, 2.6, 0.32 * smoothstep(r0, r1, length(w - c)), px)) f = mix(pa_ink(), f, Finish.cover(d + 1.3, px)) return mix(col, f, Finish.cover(d, px) * amt) } // THE FRAME. Round the page runs a thick black ink line, and outside it the // comic's noise: a blue sky under a coarse dot screen all round, and along // the top, in the caption band, speed lines, a pair of bursts, a racing // stripe and a chequered flag in the corner -- kept off the middle, where // the window's title stands, and off the window's buttons. let pa_masthead = fn(w: vec2, sz: vec2, px: float) -> vec3 { let W = sz.x var col = mix(pa_col(1.0), vec3(0.02, 0.32, 0.68), pa_screen(w, 3.6, 0.30, px)) if w.y > 34.0 { return col } let tx = abs(w.x - W * 0.5) let quiet = max(1.0 - smoothstep(80.0, 100.0, tx), smoothstep(W - 168.0, W - 146.0, w.x)) let busy = 1.0 - quiet // Speed lines: white streaks thinning to nothing, one row in two. let row = floor(w.y / 3.4) let x0 = Finish.hash(vec2(row, 3.0)) * W let len = 60.0 + 180.0 * Finish.hash(vec2(row, 11.0)) let along = (w.x - x0) / len let thick = 0.8 * clamp(1.0 - along, 0.0, 1.0) * step(0.0, along) let sl = Finish.cover(abs(w.y - (row + 0.5) * 3.4) - thick, px) * step(0.5, Finish.hash(vec2(row, 7.0))) col = mix(col, vec3(1.0, 1.0, 1.0), sl * busy * 0.9) // A racing stripe leaning across the band: a red band between white // pinstripes, inked at its edges. let s = w.x + w.y * 0.6 - (W - 260.0) let red = Finish.cover(abs(s) - 8.0, px) let wh = Finish.cover(abs(abs(s) - 12.6) - 2.6, px) let edges = Finish.cover(abs(abs(s) - 8.6) - 0.6, px) + Finish.cover(abs(abs(s) - 15.8) - 0.6, px) col = mix(col, vec3(0.97, 0.96, 0.93), wh * busy) col = mix(col, pa_col(2.0), red * busy) col = mix(col, pa_ink(), clamp(edges, 0.0, 1.0) * busy) let s2 = w.x + w.y * 0.6 - (W * 0.5 - 200.0) col = mix(col, vec3(0.97, 0.96, 0.93), Finish.cover(abs(s2) - 3.2, px) * busy) col = mix(col, pa_ink(), Finish.cover(abs(abs(s2) - 3.8) - 0.6, px) * busy) // Lightning slashes: long thin wedges of yellow and red leaning across // the band, inked. var si = 0.0 loop { if si > 5.5 { break } let sx = W * (0.05 + 0.155 * si) + 37.0 * Finish.hash(vec2(si, 4.0)) let base = vec2(sx, 31.0) let tip = vec2(sx + 46.0 + 20.0 * Finish.hash(vec2(si, 9.0)), -2.0) let side = vec2(sx + 9.0 + 4.0 * Finish.hash(vec2(si, 2.0)), 31.0) let dsl = pa_sd_tri(w, base, side, tip) let sc = mix(pa_col(0.0), pa_col(2.0), step(0.5, fract(si * 0.5))) let sf = mix(pa_ink(), sc, Finish.cover(dsl + 1.1, px)) col = mix(col, sf, Finish.cover(dsl, px) * busy) si = si + 1.0 } // The bursts. col = pa_burst(col, w, vec2(150.0, 15.0), 13.0, 34.0, 11.0, 0.3, pa_col(2.0), px, busy) col = pa_burst(col, w, vec2(150.0, 15.0), 6.5, 17.0, 9.0, 1.1, pa_col(0.0), px, busy) col = pa_burst(col, w, vec2(W * 0.5 + 175.0, 14.0), 10.0, 27.0, 10.0, 2.0, pa_col(0.0), px, busy) col = pa_burst(col, w, vec2(W * 0.5 + 175.0, 14.0), 4.5, 12.0, 7.0, 0.6, pa_col(2.0), px, busy) // The chequered flag, waving, in the top left corner. let fu = vec2(w.x, w.y + 2.2 * sin(w.x * 0.16 + 0.5)) let cs = 6.0 let qx = (abs(fract(fu.x / (2.0 * cs)) - 0.5) * 2.0 * cs) - cs * 0.5 let qy = (abs(fract(fu.y / (2.0 * cs)) - 0.5) * 2.0 * cs) - cs * 0.5 let cx = clamp(qx / px + 0.5, 0.0, 1.0) let cy = clamp(qy / px + 0.5, 0.0, 1.0) let chk = cx * cy + (1.0 - cx) * (1.0 - cy) var fc = mix(vec3(0.97, 0.96, 0.93), pa_ink(), chk) fc = mix(fc, vec3(0.62, 0.60, 0.56), pa_screen(w, 2.4, 0.16, px) * (1.0 - chk)) let fx = 62.0 + 3.0 * sin(w.y * 0.33) col = mix(col, fc, Finish.cover(w.x - fx, px)) col = mix(col, pa_ink(), Finish.cover(abs(w.x - fx) - 0.9, px)) return col } mod.widgets.Window.show_bg = true mod.widgets.Window.draw_bg.pixel = fn() { let px = pa_px(self.draw_pass.dpi_factor) let sz = self.rect_size let w = self.pos * sz + self.rect_pos let lo = vec2(4.0, 31.0) let hi = sz - vec2(4.0, 4.0) let dp = Material.sd_box(w, (lo + hi) * 0.5, (hi - lo) * 0.5, 8.0) var col = pa_page(w, sz, px) let iw = max(Finish.snap(2.0, px), px) col = mix(col, pa_masthead(w, sz, px), 1.0 - Finish.cover(dp - iw, px)) col = mix(col, pa_ink(), Finish.ring_out(dp, 0.0, iw, px)) // The window's own edge: a hairline of ink. let de = Material.sd_box(w, sz * 0.5, sz * 0.5, 0.0) col = mix(col, pa_ink(), Finish.band(de, 0.0, px, px)) return vec4(col, 1.0) } // ---------------------------------------------------------------- screens // A SCREEN: night-blue glass in a thick ink outline on its solid shadow, // the readouts' and the meter's one housing; two short white strokes of a // comic shine across its upper right corner. Its figures are the screen // ink, yellow. let pa_glass = fn(p: vec2, lo: vec2, hi: vec2, px: float, fill: vec3) -> vec4 { let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = 6.0 let ow = max(Finish.snap(2.0, px), px) let so = max(Finish.snap(2.6, px), px) let d = Material.sd_box(p, c, h, r) let sa = Finish.cover(Material.sd_box(p, c + vec2(so, so), h, r), px) var res = vec4(pa_ink() * sa, sa) let t = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0) var col = mix(fill, fill * 0.6, pa_screen(p - lo, 2.6, 0.18 * smoothstep(0.35, 1.0, t), px)) col = mix(pa_ink(), col, Finish.cover(d + ow, px)) let a = Finish.cover(d, px) return Finish.over(res, vec4(col * a, a)) } mod.widgets.ScreenView.clip_x = false mod.widgets.ScreenView.clip_y = false mod.widgets.ScreenView.padding = mod.turtle.Inset{top: 9.0 right: 11.0 bottom: 9.0 left: 11.0} mod.widgets.ScreenView.draw_bg.vertex = fn() { let m = 6.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.ScreenView.draw_bg.pixel = fn() { let m = 6.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), pa_snap(self.rect_size.y, o.y, px)) var fill = self.color.rgb if self.scan > 0.0 { fill = fill * mix(1.0, Finish.scan(p.y, px, 3.0 * px, self.scan), 0.6) } return pa_glass(p, lo, hi, px, fill) * self.color.a } mod.widgets.ScreenView.sheen = 0.9 // The shine: two short white strokes leaning across the upper right corner. let pa_shine = fn(p: vec2, lo: vec2, hi: vec2, px: float, amt: float) -> vec4 { let k = vec2(hi.x - 9.0, lo.y + 4.6) let a1 = pa_seg(p, k + vec2(-2.4, 2.4), k + vec2(2.4, -2.4)) - 0.75 let a2 = pa_seg(p, k + vec2(1.4, 3.9), k + vec2(4.2, 1.1)) - 0.6 let a = Finish.cover(min(a1, a2), px) * amt return vec4(a, a, a, a) } mod.widgets.ScreenView.draw_glass.pixel = fn() { let p = self.pos * self.rect_size let px = pa_px(self.draw_pass.dpi_factor) 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)) - self.rect_pos return pa_shine(p, lo, hi, px, clamp(self.sheen, 0.0, 1.0)) } // THE NEEDLE METER is a screen: the same outline and night-blue glass, its // scale printed on the glass in the screen's yellow, the red zone a red band // along the arc, the needle red outlined in ink, its root under a yellow // disc outlined in ink. mod.widgets.NeedleMeter.clip_x = false mod.widgets.NeedleMeter.clip_y = false mod.widgets.NeedleMeter.draw_bg.color_zone = uniform(#xff4b3e) mod.widgets.NeedleMeter.draw_bg.color_needle = uniform(#xff4b3e) mod.widgets.NeedleMeter.draw_bg.vertex = fn() { let m = 6.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.NeedleMeter.draw_bg.pixel = fn() { let m = 6.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let lo = vec2(pa_snap(0.0, o.x, px), pa_snap(0.0, o.y, px)) let hi = vec2(pa_snap(self.rect_size.x, o.x, px), pa_snap(self.rect_size.y, o.y, px)) var res = pa_glass(p, lo, hi, px, vec3(0.055, 0.106, 0.180)) 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 ink = vec3(1.0, 0.824, 0.102) let inside = Finish.cover(Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5 - vec2(2.0, 2.0), 4.0), px) let arc = Finish.cover(abs(r - rr) - px * 0.85, px) * on_scale let nn = 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) * nn + 0.5) let ak = (k / nn - 0.5) * sweep let perp = abs(q.x * cos(ak) + q.y * sin(ak)) let mn = max(floor(self.minors + 0.5), 1.0) let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001)) let len = mix(self.tick_minor, self.tick_major, major) let radial = max(rr - len - r, r - rr) let tick = Finish.cover(perp - px * mix(0.7, 1.2, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak)) res = Finish.over(res, vec4(ink, 1.0) * max(arc, tick) * inside) if self.zone < 0.999 { let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px) let zone_r = Finish.cover(max(rr + px - r, r - rr - 3.0), px) res = Finish.over(res, vec4(self.color_zone.rgb, 1.0) * zone_t * zone_r * inside) } // The needle: red, inked along both sides. let na = self.needle_angle(self.value) let nd = vec2(sin(na), 0.0 - cos(na)) let along = dot(q, nd) let across = abs(q.x * nd.y - q.y * nd.x) let nl = Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along, px) * inside res = Finish.over(res, vec4(pa_ink(), 1.0) * Finish.cover(across - 1.7, px) * nl) res = Finish.over(res, vec4(self.color_needle.rgb, 1.0) * Finish.cover(across - 0.8, px) * nl) // The pivot: a yellow disc outlined in ink. res = Finish.over(res, pa_disc(p, pv, max(self.pivot_size, 3.2), px, pa_col(0.0), 0.0, 0.0, 0.0, 0.0, 0.0) * inside) res = Finish.over(res, pa_shine(p, lo, hi, px, 0.9)) return res * self.color.a * self.opacity } // ---------------------------------------------------------------- switches // THE SWITCHES are the toggle in their own inks: the rocker prints red when // it is on, its key square; the slide switch prints green, its key round. mod.widgets.ToggleRocker.draw_bg.size = uniform(18.0) mod.widgets.ToggleSlide.draw_bg.size = uniform(18.0) mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(1.89) mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(1.89) mod.widgets.ToggleRocker.label_walk.margin.left = 34.0 mod.widgets.ToggleSlide.label_walk.margin.left = 34.0 mod.widgets.ToggleRocker.draw_bg.pixel = fn() { let m = 5.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let tw = self.size * self.pill_aspect let th = self.size let x0 = (self.rect_size.x - tw) * self.mark_at_end let y0 = self.rect_size.y * 0.5 - th * 0.5 let lo = vec2(pa_snap(x0, o.x, px), pa_snap(y0, o.y, px)) let hi = vec2(pa_snap(x0 + tw, o.x, px), pa_snap(y0 + th, o.y, px)) let kt = mix(self.active, self.drag_pos, self.drag) return pa_toggle(p, lo, hi, o, px, kt, self.active, self.hover, max(self.pressed, self.down), self.disabled, self.focus, pa_col(2.0), 0.0) } mod.widgets.ToggleSlide.draw_bg.pixel = fn() { let m = 5.0 let px = pa_px(self.draw_pass.dpi_factor) let o = self.rect_pos let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let tw = self.size * self.pill_aspect let th = self.size let x0 = (self.rect_size.x - tw) * self.mark_at_end let y0 = self.rect_size.y * 0.5 - th * 0.5 let lo = vec2(pa_snap(x0, o.x, px), pa_snap(y0, o.y, px)) let hi = vec2(pa_snap(x0 + tw, o.x, px), pa_snap(y0 + th, o.y, px)) let kt = mix(self.active, self.drag_pos, self.drag) return pa_toggle(p, lo, hi, o, px, kt, self.active, self.hover, max(self.pressed, self.down), self.disabled, self.focus, pa_col(4.0), 1.0) } // ---------------------------------------------------------------- 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.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.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.RadioButtonTabFlat.animator.hover.down.from = {all: Snap} mod.widgets.RadioButtonTabFlat.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.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.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}} true