use mod.prelude.widgets_internal.* // Art nouveau. The ground is aged cream parchment, its fibre and a faint warm // mottle drawn at the device pixel; the window is held in a frame of deep // teal enamel bands edged with bronze-gold wire, the panes set in it with // rounded corners, and the only ornament (lilies, leaves, whiplash stems and // tendrils, all drawn as curves) grows in the frame's corners and the // corners of the big surfaces, never behind words or controls. // A control is an ENAMEL face set in a bronze CLOISON: a raised wire of // bronze-gold round a flat matte enamel (teal for the working colour, ochre // for the primary action, terracotta for the one that deletes, ivory or // parchment for the quiet ones), or a WELL of ivory sunk into the parchment // behind the same wire. Knobs and thumbs are bronze, lamps are cabochons of // coloured glass in bronze settings, screens deep teal glass with amber // figures. The light falls from the upper left. // A ROW OF FIELDS IS ONE HEIGHT. The box metrics are stated once here and // handed to each field below, so the search box, the number beside it and // the drop-down after it stand at one height. let field_height = 28.0 let field_pad_v = mod.theme.mspace_1.top let field_radius = 8.0 // A hairline, for the few stock fields the faces below do not redraw. let field_border = 0.5 let field_room = fn(w) { w.min_height = field_height w.padding.top = field_pad_v w.padding.bottom = field_pad_v } let field_face = fn(d) { d.border_radius = field_radius d.border_size = field_border } // A field that lays its own parts out inside a frame: only the height. let field_frame = fn(w) { w.min_height = field_height } field_room(mod.widgets.TextInput) field_face(mod.widgets.TextInput.draw_bg) field_room(mod.widgets.TextInputFlat) field_room(mod.widgets.ComboBox) field_face(mod.widgets.ComboBox.draw_bg) field_room(mod.widgets.DropDown) field_face(mod.widgets.DropDown.draw_bg) field_room(mod.widgets.DropDown2) field_face(mod.widgets.DropDown2.draw_bg) field_room(mod.widgets.FieldWell) field_face(mod.widgets.FieldWell.draw_bg) field_room(mod.widgets.TagField) field_face(mod.widgets.TagField.draw_bg) field_frame(mod.widgets.NumberField) mod.widgets.NumberField.draw_bg.border_radius = field_radius field_frame(mod.widgets.ValueInput) field_frame(mod.widgets.DateField) field_frame(mod.widgets.TimeField) field_frame(mod.widgets.DatePicker) field_frame(mod.widgets.DateRangePicker) mod.widgets.WellInput.min_height = 0.0 mod.widgets.Window.pass.clear_color = #d8c29c // Plain views stop clipping, so a shadow or a lip may fall into the gap // beside its control. Scroll views keep their own clip. mod.widgets.View.clip_x = false mod.widgets.View.clip_y = false // --------------------------------------------------------------------------- // SHARED PARTS. Distances in points, negative inside, y down; `px` is one // device pixel in points. let g3 = fn(v: float) -> vec3 { return vec3(v, v, v) } // The palette. let an_ink = fn() -> vec3 { return vec3(0.165, 0.129, 0.086) } let an_ivory = fn() -> vec3 { return vec3(0.953, 0.910, 0.812) } let an_parch_c = fn() -> vec3 { return vec3(0.847, 0.761, 0.612) } let an_well_c = fn() -> vec3 { return vec3(0.922, 0.875, 0.769) } let an_teal = fn() -> vec3 { return vec3(0.275, 0.388, 0.353) } let an_teal_dk = fn() -> vec3 { return vec3(0.184, 0.282, 0.259) } let an_teal_lt = fn() -> vec3 { return vec3(0.420, 0.545, 0.490) } let an_ochre = fn() -> vec3 { return vec3(0.851, 0.659, 0.341) } let an_terra = fn() -> vec3 { return vec3(0.627, 0.290, 0.212) } let an_sage = fn() -> vec3 { return vec3(0.776, 0.784, 0.678) } let an_bronze_c = fn() -> vec3 { return vec3(0.776, 0.608, 0.318) } let an_bronze_dk = fn() -> vec3 { return vec3(0.380, 0.270, 0.110) } let an_gold_hi = fn() -> vec3 { return vec3(0.988, 0.894, 0.643) } let an_line_c = fn() -> vec3 { return vec3(0.690, 0.510, 0.220) } let an_shade = fn() -> vec3 { return vec3(0.180, 0.120, 0.050) } let an_glass_c = fn() -> vec3 { return vec3(0.129, 0.208, 0.188) } // A rounded box, true radius. let md_rbox = fn(p: vec2, c: vec2, h: vec2, r: float) -> float { let rr = clamp(r, 0.0, min(h.x, h.y)) let q = abs(p - c) - h + vec2(rr, rr) return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - rr } // A rounded box with a radius per corner (tl, tr, br, bl). let md_r4 = fn(p: vec2, c: vec2, h: vec2, r4: vec4) -> float { let q0 = p - c let rt = mix(r4.y, r4.x, step(q0.x, 0.0)) let rb = mix(r4.z, r4.w, step(q0.x, 0.0)) return clamp(mix(rb, rt, step(q0.y, 0.0)), 0.0, min(h.x, h.y)) } let md_rbox4 = fn(p: vec2, c: vec2, h: vec2, r4: vec4) -> float { return md_rbox(p, c, h, md_r4(p, c, h, r4)) } // The outward normal of a rounded box at `p`. let an_nrm = fn(p: vec2, c: vec2, h: vec2, r: float) -> vec2 { let q0 = p - c let sx = mix(-1.0, 1.0, step(0.0, q0.x)) let sy = mix(-1.0, 1.0, step(0.0, q0.y)) let rr = clamp(r, 0.0, min(h.x, h.y)) let q = abs(q0) - h + vec2(rr, rr) var n = vec2(0.0, 1.0) if q.x > 0.0 && q.y > 0.0 { n = q / max(length(q), 0.0001) } else { if q.x > q.y { n = vec2(1.0, 0.0) } } return vec2(n.x * sx, n.y * sy) } // The corners a button group asks for: a corner written as 0 is a square // join, any other the family's radius. let md_corners = fn(tl: float, tr: float, br: float, bl: float, r: float) -> vec4 { return vec4(mix(r, step(0.5, tl) * r, step(0.0, tl)), mix(r, step(0.5, tr) * r, step(0.0, tr)), mix(r, step(0.5, br) * r, step(0.0, br)), mix(r, step(0.5, bl) * r, step(0.0, bl))) } let md_seg = fn(p: vec2, a: vec2, b: vec2) -> float { let pa = p - a let ba = b - a let k = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0) return length(pa - ba * k) } let md_grow = fn(rp: vec2, rs: vec2, m: float) -> vec4 { return vec4(rp.x - m, rp.y - m, rs.x + 2.0 * m, rs.y + 2.0 * m) } // THE PARCHMENT at a point `w` (points) in the surface's own space: a faint // warm mottle in two slow octaves (a few per cent, wide and soft, so no // stain reads as a blotch), sparse short fibres -- a darker and a paler set, // each the contour of a stretched noise, so they wander -- and a grain of // one device pixel. let an_parch = fn(w: vec2, px: float, base: vec3) -> vec3 { let m = Finish.noise(w * 0.0065) * 0.6 + Finish.noise(w * 0.019 + vec2(3.1, 7.7)) * 0.4 - 0.5 var col = base * (1.0 + 0.055 * m) + vec3(0.010, 0.002, -0.012) * m let n1 = Finish.noise(vec2(w.x * 0.055 + w.y * 0.012, w.y * 0.42 - w.x * 0.05)) let k1 = Finish.noise(w * 0.035 + vec2(9.0, 2.0)) let f1 = (1.0 - smoothstep(0.0, 0.016, abs(n1 - 0.5))) * smoothstep(0.6, 0.8, k1) let n2 = Finish.noise(vec2(w.y * 0.06 - w.x * 0.02, w.x * 0.38 + w.y * 0.07) + vec2(17.0, 5.0)) let k2 = Finish.noise(w * 0.03 + vec2(1.0, 13.0)) let f2 = (1.0 - smoothstep(0.0, 0.014, abs(n2 - 0.5))) * smoothstep(0.64, 0.82, k2) col = mix(col, col * 0.88, f1 * 0.32) col = mix(col, min(col * 1.03 + vec3(0.01, 0.01, 0.008), vec3(1.0, 1.0, 1.0)), f2 * 0.22) // the tooth of the skin: a fine streaky noise across the grain let tooth = Finish.noise(vec2(w.x * 0.9 + w.y * 0.3, w.y * 0.22 - w.x * 0.08)) - 0.5 col = col * (1.0 + tooth * 0.022) return col + g3(Finish.grain(w / px, 0.010)) } // A RAISED BRONZE WIRE `w` points wide lying just inside an edge: `d` the // edge's distance (negative inside), `n` its outward normal. Lit from the // upper left: the half of the wire that faces the light catches a warm // highlight, the other half falls toward dark bronze; a dark hairline at // both of its edges keeps it crisp on any ground. Premultiplied. let an_wire = fn(d: float, n: vec2, w: float, px: float, glint: float) -> vec4 { let u = clamp(-d / max(w, 0.01), 0.0, 1.0) let k = 1.0 - 2.0 * u let bulge = sqrt(max(1.0 - k * k, 0.0)) let facing = dot(n * k, vec2(-0.42, -0.91)) var col = mix(an_bronze_dk(), an_bronze_c(), 0.35 + 0.65 * bulge) col = col * (0.80 + 0.32 * facing) col = mix(col, an_gold_hi(), smoothstep(0.2, 0.85, facing * 0.8 + 0.3 * bulge) * 0.6 * glint) let e = Finish.band(d, 0.0, px, px) + Finish.band(d, w - px, w, px) col = mix(col, an_bronze_dk() * 0.75, clamp(e, 0.0, 1.0) * 0.5) let a = Finish.band(d, 0.0, w, px) return vec4(col * a, a) } // A fine gold line `lw` wide at `d` = -`at` (inside an edge): the inner line // of a frame within a frame. let an_fine = fn(d: float, at: float, lw: float, px: float, amt: float) -> vec4 { let a = Finish.band(d, at, at + lw, px) * amt let col = mix(an_line_c(), an_gold_hi(), 0.35) return vec4(col * a, a) } // The soft shadow a raised part throws on the parchment, straight down and // a little to the right, in warm sepia. Premultiplied. let an_drop = fn(p: vec2, c: vec2, h: vec2, r: float, amt: float) -> vec4 { let t = Finish.drop(p, c, h, r, vec2(0.3, 0.9), 0.6) * 0.30 * amt let s = Finish.drop(p, c, h, r, vec2(0.5, 1.7), 1.5) * 0.14 * amt let a = t + s * (1.0 - t) return vec4(an_shade() * a, a) } // The tight contact shadow of a part with no room round it for the soft // one: gone a point and a half under its edge. let an_contact = fn(p: vec2, c: vec2, h: vec2, r: float, amt: float) -> vec4 { let a = Finish.drop(p, c, h, r, vec2(0.0, 0.45), 0.3) * 0.28 * amt return vec4(an_shade() * a, a) } // A FLAT MATTE ENAMEL face inside its cloison (`c`, `h`, `r` the enamel's // own box): a broad soft light across its upper part, a faint unevenness of // the paint, and the shade the wire throws into it under the top edge. // Lifted a shade under the pointer; pressed, it sinks: darker, with the // wire's shade deeper. let an_enamel = fn(p: vec2, c: vec2, h: vec2, r: float, base: vec3, hover: float, press: float) -> vec3 { let t = clamp((p.y - (c.y - h.y)) / max(2.0 * h.y, 1.0), 0.0, 1.0) var col = base * (1.05 - 0.10 * t) col = mix(col, Finish.lift(col, 0.10), (1.0 - smoothstep(0.05, 0.6, t)) * 0.55 * (1.0 - press)) let mott = (Finish.noise(p * 0.7 + vec2(5.0, 1.0)) - 0.5) * 0.022 + (Finish.noise(p * 0.09 + vec2(2.0, 8.0)) - 0.5) * 0.012 col = col * (1.0 + mott) col = Finish.lift(col, 0.15 * hover * (1.0 - press)) col = mix(col, col * 0.82, press) let sh = clamp(Finish.inset(p, c, h, r, vec2(0.0, 0.8 + 1.2 * press), 0.8 + 0.6 * press), 0.0, 1.0) col = mix(col, an_shade(), sh * (0.24 + 0.26 * press)) return col } // Focus: a teal ring of a device pixel and a half, two points outside. let md_focus = fn(d: float, px: float, focus: float) -> vec4 { let f = Finish.ring_out(d, 1.5, 1.5 + 1.5 * px, px) * 0.9 * focus let c = vec3(0.184, 0.435, 0.384) return vec4(c * f, f) } // A SUNK IVORY WELL behind its wire: the fill, the shade falling in under // the top edge, the bronze wire round it and a light lip on the parchment // under its foot. `fill` its colour; `wire` the wire's width. let an_well = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, fill: vec3, shade: float, wire: float, dis: float) -> vec4 { let d = md_rbox(p, c, h, r) let n = an_nrm(p, c, h, r) let hi = h - vec2(wire, wire) var col = fill let sh = clamp(Finish.inset(p, c, hi, max(r - wire, 0.0), vec2(0.0, 1.4), 1.2), 0.0, 1.0) col = mix(col, an_shade(), sh * shade) let a = Finish.cover(d, px) var out = vec4(0.0, 0.0, 0.0, 0.0) let lip = Finish.ring_out(d, 0.0, px, px) * smoothstep(0.0, h.y * 0.8, p.y - c.y) * 0.45 out = Finish.over(out, vec4(vec3(0.98, 0.95, 0.88) * lip, lip)) out = Finish.over(out, vec4(col * a, a)) let wv = an_wire(d, n, wire, px, 1.0 - dis) return Finish.over(out, wv * mix(1.0, 0.55, dis)) } // An indicator jewel set in a cap: used where a stock face carries a lamp. let md_led = fn(p: vec2, lc: vec2, rad: float, px: float, ink: vec3, lit: float, glow: float) -> vec4 { let q = p - lc let d = length(q) - rad let k = clamp(length(q) / max(rad, 0.001), 0.0, 1.0) let on = mix(Finish.lift(ink, 0.25), ink * 0.85, smoothstep(0.2, 1.0, k)) let off = vec3(0.12, 0.13, 0.11) var col = mix(off, on, lit) col = mix(col, an_bronze_c(), Finish.band(d, 0.0, max(px, 0.6), px)) let a = Finish.cover(d, px) let ha = glow * lit * exp(-max(d, 0.0) / 0.7) * step(0.0, d) * (1.0 - a) var out = vec4(ink * ha, ha) return Finish.over(out, vec4(col * a, a)) } // --------------------------------------------------------------------------- // THE BOTANICAL ORNAMENT. Every part is a curve, drawn at the device pixel. // A leaf or a petal: a lens `len` long from its base `b` along the unit // direction `dir`, `wid` wide either side of its midline at its widest, the // midline bowed by `bend` points. Returns the signed distance, the side // across it (-1..1) and the way along it (0..1). let an_leaf = fn(p: vec2, b: vec2, dir: vec2, len: float, wid: float, bend: float) -> vec4 { let q = p - b let u = dot(q, dir) let t = clamp(u / max(len, 0.01), 0.0, 1.0) let v = dot(q, vec2(0.0 - dir.y, dir.x)) - bend * 4.0 * t * (1.0 - t) let hw = wid * 4.0 * t * (1.0 - t) * (1.15 - 0.3 * t) let slope = wid * 4.0 * (1.0 - 2.0 * t) / max(len, 0.01) let dv = (abs(v) - hw) / sqrt(1.0 + slope * slope) let du = max(0.0 - u, u - len) return vec4(max(dv, du), v / max(hw, 0.3), t, 0.0) } // The distance to a quadratic curve from `a` through the pull of `b` to `c` // (.x), and how far along it the nearest point lies (.y, 0..1). let an_bez = fn(pos: vec2, A: vec2, B: vec2, C: vec2) -> vec2 { let a = B - A let b = A - 2.0 * B + C let c = a * 2.0 let d = A - pos let kk = 1.0 / max(dot(b, b), 0.0001) let kx = kk * dot(a, b) let ky = kk * (2.0 * dot(a, a) + dot(d, b)) / 3.0 let kz = kk * dot(d, a) let p = ky - kx * kx let p3 = p * p * p let q = kx * (2.0 * kx * kx - 3.0 * ky) + kz var h = q * q + 4.0 * p3 var res = 0.0 var tt = 0.0 if h >= 0.0 { h = sqrt(h) let x0 = (h - q) * 0.5 let x1 = (0.0 - h - q) * 0.5 let u0 = sign(x0) * pow(abs(x0), 1.0 / 3.0) let u1 = sign(x1) * pow(abs(x1), 1.0 / 3.0) tt = clamp(u0 + u1 - kx, 0.0, 1.0) let e = d + (c + b * tt) * tt res = dot(e, e) } else { let z = sqrt(0.0 - p) let v = acos(clamp(q / (p * z * 2.0), -1.0, 1.0)) / 3.0 let m = cos(v) let n = sin(v) * 1.732050808 let t0 = clamp((m + m) * z - kx, 0.0, 1.0) let t1 = clamp((0.0 - n - m) * z - kx, 0.0, 1.0) let e0 = d + (c + b * t0) * t0 let e1 = d + (c + b * t1) * t1 let r0 = dot(e0, e0) let r1 = dot(e1, e1) res = min(r0, r1) tt = mix(t1, t0, step(r0, r1)) } return vec2(sqrt(res), tt) } // An open spiral, a tendril's curl: `pitch` points between its turns, // `turns` of them out from its eye `c`, its outer end at the angle `rot`. let an_spiral = fn(p: vec2, c: vec2, pitch: float, turns: float, rot: float) -> float { let q = p - c let r = length(q) var th = atan2(q.y, q.x) - rot th = (th - floor(th / (2.0 * PI)) * 2.0 * PI) / (2.0 * PI) let k = clamp(floor(r / pitch - th + 0.5), 0.0, turns - 1.0) return abs(r - pitch * (th + k)) } // Lays a filled shape of distance `d` over `dst`: its fill, and a // bronze-gold outline `lw` points wide inside its edge. let an_lay = fn(dst: vec4, d: float, fill: vec3, lw: float, px: float) -> vec4 { let a = Finish.cover(d, px) let l = Finish.band(d, 0.0, lw, px) let col = mix(fill, an_line_c(), clamp(l / max(a, 0.001), 0.0, 1.0) * 0.9) return Finish.over(dst, vec4(col * a, a)) } // A leaf laid with its two halves in two greens (the half toward the light // paler), a gold midrib, a gold outline. let an_lay_leaf = fn(dst: vec4, l: vec4, dark: vec3, light: vec3, lw: float, px: float) -> vec4 { var fill = mix(dark, light, smoothstep(-0.15, 0.15, l.y)) fill = mix(fill, fill * 0.85, smoothstep(0.6, 1.0, l.z) * 0.4) let rib = (1.0 - smoothstep(0.05, 0.16, abs(l.y))) * step(l.z, 0.88) * 0.6 fill = mix(fill, an_line_c(), rib) return an_lay(dst, l.x, fill, lw, px) } // A petal: from a dusty rose throat to a cream tip. let an_lay_petal = fn(dst: vec4, l: vec4, root: vec3, tip: vec3, lw: float, px: float) -> vec4 { var fill = mix(root, tip, smoothstep(0.1, 0.75, l.z)) fill = mix(fill, fill * 0.88, smoothstep(0.2, 1.0, abs(l.y)) * 0.35) return an_lay(dst, l.x, fill, lw, px) } // A stem: a tapering stroke along a curve, green with gold edges. let an_lay_stem = fn(dst: vec4, b: vec2, w0: float, w1: float, fill: vec3, px: float) -> vec4 { let d = b.x - mix(w0, w1, b.y) return an_lay(dst, d, fill, min(0.6, mix(w0, w1, b.y) * 0.5), px) } let an_dir = fn(a: float) -> vec2 { return vec2(cos(a), sin(a)) } // A LILY seen from the front: three long petals behind, three shorter ones // before them, curling back at their tips, a throat of `root` and three // gold stamens. `s` its size; `rose` 0 for cream and ochre petals, 1 for // dusty rose ones. let an_lily = fn(dst: vec4, p: vec2, c: vec2, s: float, rot: float, px: float, rose: float) -> vec4 { var out = dst let root = mix(vec3(0.788, 0.553, 0.478), vec3(0.62, 0.40, 0.44), rose) let tip = mix(vec3(0.965, 0.886, 0.690), vec3(0.88, 0.70, 0.68), rose) let fr = mix(vec3(0.925, 0.733, 0.420), vec3(0.80, 0.55, 0.54), rose) let lw = max(0.45, s * 0.045) out = an_lay_petal(out, an_leaf(p, c, an_dir(rot), s, s * 0.26, s * 0.10), root, tip, lw, px) out = an_lay_petal(out, an_leaf(p, c, an_dir(rot + 2.094), s, s * 0.26, s * 0.10), root, tip, lw, px) out = an_lay_petal(out, an_leaf(p, c, an_dir(rot + 4.189), s, s * 0.26, s * 0.10), root, tip, lw, px) out = an_lay_petal(out, an_leaf(p, c, an_dir(rot + 1.047), s * 0.82, s * 0.24, 0.0 - s * 0.08), root, fr, lw, px) out = an_lay_petal(out, an_leaf(p, c, an_dir(rot + 3.142), s * 0.82, s * 0.24, 0.0 - s * 0.08), root, fr, lw, px) out = an_lay_petal(out, an_leaf(p, c, an_dir(rot + 5.236), s * 0.82, s * 0.24, 0.0 - s * 0.08), root, fr, lw, px) // the stamens var st = 1000.0 st = min(st, md_seg(p, c, c + an_dir(rot + 0.5) * s * 0.58)) st = min(st, md_seg(p, c, c + an_dir(rot + 2.6) * s * 0.58)) st = min(st, md_seg(p, c, c + an_dir(rot + 4.7) * s * 0.58)) let sa = Finish.cover(st - max(0.3, s * 0.02), px) out = Finish.over(out, vec4(an_line_c() * sa, sa)) var dots = 1000.0 dots = min(dots, length(p - (c + an_dir(rot + 0.5) * s * 0.58))) dots = min(dots, length(p - (c + an_dir(rot + 2.6) * s * 0.58))) dots = min(dots, length(p - (c + an_dir(rot + 4.7) * s * 0.58))) out = an_lay(out, dots - max(0.6, s * 0.06), vec3(0.50, 0.27, 0.15), 0.0, px) out = an_lay(out, length(p - c) - s * 0.11, vec3(0.70, 0.50, 0.22), lw, px) return out } // A BUD: a closed petal in its two green sepals, on the end of a stem. let an_bud = fn(dst: vec4, p: vec2, b: vec2, dir: vec2, s: float, px: float) -> vec4 { var out = dst let lw = max(0.45, s * 0.06) out = an_lay_petal(out, an_leaf(p, b, dir, s, s * 0.30, 0.0), vec3(0.80, 0.55, 0.45), vec3(0.95, 0.80, 0.55), lw, px) let side = vec2(0.0 - dir.y, dir.x) out = an_lay_leaf(out, an_leaf(p, b, normalize(dir + side * 0.35), s * 0.55, s * 0.12, 0.0), vec3(0.24, 0.38, 0.32), vec3(0.42, 0.58, 0.48), lw, px) out = an_lay_leaf(out, an_leaf(p, b, normalize(dir - side * 0.35), s * 0.55, s * 0.12, 0.0), vec3(0.24, 0.38, 0.32), vec3(0.42, 0.58, 0.48), lw, px) return out } // A SPRIG for a surface's corner, in the corner's own frame: `p` in points // from the corner along its two edges, both into the surface. It is drawn // the same about the corner's diagonal, so it sits in any of the four: a // whiplash stem bowed round the corner like a bracket, curling at both of // its ends, a leaf on each arm, a larger pair at the corner and an ochre bud // between them. `s` scales it (1 is about 38 points along each edge). let an_sprig = fn(dst: vec4, p: vec2, s: float, px: float) -> vec4 { var out = dst let q = p / s let pxs = px / s let dk = vec3(0.220, 0.357, 0.306) let lt = vec3(0.427, 0.580, 0.494) let stem = vec3(0.290, 0.420, 0.357) // fold onto one side of the diagonal for the mirrored arms let m = vec2(min(q.x, q.y), max(q.x, q.y)) out = an_lay(out, an_spiral(m, vec2(4.6, 33.0), 1.5, 2.0, 0.0 - 1.4) - 0.42, stem, 0.25, pxs) out = an_lay_stem(out, an_bez(m, vec2(3.0, 4.0), vec2(1.5, 18.0), vec2(5.1, 30.0)), 1.0, 0.45, stem, pxs) out = an_lay_leaf(out, an_leaf(m, vec2(2.6, 15.0), normalize(vec2(1.0, 0.55)), 11.0, 2.4, 1.2), dk, lt, 0.38, pxs) out = an_lay_leaf(out, an_leaf(q, vec2(3.0, 3.0), normalize(vec2(1.0, 0.45)), 16.0, 3.2, 2.2), dk, lt, 0.4, pxs) out = an_lay_leaf(out, an_leaf(q, vec2(3.0, 3.0), normalize(vec2(0.45, 1.0)), 16.0, 3.2, -2.2), dk, lt, 0.4, pxs) out = an_bud(out, q, vec2(3.5, 3.5), normalize(vec2(1.0, 1.0)), 11.0, pxs) return out } // The sprig set into a box's corner `k` (0 top left, 1 top right, 2 bottom // right, 3 bottom left), `ins` points in from its edges. let an_corner = fn(dst: vec4, p: vec2, lo: vec2, hi: vec2, k: float, ins: float, s: float, px: float) -> vec4 { let right = step(0.5, k) * step(k, 2.5) let bottom = step(1.5, k) let x = mix(p.x - lo.x, hi.x - p.x, right) - ins let y = mix(p.y - lo.y, hi.y - p.y, bottom) - ins if x > -2.0 && y > -2.0 && x < 42.0 * s && y < 42.0 * s { return an_sprig(dst, vec2(x, y), s, px) } return dst } // --------------------------------------------------------------------------- // BUTTONS. One face for the family: an enamel pill in its bronze wire, a // fine gold line inside the wire on the filled ones, a soft sepia shadow // under it. `kind`: 0 teal enamel, 1 ochre (primary), 2 a peacock teal // (secondary), 3 pale sage (tertiary), 4 terracotta (danger), 5 the outline // (the parchment shows, a bronze wire and a teal line inside it), 6 bare // until touched (flatter), 7 an ivory pill (the plain button). Pressed, the // enamel sinks; the quad is grown by `m` for the shadow and the focus ring. let an_kind_c = fn(kind: float) -> vec3 { var c = an_teal() c = mix(c, an_ochre(), step(0.5, kind) * step(kind, 1.5)) c = mix(c, vec3(0.224, 0.400, 0.420), step(1.5, kind) * step(kind, 2.5)) c = mix(c, an_sage(), step(2.5, kind) * step(kind, 3.5)) c = mix(c, an_terra(), step(3.5, kind) * step(kind, 4.5)) c = mix(c, vec3(0.906, 0.851, 0.737), step(4.5, kind)) return c } let an_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, loading: float, anim: float, r4: vec4, kind: float, m: float) -> vec4 { let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - dis let press = down * live let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let r = md_r4(p, c, h, r4) let d = md_rbox(p, c, h, r) let n = an_nrm(p, c, h, r) let wire = 1.6 let outline = step(4.5, kind) * step(kind, 5.5) let bare = step(5.5, kind) * step(kind, 6.5) let show = mix(1.0, clamp(hover * live * 0.8 + press, 0.0, 1.0), bare) var out = an_drop(p, c, h, r, (1.0 - 0.65 * press) * mix(1.0, 0.45, dis) * (1.0 - outline) * show) var base = an_kind_c(kind) base = mix(base, mix(base, vec3(0.70, 0.69, 0.60), 0.62) * 1.08, dis) let hf = h - vec2(wire, wire) var face = an_enamel(p, c, hf, max(r - wire, 0.0), base, hover * live, press) let fa = Finish.cover(d + wire * 0.5, px) * (1.0 - outline * (1.0 - max(press * 0.5, hover * live * 0.35))) out = Finish.over(out, vec4(face * fa, fa) * show) // the fine line inside the wire, on the filled enamels let filled = 1.0 - step(4.5, kind) out = Finish.over(out, an_fine(d, wire + 1.2, max(px, 0.55), px, 0.55 * filled * mix(1.0, 0.4, dis))) // the outline's teal line inside the wire let tl = Finish.band(d, wire + 0.8, wire + 0.8 + max(px, 0.6), px) * outline * mix(0.85, 0.4, dis) out = Finish.over(out, vec4(an_teal_dk() * tl, tl)) let wv = an_wire(d, n, wire, px, mix(1.0, 0.3, dis) * (1.0 + 0.4 * hover * live)) out = Finish.over(out, wv * mix(1.0, 0.55, dis) * show) out = Finish.over(out, md_focus(d, px, focus * live)) if loading > 0.0 { let sdf = Sdf2d.viewport(p) let rr = max(min(h.x, h.y) - 5.0, 2.0) let a0 = anim * 2.0 * PI sdf.arc_round_caps(c.x, c.y, rr, a0, a0 + 1.5 * PI, 1.2) sdf.fill(vec4(0.165, 0.129, 0.086, loading)) out = Finish.over(out, sdf.result) } return out } // Every button template: the plain root (and the sizes, which share its draw // object) wears the ivory pill; the roles are specialised below. mod.widgets.ButtonFlat.clip_x = false mod.widgets.ButtonFlat.clip_y = false mod.widgets.ButtonFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 8.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 7.0, 8.0), 8.0) } mod.widgets.Button.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 0.0, 8.0), 8.0) } mod.widgets.ButtonPrimary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 1.0, 8.0), 8.0) } mod.widgets.ButtonSecondary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 2.0, 8.0), 8.0) } mod.widgets.ButtonTertiary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 3.0, 8.0), 8.0) } mod.widgets.ButtonDanger.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 4.0, 8.0), 8.0) } mod.widgets.ButtonOutline.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 5.0, 8.0), 8.0) } mod.widgets.ButtonFlatter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 6.0, 8.0), 8.0) } // The large button is the teal one, as in the pictures. mod.widgets.ButtonLg.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, md_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 99.0), 0.0, 8.0), 8.0) } // Templates that draw a face of their own on top of these roots keep the // plain quad: the grown one is only for the faces written here. mod.widgets.CloseButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.CopyButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.BurgerButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.ButtonFloating.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.ButtonFloatingSm.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.glass.CutButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } // Geometry: one 26 point row, 14 points either side of the word. mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 4.0 bottom: 4.0} mod.widgets.Button.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0} mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 14.0 height: Fit} mod.widgets.ButtonFlat.min_height = 26.0 mod.widgets.Button.min_height = 26.0 mod.widgets.ButtonXs.min_height = 0.0 mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 0.0 bottom: 0.0} mod.widgets.ButtonXs.draw_text.text_style.font_size = 7.5 mod.widgets.ButtonSm.min_height = 0.0 // Words on the buttons: Cormorant semibold; ivory on the dark enamels, the // sepia ink on the pale ones. mod.widgets.ButtonFlat.draw_text.text_style.font_family = mod.theme.font_label.font_family mod.widgets.ButtonFlat.draw_text.text_style.font_size = 10.5 mod.widgets.ButtonFlat.draw_text.color = #2a2116 mod.widgets.ButtonFlat.draw_text.color_hover = #1f180f mod.widgets.ButtonFlat.draw_text.color_down = #1f180f mod.widgets.ButtonFlat.draw_text.color_focus = #2a2116 mod.widgets.ButtonFlat.draw_text.color_disabled = #8c7a5c mod.widgets.Button.draw_text.color = #f3e8cf mod.widgets.Button.draw_text.color_hover = #fbf3e0 mod.widgets.Button.draw_text.color_down = #e6dac0 mod.widgets.Button.draw_text.color_focus = #f3e8cf mod.widgets.Button.draw_text.color_disabled = #e8e2d0 // The icon on a teal button is ivory, whatever colour its caller asked for // (the catalogue asks for the working ink, which a teal face would swallow). mod.widgets.Button.draw_icon.get_color = fn() { let base = self.eval_gradient() let k = vec3(0.953, 0.910, 0.812) return vec4(k * base.a, base.a) * self.opacity } mod.widgets.ButtonLg.draw_text.color = #f3e8cf mod.widgets.ButtonLg.draw_text.color_hover = #fbf3e0 mod.widgets.ButtonLg.draw_text.color_down = #e6dac0 mod.widgets.ButtonLg.draw_text.color_focus = #f3e8cf mod.widgets.ButtonLg.draw_text.text_style.font_size = 12.0 mod.widgets.ButtonPrimary.draw_text.color = #2a2116 mod.widgets.ButtonPrimary.draw_text.color_hover = #1f180f mod.widgets.ButtonPrimary.draw_text.color_down = #1f180f mod.widgets.ButtonPrimary.draw_text.color_focus = #2a2116 mod.widgets.ButtonPrimary.draw_text.color_disabled = #8c7a5c mod.widgets.ButtonPrimary.draw_icon.color = #2a2116 mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 10.5} mod.widgets.ButtonSecondary.draw_text.color = #f3e8cf mod.widgets.ButtonSecondary.draw_text.color_hover = #fbf3e0 mod.widgets.ButtonSecondary.draw_text.color_down = #e6dac0 mod.widgets.ButtonSecondary.draw_text.color_focus = #f3e8cf mod.widgets.ButtonSecondary.draw_icon.color = #f3e8cf mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_label{font_size: 10.5} mod.widgets.ButtonTertiary.draw_text.color = #24342f mod.widgets.ButtonTertiary.draw_text.color_hover = #1a2622 mod.widgets.ButtonTertiary.draw_text.color_down = #1a2622 mod.widgets.ButtonTertiary.draw_text.color_focus = #24342f mod.widgets.ButtonTertiary.draw_icon.color = #24342f mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_label{font_size: 10.5} mod.widgets.ButtonDanger.draw_text.color = #f3e8cf mod.widgets.ButtonDanger.draw_text.color_hover = #fbf3e0 mod.widgets.ButtonDanger.draw_text.color_down = #e6dac0 mod.widgets.ButtonDanger.draw_text.color_focus = #f3e8cf mod.widgets.ButtonDanger.draw_icon.color = #f3e8cf mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_bold{font_size: 10.5} mod.widgets.ButtonOutline.draw_text.color = #24342f mod.widgets.ButtonOutline.draw_text.color_hover = #1a2622 mod.widgets.ButtonOutline.draw_text.color_down = #1a2622 mod.widgets.ButtonOutline.draw_text.color_focus = #24342f mod.widgets.ButtonOutline.draw_text.color_disabled = #8c7a5c mod.widgets.ButtonOutline.draw_icon.color = #24342f mod.widgets.ButtonFlat.draw_bg.loading_color = #2a2116 // The window's own buttons stand on the teal band: ivory strokes. mod.widgets.DesktopButton.draw_bg.color = uniform(#f3e8cf) mod.widgets.DesktopButton.draw_bg.color_hover = uniform(#fffaf0) mod.widgets.DesktopButton.draw_bg.color_down = uniform(#e8dcc0) mod.widgets.DesktopButton.draw_bg.bg_color_hover = uniform(#f3e8cf26) mod.widgets.DesktopButton.draw_bg.bg_color_down = uniform(#f3e8cf40) // --------------------------------------------------------------------------- // CHECK BOXES. A 14 point box with rounded corners in its bronze wire: clear, // a sunk ivory well; set, teal enamel with an ivory check; mixed, teal with // an ivory dash. mod.widgets.CheckBoxFlat.clip_x = false mod.widgets.CheckBoxFlat.clip_y = false mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.CheckBoxCustom.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0} mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 22.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.CheckBoxFlat.draw_bg.size = uniform(14.0) mod.widgets.CheckBoxFlat.min_height = 24.0 mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 4.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let sz = self.size let live = 1.0 - self.disabled let x0 = mix(1.0, size.x - sz - 1.0, self.mark_at_end) let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - sz) * 0.5, px)) - o let h = vec2(sz * 0.5, sz * 0.5) let c = lo + h let r = mix(3.5, h.y, step(20.0, self.border_radius)) let d = md_rbox(p, c, h, r) let on = max(self.active, self.mixed) let press = self.down * live var fill = Finish.lift(an_well_c(), 0.6 * self.hover * live) fill = mix(fill, mix(an_well_c(), an_parch_c(), 0.6), self.disabled) var out = an_well(p, c, h, r, px, fill, 0.30 - 0.12 * self.hover * live, 1.3, self.disabled) // the teal enamel when set let hf = h - vec2(1.3, 1.3) var tcol = an_enamel(p, c, hf, max(r - 1.3, 0.0), mix(an_teal(), vec3(0.62, 0.63, 0.55), self.disabled), self.hover * live, press) let ta = Finish.cover(md_rbox(p, c, hf, max(r - 1.3, 0.0)), px) * on out = Finish.over(out, vec4(tcol * ta, ta)) let k = sz / 14.0 let m0 = lo + vec2(3.6, 7.3) * k let m1 = lo + vec2(6.0, 9.8) * k let m2 = lo + vec2(10.6, 4.3) * k let ck = min(md_seg(p, m0, m1), md_seg(p, m1, m2)) - 0.85 * k let d0 = lo + vec2(4.0, 7.0) * k let d1 = lo + vec2(10.0, 7.0) * k let dash = md_seg(p, d0, d1) - 0.85 * k let show_ck = self.active * (1.0 - self.mixed) let mk = mix(Finish.cover(ck, px) * show_ck, Finish.cover(dash, px), self.mixed) let ink = mix(an_ivory(), vec3(0.85, 0.82, 0.74), self.disabled) out = Finish.over(out, vec4(ink * mk, mk)) let err = Finish.band(d, 0.0, 2.0 * px, px) * self.error out = Finish.over(out, vec4(vec3(0.60, 0.20, 0.15) * err, err)) return Finish.over(out, md_focus(d, px, self.focus * live)) } // --------------------------------------------------------------------------- // TOGGLES. A 38 by 16 point pill sunk in its wire: parchment-sage while off, // teal enamel while on; riding in it a 12 point ivory disc in a bronze ring, // a teal jewel at its middle, with its own small shadow. let an_toggle = fn(pos: vec2, size: vec2, o: vec2, px: float, active: float, drag_pos: float, drag: float, hover: float, down: float, focus: float, dis: float, err: float, at_end: float, metal: float) -> vec4 { let m = 4.0 let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - dis let tw = 38.0 let th = 16.0 let x0 = mix(1.0, size.x - tw - 1.0, at_end) let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - th) * 0.5, px)) - o let c = lo + vec2(tw * 0.5, th * 0.5) let h = vec2(tw * 0.5, th * 0.5) let r = h.y let d = md_rbox(p, c, h, r) let on = active var fill = mix(vec3(0.788, 0.769, 0.639), an_teal(), on) fill = Finish.lift(fill, 0.10 * hover * live) fill = mix(fill, mix(fill, vec3(0.80, 0.78, 0.70), 0.6), dis) var out = an_well(p, c, h, r, px, fill, mix(0.38, 0.45, on), 1.3, dis) let ea = Finish.band(d, 0.0, 2.0 * px, px) * err out = Finish.over(out, vec4(vec3(0.60, 0.20, 0.15) * ea, ea)) // the disc let kt = clamp(mix(on, drag_pos, drag), 0.0, 1.0) let kr = 6.0 let kx = Finish.snap(o.x + lo.x + 2.0 + kr + kt * (tw - 4.0 - 2.0 * kr), px) - o.x let kc = vec2(kx, c.y) let press = down * live out = Finish.over(out, an_drop(p, kc, vec2(kr, kr), kr, (0.9 - 0.5 * press) * mix(1.0, 0.4, dis))) let q = p - kc let kd = length(q) - kr let kn = q / max(length(q), 0.001) // the ivory (or, on the rocker, bronze) face, domed toward the light let lit = clamp(0.5 - 0.5 * dot(q / kr, vec2(0.45, 0.85)), 0.0, 1.0) var face = mix(mix(vec3(0.80, 0.74, 0.60), an_ivory(), lit), mix(an_bronze_dk(), an_gold_hi(), lit * 0.9), metal) face = Finish.lift(face, 0.12 * hover * live * (1.0 - press)) face = mix(face, face * 0.9, press) face = mix(face, mix(face, vec3(0.80, 0.78, 0.72), 0.6), dis) let ka = Finish.cover(kd, px) out = Finish.over(out, vec4(face * ka, ka)) out = Finish.over(out, an_wire(kd, kn, 1.3, px, live) * mix(1.0, 0.5, dis)) // the jewel: teal when on, a dark bead when off let jd = length(q) - 2.0 var jc = mix(vec3(0.20, 0.22, 0.18), vec3(0.30, 0.62, 0.53), on * live) jc = mix(jc, an_ivory(), (1.0 - smoothstep(0.0, 1.2, length(q - vec2(-0.6, -0.7)))) * 0.6) out = an_lay(out, jd, jc, 0.4, px) return Finish.over(out, md_focus(d, px, focus * live)) } mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleFlat.draw_bg.size = 14.0 mod.widgets.ToggleFlat.min_height = 24.0 mod.widgets.ToggleFlat.draw_bg.pill_aspect = 2.7143 mod.widgets.ToggleFlat.draw_bg.knob_inset = 7.0 mod.widgets.ToggleFlat.clip_x = false mod.widgets.ToggleFlat.clip_y = false mod.widgets.ToggleFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ToggleFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return an_toggle(self.pos, self.rect_size, self.rect_pos, px, self.active, self.drag_pos, self.drag, self.hover, self.down, self.focus, self.disabled, self.error, self.mark_at_end, 0.0) } // --------------------------------------------------------------------------- // RADIOS. A round ivory well in its wire; chosen, a teal enamel disc fills // it with an ivory pip at its middle. let md_radio = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, down: float, focus: float, active: float, dis: float) -> vec4 { let live = 1.0 - dis let q = p - c let d = length(q) - rad var fill = Finish.lift(an_well_c(), 0.6 * hover * live) fill = mix(fill, mix(an_well_c(), an_parch_c(), 0.6), dis) var out = an_well(p, c, vec2(rad, rad), rad, px, fill, 0.30 - 0.12 * hover * live, 1.3, dis) let ir = rad - 2.6 let tcol = an_enamel(p, c, vec2(ir, ir), ir, mix(an_teal(), vec3(0.62, 0.63, 0.55), dis), hover * live, down * live) let ta = Finish.cover(length(q) - ir, px) * active out = Finish.over(out, vec4(tcol * ta, ta)) let pa = Finish.cover(length(q) - rad * 0.26, px) * active out = Finish.over(out, vec4(mix(an_ivory(), vec3(0.85, 0.82, 0.74), dis) * pa, pa)) return Finish.over(out, md_focus(d, px, focus * live)) } mod.widgets.RadioButtonFlat.clip_x = false mod.widgets.RadioButtonFlat.clip_y = false mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.glass.RadioButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0} mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 22.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.RadioButtonFlat.draw_bg.size = uniform(14.0) mod.widgets.RadioButtonFlat.min_height = 24.0 mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 4.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let sz = self.size let lo = vec2(Finish.snap(o.x + 1.0, px), Finish.snap(o.y + (self.rect_size.y - sz) * 0.5, px)) - o let c = lo + vec2(sz * 0.5, sz * 0.5) return md_radio(p, c, sz * 0.5, px, self.hover, self.down, self.focus, self.active, self.disabled) } // The flatter radio is its word alone: chosen, the word sits on a small // ochre enamel pill; under the pointer a faint wash. mod.widgets.RadioButtonFlatter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 4.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let lo = vec2(Finish.snap(o.x - 3.0, px), Finish.snap(o.y + 1.0, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x + 3.0, px), Finish.snap(o.y + self.rect_size.y - 1.0, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let d = md_rbox(p, c, h, h.y) let wash = Finish.cover(d, px) * 0.15 * self.hover * live * (1.0 - self.active) var out = vec4(an_ochre() * wash, wash) let face = an_enamel(p, c, h - vec2(1.2, 1.2), h.y, an_ochre(), 0.0, 0.0) let a = Finish.cover(d, px) * self.active out = Finish.over(out, vec4(face * a, a)) out = Finish.over(out, an_wire(d, an_nrm(p, c, h, h.y), 1.2, px, 1.0) * self.active) return Finish.over(out, md_focus(d, px, self.focus * live)) } // Radio tabs (the catalogue's Docs, Controls, Actions, Theme): ivory pills // in their wire; the chosen one teal enamel, pressed in, its word ivory. mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 6.0 bottom: 2.0} mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.RadioButtonTabFlat.min_height = 26.0 mod.widgets.RadioButtonTabFlat.clip_x = false mod.widgets.RadioButtonTabFlat.clip_y = false mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let ivory = an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0, 0.0, vec4(99.0, 99.0, 99.0, 99.0), 7.0, 6.0) let teal = an_button(self.pos, self.rect_size, self.rect_pos, px, self.hover * 0.5, 0.55, self.focus, self.disabled, 0.0, 0.0, vec4(99.0, 99.0, 99.0, 99.0), 0.0, 6.0) return mix(ivory, teal, self.active) } mod.widgets.RadioButtonTabFlat.draw_text.color = #2a2116 mod.widgets.RadioButtonTabFlat.draw_text.color_active = #f3e8cf mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #8c7a5c mod.widgets.RadioButtonTabFlat.draw_text.text_style = mod.theme.font_label{font_size: 10.5} // --------------------------------------------------------------------------- // SLIDERS. A fine bronze channel: a 4 point groove behind its wire, dark // parchment where it is empty and teal enamel where the value fills it. The // thumb is a bronze coin with a teal leaf on its face, riding with its own // shadow. Every slider's quad grows six points for the coin, its shadow and // the focus ring. let an_coin = fn(p: vec2, c: vec2, rad: float, px: float, hover: float, press: float, dis: float) -> vec4 { let live = 1.0 - dis var out = an_drop(p, c, vec2(rad, rad), rad, (1.0 - 0.5 * press) * mix(1.0, 0.4, dis)) let q = p - c let d = length(q) - rad let n = q / max(length(q), 0.001) let rr = clamp(length(q) / rad, 0.0, 1.0) // the coin's face: a polished dome, light from the upper left let lit = clamp(0.5 - 0.6 * dot(q / rad, vec2(0.45, 0.85)), 0.0, 1.0) var face = mix(an_bronze_dk(), an_bronze_c(), 0.45 + 0.55 * lit) face = mix(face, an_gold_hi(), smoothstep(0.55, 1.0, lit) * 0.55) face = Finish.lift(face, 0.14 * hover * live * (1.0 - press)) face = mix(face, face * 0.88, press) face = mix(face, mix(face, vec3(0.75, 0.72, 0.64), 0.55), dis) let a = Finish.cover(d, px) out = Finish.over(out, vec4(face * a, a)) // an engraved ring a little inside the rim let ring = Finish.band(d, rad * 0.24, rad * 0.24 + max(px, 0.5), px) out = Finish.over(out, vec4(an_bronze_dk() * ring * 0.8, ring * 0.8)) let ringl = Finish.band(d, rad * 0.24 + max(px, 0.5), rad * 0.24 + 2.0 * max(px, 0.5), px) * 0.6 out = Finish.over(out, vec4(an_gold_hi() * ringl, ringl)) // the leaf on its face let lf = an_leaf(p, c + vec2(-0.42, 0.42) * rad * 0.62, normalize(vec2(1.0, -1.0)), rad * 1.24, rad * 0.30, rad * 0.10) out = an_lay_leaf(out, lf, mix(vec3(0.16, 0.30, 0.25), vec3(0.5, 0.5, 0.45), dis), mix(vec3(0.33, 0.52, 0.44), vec3(0.6, 0.6, 0.55), dis), 0.35, px) out = Finish.over(out, an_wire(d, n, max(1.0, rad * 0.16), px, live)) return out } let an_groove = fn(p: vec2, c: vec2, h: vec2, px: float, f0: float, f1: float, dis: float, hover: float) -> vec4 { let r = h.y let d = md_rbox(p, c, h, r) var empty = mix(vec3(0.765, 0.682, 0.541), vec3(0.80, 0.74, 0.62), dis) empty = Finish.lift(empty, 0.08 * hover) var out = an_well(p, c, h, r, px, empty, 0.4, 1.0, dis) let fh = vec2(max((f1 - f0) * 0.5, 0.0), h.y - 1.0) let fc = vec2((f0 + f1) * 0.5, c.y) let fd = md_rbox(p, fc, fh, fh.y) let fcol = an_enamel(p, fc, fh, fh.y, mix(an_teal_lt(), vec3(0.66, 0.67, 0.60), dis), hover, 0.0) let fa = Finish.cover(fd, px) * step(0.01, f1 - f0) * Finish.cover(d + 1.0, px) return Finish.over(out, vec4(fcol * fa, fa)) } mod.widgets.SliderFlat.height = 32.0 mod.widgets.SliderFlat.clip_x = false mod.widgets.SliderFlat.clip_y = false mod.widgets.SliderFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let cy = Finish.snap(o.y + size.y - 8.0, px) - o.y let x0 = Finish.snap(o.x + 1.0, px) - o.x let x1 = Finish.snap(o.x + size.x - 1.0, px) - o.x let w = x1 - x0 let span = w - 18.0 let kx = x0 + 9.0 + self.slide_pos * span let ox = x0 + 9.0 + self.origin_pos * span let f0 = mix(x0 + 2.0, min(ox, kx), self.arc_origin) let f1 = mix(kx, max(ox, kx), self.arc_origin) var out = an_groove(p, vec2((x0 + x1) * 0.5, cy), vec2(w * 0.5, 2.5), px, f0, f1, self.disabled, max(self.hover, self.drag) * live) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy) out = Finish.over(out, an_coin(p, kc, 7.5, px, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled)) return Finish.over(out, md_focus(length(p - kc) - 7.5, px, self.focus * live)) } mod.widgets.SliderFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 9.5} // The range slider: the same channel, teal between the two ends, a round // coin on each end, pressed while it is the one held. mod.widgets.RangeSliderFlat.handle_size = 16.0 mod.widgets.RangeSliderFlat.track_inset = 1.0 mod.widgets.RangeSliderFlat.clip_x = false mod.widgets.RangeSliderFlat.clip_y = false mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let top = self.label_px let cy = Finish.snap(o.y + top + (size.y - top) * 0.5, px) - o.y let x0 = Finish.snap(o.x + 1.0, px) - o.x let x1 = Finish.snap(o.x + size.x - 1.0, px) - o.x let w = x1 - x0 let span = max(size.x - self.inset_px * 2.0 - self.handle_px, 1.0) let sx = self.inset_px + self.handle_px * 0.5 + self.start_pos * span let ex = self.inset_px + self.handle_px * 0.5 + self.end_pos * span var out = an_groove(p, vec2((x0 + x1) * 0.5, cy), vec2(w * 0.5, 2.5), px, sx, ex, self.disabled, max(self.hover, self.drag) * live) let k0 = vec2(Finish.snap(o.x + sx, px) - o.x, cy) let k1 = vec2(Finish.snap(o.x + ex, px) - o.x, cy) out = Finish.over(out, an_coin(p, k0, 7.0, px, max(self.hot_start, self.focus * 0.5) * live, self.drag * self.hot_start * live, self.disabled)) out = Finish.over(out, an_coin(p, k1, 7.0, px, max(self.hot_end, self.focus * 0.5) * live, self.drag * self.hot_end * live, self.disabled)) return out } // The minimal slider: a bronze hairline (a dark device pixel over a gold // one), a teal thread along it to the thumb, and a bronze drop riding on // it, its point upward, a teal jewel in its round. mod.widgets.SliderMinimal.clip_x = false mod.widgets.SliderMinimal.clip_y = false mod.widgets.SliderMinimal.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderMinimal.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let cy = Finish.snap(o.y + size.y - 6.0, px) - o.y let kr = 5.0 let kx = kr + self.slide_pos * (size.x - 2.0 * kr) let inx = step(0.0, p.x) * step(p.x, size.x) let dark = step(cy - px, p.y) * step(p.y, cy) * inx let light = step(cy, p.y) * step(p.y, cy + px) * inx let thread = step(cy - 2.0 * px, p.y) * step(p.y, cy + px) * step(p.x, kx) * inx var out = vec4(0.0, 0.0, 0.0, 0.0) let la = light * 0.8 out = Finish.over(out, vec4(an_gold_hi() * la, la)) let da = dark * 0.85 * mix(1.0, 0.5, self.disabled) out = Finish.over(out, vec4(an_bronze_dk() * da, da)) let tcol = mix(an_teal(), vec3(0.66, 0.67, 0.60), self.disabled) out = Finish.over(out, vec4(tcol * thread, thread)) // the drop: a round body on the line, a point rising from it let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy) let q = p - kc let body = length(q) - kr let ty = q.y + kr * 1.9 let cone = max((abs(q.x) - ty * 0.618) * 0.851, 0.0 - ty) let dd = min(body, max(cone, q.y)) let press = self.drag * live out = Finish.over(out, an_drop(p, kc, vec2(kr, kr), kr, (1.0 - 0.5 * press) * mix(1.0, 0.4, self.disabled))) let lit = clamp(0.5 - 0.6 * dot(q / (kr * 1.6), vec2(0.45, 0.85)), 0.0, 1.0) var face = mix(an_bronze_dk(), an_bronze_c(), 0.45 + 0.55 * lit) face = mix(face, an_gold_hi(), smoothstep(0.55, 1.0, lit) * 0.55) face = Finish.lift(face, 0.14 * self.hover * live * (1.0 - press)) face = mix(face, mix(face, vec3(0.75, 0.72, 0.64), 0.55), self.disabled) let a = Finish.cover(dd, px) out = Finish.over(out, vec4(face * a, a)) let e = Finish.band(dd, 0.0, max(px, 0.55), px) out = Finish.over(out, vec4(an_bronze_dk() * e * 0.85, e * 0.85)) let jd = length(q - vec2(0.0, 0.3)) - kr * 0.45 out = an_lay(out, jd, mix(vec3(0.26, 0.52, 0.45), vec3(0.6, 0.6, 0.55), self.disabled), 0.35, px) return Finish.over(out, md_focus(body, px, self.focus * live)) } mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 9.5} // The round slider: a teal-lined channel beside its label, a teal capsule in // it and a bronze coin on the capsule's end; the value reads past it. mod.widgets.SliderRoundFlat.height = 18.0 mod.widgets.SliderRoundFlat.clip_x = false mod.widgets.SliderRoundFlat.clip_y = false mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderRoundFlat.text_input.margin = mod.turtle.Inset{left: 0.0 right: 8.0 top: 0.0 bottom: 0.0} mod.widgets.SliderRoundFlat.text_input.width = 30.0 mod.widgets.SliderRoundFlat.text_input.min_height = 0.0 mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 9.5} mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let x0 = Finish.snap(o.x + self.label_size, px) - o.x let y0 = Finish.snap(o.y + 1.0, px) - o.y let x1 = Finish.snap(o.x + size.x - 44.0, px) - o.x let y1 = Finish.snap(o.y + size.y - 1.0, px) - o.y let h = vec2((x1 - x0) * 0.5, (y1 - y0) * 0.5) let c = vec2(x0, y0) + h let ins = 2.0 let kr = h.y - ins + 0.5 let kx = x0 + ins + kr + self.slide_pos * (2.0 * h.x - 2.0 * ins - 2.0 * kr) var out = an_groove(p, c, h, px, x0 + ins, kx, self.disabled, self.hover * live) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, c.y) out = Finish.over(out, an_coin(p, kc, kr, px, self.hover, self.drag * live, self.disabled)) return Finish.over(out, md_focus(md_rbox(p, c, h, h.y), px, self.focus * live)) } // --------------------------------------------------------------------------- // FADERS. A deep teal slot four points wide in its bronze wire, bronze ticks // either side, a gold thread in the slot for the value, and a cap: a teal // enamel plaque 22 points across in a bronze wire, a gold line across its // middle and a small bronze lozenge on it. mod.widgets.SliderFader.cap_size = 13.0 mod.widgets.SliderFader.clip_x = false mod.widgets.SliderFader.clip_y = false mod.widgets.SliderFader.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderFader.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let vert = self.is_vertical let a = mix(p.x, size.y - p.y, vert) let b = mix(p.y, p.x, vert) let along = mix(size.x, size.y, vert) let across = mix(size.y, size.x, vert) let travel = max(along - self.inset_px * 2.0 - self.cap_px, 1.0) let foot = self.inset_px + self.cap_px * 0.5 let mid = floor(across * 0.5 / px) * px // the slot, in screen space let sw = 5.0 let sc = mix(vec2(along * 0.5, mid), vec2(mid, size.y - along * 0.5), vert) let sh = mix(vec2(along * 0.5 - self.inset_px, sw * 0.5), vec2(sw * 0.5, along * 0.5 - self.inset_px), vert) var out = an_well(p, sc, sh, sw * 0.5, px, mix(an_teal_dk() * 0.8, vec3(0.6, 0.6, 0.55), self.disabled), 0.5, 1.0, self.disabled) // the value: a gold thread in the slot, from the origin (or the foot) to // the cap let va0 = foot + self.fill_lo * travel let va1 = foot + self.fill_hi * travel let vl = step(va0, a) * step(a, va1) * (1.0 - smoothstep(0.55, 0.55 + px, abs(b - mid))) * live out = Finish.over(out, vec4(an_gold_hi() * 0.9 * vl, vl * 0.9)) // ticks: a long one every quarter of the travel, short ones between let nt = 16.0 let f = (a - foot) / travel * nt let fi = floor(f + 0.5) let da = abs(a - (foot + fi / nt * travel)) let major = 1.0 - step(0.1, abs(fi * 0.25 - floor(fi * 0.25 + 0.5))) let tl = mix(2.5, 4.5, major) let off = abs(b - mid) - sw * 0.5 - 2.5 let in_rng = step(-0.5, fi) * step(fi, nt + 0.5) let tk = (1.0 - smoothstep(px * 0.6, px * 0.6 + 0.01 + px * 0.4, da)) * step(0.0, off) * step(off, tl) * in_rng * 0.8 * mix(1.0, 0.5, self.disabled) out = Finish.over(out, vec4(an_line_c() * 0.8 * tk, tk)) // the cap let at = foot + self.slide_pos * travel let cw = min(22.0, across) let cs = mix(vec2(at, mid), vec2(mid, size.y - at), vert) let ch = mix(vec2(self.cap_px * 0.5, cw * 0.5), vec2(cw * 0.5, self.cap_px * 0.5), vert) let cc = vec2(Finish.snap(o.x + cs.x, px) - o.x, Finish.snap(o.y + cs.y, px) - o.y) let cr = 4.0 let cd = md_rbox(p, cc, ch, cr) let press = self.drag * live out = Finish.over(out, an_drop(p, cc, ch, cr, (1.0 - press * 0.5) * mix(1.0, 0.4, self.disabled))) let hf = ch - vec2(1.4, 1.4) let face = an_enamel(p, cc, hf, cr - 1.4, mix(an_teal(), vec3(0.62, 0.63, 0.55), self.disabled), max(self.hover, self.focus * 0.5) * live, press) let fa = Finish.cover(cd, px) out = Finish.over(out, vec4(face * fa, fa)) // the gold line across the cap let ln = mix(abs(p.y - cc.y), abs(p.x - cc.x), 1.0 - vert) let lin = (1.0 - smoothstep(0.4, 0.4 + px, ln)) * Finish.cover(cd + 2.0, px) out = Finish.over(out, vec4(an_gold_hi() * 0.9 * lin, lin * 0.9)) // the lozenge let lq = abs(p - cc) let lz = (lq.x + lq.y) - 3.2 out = an_lay(out, lz, mix(an_bronze_c(), an_gold_hi(), 0.3), 0.5, px) out = Finish.over(out, an_wire(cd, an_nrm(p, cc, ch, cr), 1.4, px, live) * mix(1.0, 0.5, self.disabled)) return Finish.over(out, md_focus(cd, px, self.focus * live)) } // --------------------------------------------------------------------------- // KNOBS. A polished bronze disc, lit from the upper left, two rings engraved // in it (a dark cut with a bright edge under it); in its middle a medallion // of teal enamel with a spray of three leaves rising from its foot; the // pointer a gold spear from a bronze boss out to the rim, turning with the // value. Round it a fine bronze track and the value in gold. let an_engrave = fn(d: float, at: float, px: float, amt: float) -> vec4 { let w = max(px, 0.5) let dk = Finish.band(d, at, at + w, px) * 0.75 * amt let lt = Finish.band(d, at + w, at + 2.0 * w, px) * 0.6 * amt var out = vec4(an_bronze_dk() * 0.7 * dk, dk) return Finish.over(out, vec4(an_gold_hi() * lt, lt)) } let an_knob = fn(p: vec2, c: vec2, rad: float, px: float, ang: float, hover: float, press: float, dis: float) -> vec4 { let live = 1.0 - dis var out = an_drop(p, c, vec2(rad, rad), rad, (1.0 - 0.4 * press) * mix(1.0, 0.4, dis)) let q = p - c let d = length(q) - rad let n = q / max(length(q), 0.001) // the bronze disc: a dome, its light pooled on the upper left, a broad // soft sheen and a darker foot let lit = clamp(0.5 - 0.62 * dot(q / rad, vec2(0.45, 0.85)), 0.0, 1.0) var face = mix(an_bronze_dk(), an_bronze_c(), 0.30 + 0.70 * lit) face = mix(face, an_gold_hi(), smoothstep(0.62, 1.0, lit) * 0.55) face = Finish.lift(face, 0.12 * hover * live * (1.0 - press)) face = mix(face, face * 0.9, press) face = mix(face, mix(face, vec3(0.75, 0.72, 0.64), 0.55), dis) let a = Finish.cover(d, px) out = Finish.over(out, vec4(face * a, a)) out = Finish.over(out, an_engrave(d, rad * 0.10, px, live * 0.9 + 0.1)) // the medallion let mr = rad * 0.70 let md = length(q) - mr let mcol = an_enamel(p, c, vec2(mr, mr), mr, mix(an_teal(), vec3(0.62, 0.63, 0.55), dis), hover * live, press * 0.4) * (0.9 + 0.2 * lit) let ma = Finish.cover(md, px) out = Finish.over(out, vec4(mcol * ma, ma)) // three leaves rising from the medallion's foot, fixed (the medallion // is the knob's face, the pointer turns over it) let ldk = mix(vec3(0.235, 0.369, 0.322), vec3(0.56, 0.57, 0.51), dis) let llt = mix(vec3(0.459, 0.604, 0.525), vec3(0.67, 0.67, 0.61), dis) let lw = max(0.3, rad * 0.02) let lb = c + vec2(0.0, mr * 0.80) if md < 1.0 { out = an_lay_leaf(out, an_leaf(p, lb, normalize(vec2(-0.62, -1.0)), mr * 1.25, mr * 0.20, mr * 0.14), ldk, llt, lw, px) out = an_lay_leaf(out, an_leaf(p, lb, normalize(vec2(0.62, -1.0)), mr * 1.25, mr * 0.20, mr * -0.14), ldk, llt, lw, px) out = an_lay_leaf(out, an_leaf(p, lb, vec2(0.0, -1.0), mr * 1.45, mr * 0.17, 0.0), ldk, llt, lw, px) } // the medallion's wire out = Finish.over(out, an_wire(md, n, max(0.9, rad * 0.07), px, live) * mix(1.0, 0.55, dis)) // the spear: a long lozenge from the boss to the rim let dir = vec2(0.0 - sin(ang), cos(ang)) let sl = an_leaf(p, c - dir * rad * 0.10, dir, rad * 1.0, max(0.9, rad * 0.085), 0.0) let sfill = mix(mix(an_bronze_c(), an_gold_hi(), smoothstep(-0.3, 0.3, sl.y) * 0.85), vec3(0.8, 0.78, 0.7), dis) out = an_lay(out, sl.x, sfill, max(0.35, rad * 0.025), px) // the boss let br = max(1.6, rad * 0.15) let bd = length(q) - br let bl = clamp(0.5 - 0.6 * dot(q / br, vec2(0.45, 0.85)), 0.0, 1.0) out = an_lay(out, bd, mix(an_bronze_dk(), an_gold_hi(), bl), max(0.35, rad * 0.02), px) // the rim out = Finish.over(out, an_wire(d, n, max(1.2, rad * 0.08), px, live) * mix(1.0, 0.55, dis)) return out } // The value arc round a knob: `ar` its middle radius, `aw` its width; the // gap centred at the bottom, the value clockwise from its left end, or from // the origin when the knob is bipolar. The track a fine bronze line, the // value gold with a dark edge. let md_arc = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, v0: float, v1: float, gap: float, dis: float) -> vec4 { let start = gap * 0.5 let sweep = 2.0 * PI - gap let q = p - c var ang = atan2(0.0 - q.x, q.y) if ang < 0.0 { ang = ang + 2.0 * PI } let rd = abs(length(q) - ar) - aw * 0.5 let rt = abs(length(q) - ar) - max(px, 0.45) let in_arc = step(start, ang) * step(ang, start + sweep) let e0 = c + vec2(0.0 - sin(start), cos(start)) * ar let e1 = c + vec2(0.0 - sin(start + sweep), cos(start + sweep)) * ar let track_d = min(mix(1000.0, rt, in_arc), min(length(p - e0), length(p - e1)) - aw * 0.6) let a0 = start + sweep * v0 let a1 = start + sweep * v1 let l0 = c + vec2(0.0 - sin(a0), cos(a0)) * ar let l1 = c + vec2(0.0 - sin(a1), cos(a1)) * ar let lit_in = step(a0, ang) * step(ang, a1) let lit_d = min(mix(1000.0, rd, lit_in), min(length(p - l0), length(p - l1)) - aw * 0.5) let ta = Finish.cover(track_d, px) * 0.7 var out = vec4(an_line_c() * ta, ta) let la = Finish.cover(lit_d, px) let ink = mix(mix(an_bronze_c(), an_gold_hi(), 0.55), vec3(0.75, 0.72, 0.64), dis) out = Finish.over(out, vec4(ink * la, la)) let le = Finish.band(lit_d, 0.0, max(px, 0.4), px) * 0.6 return Finish.over(out, vec4(an_bronze_dk() * le, le)) } mod.widgets.RotaryKnob.clip_x = false mod.widgets.RotaryKnob.clip_y = false mod.widgets.RotaryKnob.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RotaryKnob.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let dia = min(size.x, size.y) let rr = dia * 0.5 - 1.0 let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + size.y - dia * 0.5, px) - o.y) let gap = self.gap * PI / 180.0 let aw = 1.8 let ar = floor((rr - aw * 0.5) / px) * px let rad = floor((ar - aw * 0.5 - 2.4) / px) * px let lo = min(self.origin_pos, self.slide_pos) let hi = max(self.origin_pos, self.slide_pos) let v0 = mix(0.0, lo, self.arc_origin) let v1 = mix(self.slide_pos, hi, self.arc_origin) var out = md_arc(p, c, ar, aw, px, v0, v1, gap, self.disabled) let ang = gap * 0.5 + (2.0 * PI - gap) * self.slide_pos out = Finish.over(out, an_knob(p, c, rad, px, ang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled)) return Finish.over(out, md_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live)) } // Knob captions are small engraved words in the sepia ink. mod.widgets.RotaryKnob.draw_text.color = #3a2e1f mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_bold{font_size: 9.0} // The dial: the knob above set in a deep teal bezel ring between two bronze // wires, gold pips on the bezel every thirtieth of a turn, the value in gold // round the bezel. mod.widgets.RotaryFlat.clip_x = false mod.widgets.RotaryFlat.clip_y = false mod.widgets.RotaryFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RotaryFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let room = vec2(size.x, size.y - 24.0) let dia = min(room.x, room.y) let rr = dia * 0.5 - 1.0 let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + 24.0 + room.y * 0.5, px) - o.y) let gap = self.gap * PI / 180.0 let lo = min(self.origin_pos, self.slide_pos) let hi = max(self.origin_pos, self.slide_pos) let v0 = mix(0.0, lo, self.arc_origin) let v1 = mix(self.slide_pos, hi, self.arc_origin) // the bezel: a teal ring between two wires let q = p - c let n = q / max(length(q), 0.001) let bw = max(5.0, rr * 0.20) let d_out = length(q) - rr let d_in = length(q) - (rr - bw) var out = an_drop(p, c, vec2(rr, rr), rr, mix(1.0, 0.4, self.disabled)) let ba = Finish.cover(d_out, px) let bcol = an_enamel(p, c, vec2(rr, rr), rr, mix(an_teal_dk(), vec3(0.6, 0.6, 0.55), self.disabled), 0.0, 0.0) out = Finish.over(out, vec4(bcol * ba, ba)) // the value round the bezel let ar = rr - bw * 0.5 out = Finish.over(out, md_arc(p, c, ar, bw * 0.38, px, v0, v1, gap, self.disabled)) // the pips var ang = atan2(0.0 - q.x, q.y) if ang < 0.0 { ang = ang + 2.0 * PI } let st = 2.0 * PI / 24.0 let ai = floor(ang / st + 0.5) * st let pc = c + vec2(0.0 - sin(ai), cos(ai)) * ar let on_track = step(gap * 0.5 - 0.01, ai) * step(ai, 2.0 * PI - gap * 0.5 + 0.01) let pd = length(p - pc) - max(0.7, bw * 0.12) let pa = Finish.cover(pd, px) * on_track * 0.85 out = Finish.over(out, vec4(an_gold_hi() * pa, pa)) out = Finish.over(out, an_wire(d_out, n, 1.6, px, live) * mix(1.0, 0.55, self.disabled)) out = Finish.over(out, an_wire(0.0 - d_in - 1.4, n * -1.0, 1.4, px, live) * mix(1.0, 0.55, self.disabled) * Finish.cover(d_out, px)) let rad = rr - bw - 1.5 let kang = gap * 0.5 + (2.0 * PI - gap) * self.slide_pos out = Finish.over(out, an_knob(p, c, rad, px, kang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled)) return Finish.over(out, md_focus(d_out, px, self.focus * live)) } // The dial's readout: a small pill of the screens' deep teal glass in its // bronze wire, amber figures. let md_lcd = fn(p: vec2, size: vec2, o: vec2, px: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.0, px) let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(0.0, 2.0 * px) let h = (hi - lo) * 0.5 let c = lo + h let r = h.y let d = md_rbox(p, c, h, r) let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0) var col = an_glass_c() * (1.12 - 0.2 * t) col = mix(col, an_shade(), clamp(Finish.inset(p, c, h, r, vec2(0.0, 1.2), 1.0), 0.0, 1.0) * 0.5) let a = Finish.cover(d, px) var out = an_drop(p, c, h, r, 0.6) out = Finish.over(out, vec4(col * a, a)) return Finish.over(out, an_wire(d, an_nrm(p, c, h, r), 1.3, px, 1.0)) } mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 2.0 bottom: 2.0} mod.widgets.RotaryFlat.text_input.min_height = 20.0 mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return md_lcd(self.pos * self.rect_size, self.rect_size, self.rect_pos, px) } mod.widgets.RotaryFlat.text_input.draw_text.color = #ecc77c mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #f4d692 mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #f4d692 mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #a89060 mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 10.0} // --------------------------------------------------------------------------- // TEXT FIELDS. A sunk ivory well in its bronze wire, its corners rounded // half way to a pill; focused, a teal line inside the wire and the ivory // takes a breath of sage. Read only, the well is a deeper parchment. let md_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, focus: float, dis: float, r: float) -> vec4 { let ins = 1.5 let lo = vec2(Finish.snap(o.x + ins, px), Finish.snap(o.y + ins, px)) - o let hi = vec2(Finish.snap(o.x + size.x - ins, px), Finish.snap(o.y + size.y - ins, px)) - o - vec2(0.0, px) let h = (hi - lo) * 0.5 let c = lo + h let rr = min(r, h.y) let d = md_rbox(p, c, h, rr) var fill = Finish.lift(an_well_c(), 0.30 * hover * (1.0 - dis)) fill = mix(fill, vec3(0.855, 0.867, 0.773), focus * 0.6) fill = mix(fill, vec3(0.827, 0.749, 0.600), dis) var out = an_well(p, c, h, rr, px, fill, mix(0.32, 0.18, dis), 1.3, dis) let il = Finish.band(d, 2.0, 2.0 + max(px, 0.5), px) * mix(0.55, 0.25, dis) * (1.0 - focus) out = Finish.over(out, vec4(an_teal_dk() * il, il)) let fl = Finish.band(d, 1.3, 1.3 + 1.2 * max(px, 0.75), px) * focus * (1.0 - dis) return Finish.over(out, vec4(vec3(0.184, 0.435, 0.384) * fl, fl)) } mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 4.0 bottom: 4.0} 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 = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return md_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 14.0) } mod.widgets.TextInputFlat.draw_text.color_empty = #6a5940 mod.widgets.TextInputFlat.draw_text.color_disabled = #7a6a50 mod.widgets.TextInputFlat.draw_cursor.color = #2a2116 // NUMBER FIELDS. The ivory well, and on its right a column split by a fine // bronze line, a small chevron in each half; the half under the pointer // lifts, the held half darkens. mod.widgets.NumberField.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return md_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 14.0) } mod.widgets.NumberField.pad_left = 10.0 mod.widgets.NumberSpin.width = 16.0 mod.widgets.NumberSpin.draw_text.color = #00000000 mod.widgets.NumberSpin.draw_text.color_disabled = #00000000 let an_chev = fn(p: vec2, c: vec2, hw: float, hh: float, dir: float) -> float { // a chevron hw either side, hh tall, pointing up (dir -1) or down (1) let a = c + vec2(0.0 - hw, 0.0 - hh * dir * 0.5) let b = c + vec2(0.0, hh * dir * 0.5) let e = c + vec2(hw, 0.0 - hh * dir * 0.5) return min(md_seg(p, a, b), md_seg(p, b, e)) } mod.widgets.NumberSpin.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let live = 1.0 - self.disabled let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 3.0, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x - 3.0, px), Finish.snap(o.y + self.rect_size.y - 3.0, px)) - o let mid = Finish.snap(o.y + (lo.y + hi.y) * 0.5, px) - o.y let upper = step(p.y, mid) let hot = mix(max(0.0, 0.0 - self.hot), max(0.0, self.hot), upper) * live let dn = mix(max(0.0, 0.0 - self.down), max(0.0, self.down), upper) * live let inx = step(lo.x, p.x) * step(p.x, hi.x) * step(lo.y, p.y) * step(p.y, hi.y) let wash = inx * (0.10 * hot + 0.12 * dn) var out = vec4(mix(vec3(1.0, 0.97, 0.9), an_shade(), step(0.01, dn)) * wash, wash) // the fine bronze line down its left and across its middle let vl = step(lo.x, p.x) * step(p.x, lo.x + max(px, 0.6)) * step(lo.y, p.y) * step(p.y, hi.y) * 0.8 let hl = step(abs(p.y - mid), max(px, 0.6) * 0.5) * step(lo.x, p.x) * step(p.x, hi.x) * 0.6 let la = max(vl, hl) out = Finish.over(out, vec4(an_line_c() * la, la)) let cx = (lo.x + hi.x) * 0.5 + 0.5 let cu = an_chev(p, vec2(cx, (lo.y + mid) * 0.5), 2.8, 2.6, -1.0) - 0.55 let cd = an_chev(p, vec2(cx, (mid + hi.y) * 0.5), 2.8, 2.6, 1.0) - 0.55 let ti = Finish.cover(min(cu, cd), px) let ink = mix(an_ink(), vec3(0.6, 0.53, 0.42), self.disabled) return Finish.over(out, vec4(ink * ti, ti)) } // The drag-number: the ivory well, its track a pale teal wash behind the // ink. mod.widgets.ValueInput.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size var out = md_field(p, self.rect_size, self.rect_pos, px, max(self.hover, self.drag), self.focus, 0.0, 14.0) let d = md_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(3.0, 3.0), 6.0) let wx = 3.0 + (self.rect_size.x - 6.0) * max(self.fill, 0.0) let wash = step(0.0, self.fill) * step(p.x, wx) * Finish.cover(d, px) * 0.22 return Finish.over(out, vec4(an_teal_lt() * wash, wash)) } mod.widgets.ValueInput.draw_text.color = #2a2116 mod.widgets.ValueInput.draw_text.text_style = mod.theme.font_label{font_size: 10.0 line_spacing: 1.0} mod.widgets.ValueInput.arrow_color = #5a4a34 mod.widgets.ValueInput.text_input.draw_bg.color = #00000000 // --------------------------------------------------------------------------- // DROP-DOWNS. The ivory pill with a chevron on the right, set off by a fine // bronze line. Open, the pill is held pressed in. let md_dropdown = fn(pos0: vec2, size0: vec2, o0: vec2, px: float, hover: float, held: float, focus: float, dis: float, ins: float) -> vec4 { let m = 6.0 let p = pos0 * (size0 + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) - vec2(0.0, ins) let size = size0 - vec2(0.0, 2.0 * ins) let o = o0 + vec2(0.0, ins) let pos = (p + vec2(m, m)) / (size + vec2(2.0 * m, 2.0 * m)) var out = an_button(pos, size, o, px, hover, held, focus, dis, 0.0, 0.0, vec4(99.0, 99.0, 99.0, 99.0), 7.0, m) let live = 1.0 - dis let dx = Finish.snap(o.x + size.x - 24.0, px) - o.x let in_y = step(size.y * 0.22, p.y) * step(p.y, size.y * 0.78) let dl = step(dx - max(px, 0.6) * 0.5, p.x) * step(p.x, dx + max(px, 0.6) * 0.5) * in_y * mix(0.8, 0.4, dis) out = Finish.over(out, vec4(an_line_c() * dl, dl)) let cc = vec2(Finish.snap(o.x + size.x - 12.5, px) - o.x, size.y * 0.5 + held * live * px) let ch = an_chev(p, cc, 3.4, 3.2, 1.0) - 0.65 let tri = Finish.cover(ch, px) let ink = mix(an_ink(), vec3(0.6, 0.53, 0.42), dis) return Finish.over(out, vec4(ink * tri, tri)) } mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 12.0 right: 32.0 top: 4.0 bottom: 4.0} mod.widgets.DropDown.padding = mod.turtle.Inset{left: 12.0 right: 32.0 top: 4.0 bottom: 4.0} mod.widgets.DropDownFlat.clip_x = false mod.widgets.DropDownFlat.clip_y = false mod.widgets.DropDownFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.DropDownFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return md_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, max(self.down, self.active), self.focus, self.disabled, 1.0) } mod.widgets.DropDownFlat.draw_text.text_style = mod.theme.font_label{font_size: 10.5} mod.widgets.Select.clip_x = false mod.widgets.Select.clip_y = false mod.widgets.MultiSelect.clip_x = false mod.widgets.MultiSelect.clip_y = false mod.widgets.Select.face.clip_x = false mod.widgets.Select.face.clip_y = false mod.widgets.MultiSelect.face.clip_x = false mod.widgets.MultiSelect.face.clip_y = false mod.widgets.Select.face.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.MultiSelect.face.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Select.face.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return md_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0) } mod.widgets.MultiSelect.face.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return md_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0) } mod.widgets.Select.face.padding.right = 32.0 mod.widgets.MultiSelect.face.padding.right = 32.0 // The combo box: the ivory well one types into, its chevron behind a fine // bronze line at the right end. mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 10.0 right: 32.0 top: 4.0 bottom: 4.0} mod.widgets.ComboBox.input.min_height = 0.0 mod.widgets.ComboBox.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let size = self.rect_size let o = self.rect_pos let live = 1.0 - self.disabled var out = md_field(p, size, o, px, self.hover, self.focus, self.disabled, 14.0) let dx = Finish.snap(o.x + size.x - 24.0, px) - o.x let in_y = step(size.y * 0.25, p.y) * step(p.y, size.y * 0.75) let dl = step(dx - max(px, 0.6) * 0.5, p.x) * step(p.x, dx + max(px, 0.6) * 0.5) * in_y * mix(0.8, 0.4, self.disabled) out = Finish.over(out, vec4(an_line_c() * dl, dl)) let held = self.down * self.hover * live let cc = vec2(Finish.snap(o.x + size.x - 12.5, px) - o.x, size.y * 0.5 + held * px) let tri = Finish.cover(an_chev(p, cc, 3.4, 3.2, 1.0) - 0.65, px) let ink = mix(an_ink(), vec3(0.6, 0.53, 0.42), self.disabled) return Finish.over(out, vec4(ink * tri, tri)) } // --------------------------------------------------------------------------- // SEGMENTS AND TABS. One ivory pill across the strip in its wire; the chosen // segment is a teal enamel pill pressed into it, its word ivory. let md_strip = fn(p: vec2, size: vec2, o: vec2, px: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = md_rbox(p, c, h, h.y) var out = an_drop(p, c, h, h.y, 0.7) let hf = h - vec2(1.5, 1.5) let face = an_enamel(p, c, hf, hf.y, vec3(0.906, 0.851, 0.737), 0.0, 0.0) let fa = Finish.cover(d + 0.75, px) out = Finish.over(out, vec4(face * fa, fa)) return Finish.over(out, an_wire(d, an_nrm(p, c, h, h.y), 1.5, px, 1.0)) } // The chosen cell of a strip: an enamel pill of `base` inside the cell, // pressed in, in a wire of its own. let md_cell = fn(p: vec2, lo: vec2, hi: vec2, px: float, base: vec3) -> vec4 { let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = md_rbox(p, c, h, h.y) let hf = h - vec2(1.2, 1.2) let face = an_enamel(p, c, hf, hf.y, base, 0.0, 0.6) let a = Finish.cover(d + 0.6, px) var out = vec4(face * a, a) return Finish.over(out, an_wire(d, an_nrm(p, c, h, h.y), 1.2, px, 1.0)) } mod.widgets.SegmentedControlFlat.segment_height = 22.0 mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return md_strip(p, self.rect_size, self.rect_pos, px) } // The pill is the segment's own box: its quad grows a point each way so the // chosen pill stands just inside the strip's wire. mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(1.0, 1.0), self.rect_size + vec2(2.0, 2.0)) } mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let o = self.rect_pos let size = self.rect_size let p = self.pos * (size + vec2(2.0, 2.0)) - vec2(1.0, 1.0) let lo = vec2(Finish.snap(o.x - 0.5, px), Finish.snap(o.y - 0.5, px)) - o let hi = vec2(Finish.snap(o.x + size.x + 0.5, px), Finish.snap(o.y + size.y + 0.5, px)) - o return md_cell(p, lo, hi, px, an_teal()) } mod.widgets.SegmentedControlFlat.draw_text.color = #3a2e1f mod.widgets.SegmentedControlFlat.draw_text_selected.color = #f6eedb mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #f6eedb mod.widgets.SegmentedControlFlat.draw_text.text_style = mod.theme.font_label{font_size: 10.5} mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_label{font_size: 10.5} // The tab strip: the same pill; the tab in use an ochre enamel pill pressed // into it, a hovered tab lifts a little, the quiet ones parted by a fine // bronze line with a gold dot at its middle. mod.widgets.Tabs.height = 26.0 mod.widgets.Tabs.tab_min_width = 46.0 mod.widgets.Tabs.clip_x = false mod.widgets.Tabs.clip_y = false mod.widgets.Tabs.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return md_strip(p, self.rect_size, self.rect_pos, px) } mod.widgets.Tabs.draw_tab.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_tab_hover.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let h = self.rect_size * 0.5 - vec2(2.0, 2.5) let d = md_rbox(p, self.rect_size * 0.5, h, h.y) let a = Finish.cover(d, px) * 0.35 return vec4(vec3(1.0, 0.98, 0.92) * a, a) } mod.widgets.Tabs.draw_tab_active.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let o = self.rect_pos let size = self.rect_size let lo = vec2(Finish.snap(o.x + 1.0, px), Finish.snap(o.y + 1.5, px)) - o let hi = vec2(Finish.snap(o.x + size.x - 1.0, px), Finish.snap(o.y + size.y - 1.5, px)) - o return md_cell(self.pos * size, lo, hi, px, an_ochre()) } mod.widgets.Tabs.draw_sep.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let x = self.rect_size.x * 0.5 let y = self.rect_size.y * 0.5 let ln = step(abs(p.x - x), max(px, 0.6) * 0.5) * step(abs(p.y - y), self.rect_size.y * 0.28) * 0.7 let dot = Finish.cover(length(p - vec2(x, y)) - 1.2, px) let a = max(ln, dot) return vec4(mix(an_line_c(), an_gold_hi(), dot * 0.4) * a, a) } mod.widgets.Tabs.draw_mark.color = #3a2e1f mod.widgets.Tabs.draw_mark.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o // the add mark a plus in the middle of its room; the close mark a small // cross tucked into the top right of its room, clear of the word let a = mix(0.7853981634, 0.0, self.plus) let r = mix(2.0, 3.6, self.plus) let cm = mix(vec2(self.rect_size.x - 3.0, 4.5), c, self.plus) let u = vec2(cos(a), sin(a)) * r let v = vec2(0.0 - sin(a), cos(a)) * r let dd = min(md_seg(p, cm - u, cm + u), md_seg(p, cm - v, cm + v)) - mix(0.5, 0.6, self.plus) let k = Finish.cover(dd, px) * self.color.a return vec4(self.color.rgb * k, k) } mod.widgets.Tabs.draw_text.color = #3a2e1f mod.widgets.Tabs.draw_text_active.color = #1f180f mod.widgets.Tabs.draw_text.text_style = mod.theme.font_label{font_size: 9.0 top_drop: 0.22} mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_label{font_size: 9.0 top_drop: 0.22} // --------------------------------------------------------------------------- // PROGRESS. A channel of deep teal glass in its bronze wire; the value a // sage-teal enamel combed with the fine veins of a long feather-like leaf, // a gold midrib down it. The unknown value is a still run of leaves across // the whole channel: a face may not run a clock. mod.widgets.ProgressBarFlat.height = 12.0 mod.widgets.ProgressBarFlat.draw_bg.thickness = uniform(12.0) mod.widgets.ProgressBarFlat.clip_x = false mod.widgets.ProgressBarFlat.clip_y = false mod.widgets.ProgressBarFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 3.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 3.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let th = min(self.thickness, size.y) let top = Finish.snap(o.y + (size.y - th) * 0.5, px) - o.y let bot = Finish.snap(o.y + (size.y + th) * 0.5, px) - o.y let x0 = Finish.snap(o.x + size.x * self.start, px) - o.x let x1 = Finish.snap(o.x + size.x * self.end, px) - o.x let h = vec2((x1 - x0) * 0.5, (bot - top) * 0.5) let c = vec2(x0, top) + h let r = h.y let d = md_rbox(p, c, h, r) let dis = step(4.5, self.intent) var out = an_well(p, c, h, r, px, mix(vec3(0.150, 0.235, 0.212), vec3(0.62, 0.62, 0.56), dis), 0.55, 1.3, dis) var v1 = x0 + (x1 - x0) * clamp(self.value, 0.0, 1.0) var unknown = 0.0 if self.value < 0.0 { v1 = x1 unknown = 1.0 } let fh = vec2(max((v1 - x0) * 0.5 - 1.3, 0.0), h.y - 1.3) let fc = vec2((x0 + v1) * 0.5, c.y) let fd = md_rbox(p, fc, fh, min(fh.y, fh.x + fh.y)) var base = vec3(0.447, 0.616, 0.537) if self.intent > 0.5 { base = self.fill_color().rgb } base = mix(base, vec3(0.70, 0.69, 0.60), dis) var fcol = an_enamel(p, fc, fh, fh.y, base, 0.0, 0.0) // the veins: fine pale strokes slanting back from the leading edge, // wavering a little so no two run alike let vy = (p.y - c.y) / max(h.y, 1.0) let vx = p.x - abs(vy) * 6.0 + (Finish.noise(vec2(p.x * 0.15, 3.0)) - 0.5) * 2.0 let fv = abs(fract(vx / 3.6) - 0.5) * 3.6 let vln = (1.0 - smoothstep(0.3, 0.3 + px * 1.5, fv)) * 0.30 fcol = mix(fcol, Finish.lift(fcol, 0.45), vln) fcol = mix(fcol, fcol * 0.8, smoothstep(0.35, 1.0, abs(vy)) * 0.5) // the midrib along the value let rib = (1.0 - smoothstep(0.3, 0.3 + px, abs(p.y - c.y))) * 0.35 fcol = mix(fcol, an_gold_hi(), rib) // the unknown value: leaves laid end to end if unknown > 0.5 { let cell = 12.0 let lx = p.x - floor(p.x / cell) * cell let lf = an_leaf(vec2(lx, p.y), vec2(1.0, c.y + 1.5), normalize(vec2(1.0, -0.25)), cell - 2.0, h.y * 0.55, 0.0) let la = Finish.cover(lf.x, px) fcol = mix(vec3(0.150, 0.235, 0.212), mix(vec3(0.30, 0.46, 0.40), vec3(0.50, 0.66, 0.57), smoothstep(-0.2, 0.2, lf.y)), la) fcol = mix(fcol, an_line_c(), Finish.band(lf.x, 0.0, max(px, 0.45), px)) } let fa = Finish.cover(fd, px) * step(0.01, v1 - x0) out = Finish.over(out, vec4(fcol * fa, fa)) return out * self.track * self.opacity } // --------------------------------------------------------------------------- // LEVEL METERS. A dark teal well of cells four points long with a point // between: unlit cells the deep teal glass, lit ones a sage green, ochre from // 70 % and terracotta from 90 %; the hold an ivory line; the over lamp a // small jewel. mod.widgets.LevelMeter.height = 8.0 mod.widgets.LevelMeterColumn.width = 8.0 mod.widgets.LevelMeter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let vert = self.vertical let along = mix(self.rect_size.x, self.rect_size.y, vert) let across = mix(self.rect_size.y, self.rect_size.x, vert) let a = mix(p.x, self.rect_size.y - p.y, vert) let b = mix(p.y, p.x, vert) let q = vec2(a, b) let th = min(7.0, across) let lamp_room = self.lamp * (th + 4.0) let bar = max(4.0, along - lamp_room) let h = vec2(bar * 0.5, th * 0.5) let c = vec2(h.x, across * 0.5) let r = 2.0 let d = md_rbox(q, c, h, r) let pad = 1.5 let span = max(1.0, bar - pad * 2.0) let share = clamp((a - pad) / span, 0.0, 1.0) let pitch = 5.0 let cell = fract((a - pad) / pitch) let in_cell = step(cell, 4.0 / pitch) let lit = step(a, pad + span * self.level) * step(pad, a) * in_cell let ramp = mix(mix(mix(vec3(0.45, 0.66, 0.52), vec3(0.70, 0.76, 0.44), smoothstep(0.0, 0.7, share)), vec3(0.92, 0.72, 0.34), step(0.7, share)), vec3(0.86, 0.40, 0.27), step(0.9, share)) let well = vec3(0.12, 0.19, 0.17) var fill = mix(well, ramp, lit) let cell_row = in_cell * step(pad, a) * step(a, pad + span) fill = mix(fill, mix(well, vec3(0.2, 0.3, 0.27), 0.5), (1.0 - lit) * cell_row * step(1.2, abs(b - c.y) + 1.2)) let bt = b - (c.y - h.y) fill = mix(fill, vec3(1.0, 0.97, 0.88), lit * step(1.0, bt) * step(bt, 1.0 + px) * 0.3) if self.hold > 0.002 { let at = pad + span * self.hold let hm = step(abs(a - at), px) * step(abs(b - c.y), h.y) fill = mix(fill, an_ivory(), hm) } let ca = Finish.cover(d, px) var out = vec4(fill * ca, ca) let e = Finish.band(d, 0.0, max(px, 0.7), px) out = Finish.over(out, vec4(an_line_c() * e, e)) if self.lamp > 0.5 { let la = bar + 2.0 + th * 0.5 + 1.0 let lc = vec2(mix(la, across * 0.5, vert), mix(across * 0.5, self.rect_size.y - la, vert)) out = Finish.over(out, md_led(p, lc, 2.6, px, vec3(0.90, 0.30, 0.20), step(0.5, self.over), 0.3)) } return out * self.opacity } // --------------------------------------------------------------------------- // SCROLL BARS. A bronze hairline down the length of the view and a slim teal // enamel pill riding on it in its own wire. mod.widgets.ScrollBar.bar_size = 10.0 mod.widgets.ScrollBar.bar_side_margin = 0.0 mod.widgets.ScrollBar.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let vert = self.is_vertical let a = mix(p.x, p.y, vert) let b = mix(p.y, p.x, vert) let along = mix(self.rect_size.x, self.rect_size.y, vert) let across = mix(self.rect_size.y, self.rect_size.x, vert) let q = vec2(a, b) let mid = across * 0.5 let rail = (1.0 - smoothstep(0.35, 0.35 + px, abs(b - mid))) * 0.55 var out = vec4(an_line_c() * rail, rail) let t0 = along * self.norm_scroll let tl = along * self.norm_handle let th = vec2(max(tl * 0.5 - 1.0, 4.0), 3.5) let tc = vec2(t0 + tl * 0.5, mid) let td = md_rbox(q, tc, th, th.y) let hf = th - vec2(1.0, 1.0) let face = an_enamel(q, tc, hf, hf.y, Finish.lift(an_teal(), 0.12 * self.hover), 0.0, 0.3 * self.drag) let ta = Finish.cover(td, px) out = Finish.over(out, vec4(face * ta, ta)) let e = Finish.band(td, 0.0, 1.0, px) out = Finish.over(out, vec4(mix(an_bronze_c(), an_gold_hi(), 0.3) * e, e)) return out * self.opacity } // --------------------------------------------------------------------------- // VIEWS AND PANELS. A raised panel is sage enamel in a bronze wire with a // fine gold line inside it, a sprig of leaves and a bud in its top right and // bottom right corners; the sunken one a well of deeper parchment under the // same wire, sprigs at its bottom corners. The words on both keep the sepia // ink, at better than 6:1. let md_plate = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, sunken: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let d = md_rbox(p, c, h, r) let n = an_nrm(p, c, h, r) var out = vec4(0.0, 0.0, 0.0, 0.0) let s = clamp(min(size.x, size.y) / 80.0, 0.55, 1.1) if sunken > 0.5 { var face = an_parch(p, px, vec3(0.545, 0.616, 0.561)) let sh = clamp(Finish.inset(p, c, h - vec2(1.6, 1.6), r - 1.6, vec2(0.0, 2.0), 2.4), 0.0, 1.0) face = mix(face, an_shade(), sh * 0.38) let a = Finish.cover(d, px) let lip = Finish.ring_out(d, 0.0, px, px) * smoothstep(0.0, h.y, p.y - c.y) * 0.5 out = Finish.over(out, vec4(vec3(0.98, 0.95, 0.88) * lip, lip)) out = Finish.over(out, vec4(face * a, a)) out = an_corner(out, p, lo, hi, 1.0, 3.5, s * 0.8, px) out = an_corner(out, p, lo, hi, 2.0, 3.5, s, px) out = Finish.over(out, an_fine(d, 3.4, max(px, 0.6), px, 0.6)) return Finish.over(out, an_wire(d, n, 1.6, px, 1.0)) } out = an_drop(p, c, h, r, 1.0) var face = an_enamel(p, c, h - vec2(1.6, 1.6), r - 1.6, vec3(0.580, 0.663, 0.596), 0.0, 0.0) face = face + g3(Finish.grain(p / px, 0.008)) let a = Finish.cover(d, px) out = Finish.over(out, vec4(face * a, a)) out = an_corner(out, p, lo, hi, 1.0, 3.5, s * 0.8, px) out = an_corner(out, p, lo, hi, 2.0, 3.5, s, px) out = Finish.over(out, an_fine(d, 3.4, max(px, 0.6), px, 0.7)) return Finish.over(out, an_wire(d, n, 1.6, px, 1.0)) } mod.widgets.PanelView.clip_x = false mod.widgets.PanelView.clip_y = false mod.widgets.PanelView.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.PanelView.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return md_plate(p, self.rect_size, self.rect_pos, px, 10.0, self.sunken) } // The card is an ivory leaf of parchment in a bronze wire and a fine gold // line, a sprig in its bottom right corner and a smaller one top right. It // lifts a little under the pointer. mod.widgets.Card.radius = 10.0 mod.widgets.Card.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) return self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Card.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let r = 10.0 let d = md_rbox(p, c, h, r) var out = an_drop(p, c, h, r, 1.0 + 0.4 * self.hover) var face = an_parch(p, px, vec3(0.925, 0.878, 0.776)) face = Finish.lift(face, 0.05 * self.hover - 0.0 * self.down) let a = Finish.cover(d, px) out = Finish.over(out, vec4(face * a, a)) let s = clamp(min(size.x, size.y) / 80.0, 0.55, 1.1) out = an_corner(out, p, lo, hi, 1.0, 3.5, s * 0.8, px) out = an_corner(out, p, lo, hi, 2.0, 3.5, s, px) out = Finish.over(out, an_fine(d, 3.4, max(px, 0.6), px, 0.75)) return Finish.over(out, an_wire(d, an_nrm(p, c, h, r), 1.6, px, 1.0)) } // The plain rounded view keeps the colours its caller gives it; the edge is // a snapped hairline at the true radius. mod.widgets.RoundedView.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size var fill = self.color if self.color_2.x > -0.5 { fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal)) } let lo = vec2(self.border_inset.x, self.border_inset.y) let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w) let h = (hi - lo) * 0.5 let c = lo + h let d = md_rbox(p, c, h, max(self.border_radius * 2.0, 1.0)) var col = fill if self.border_size > 0.0 { col = mix(col, self.border_color, Finish.band(d, 0.0, max(self.border_size, px), px) * self.border_color.a) } let a = Finish.cover(d, px) * col.a return vec4(col.rgb * a, a) } // --------------------------------------------------------------------------- // LISTS. Rows of 22 points on the parchment, parted by a faint bronze // hairline; a hovered row takes a breath of ochre; the chosen row is an // ochre enamel pill in its bronze wire, ending short of the scroll bar. mod.widgets.ListItemOne.height = 22.0 mod.widgets.ListItem.padding = mod.turtle.Inset{left: 12.0 right: 8.0 top: 0.0 bottom: 0.0} mod.widgets.ListItem.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let live = 1.0 - self.disabled let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o let lay = (self.hover * 0.16 + self.down * 0.10) * live * (1.0 - self.selected) let hd = md_rbox(p, (lo + hi) * 0.5 - vec2(3.0, 0.0), (hi - lo) * 0.5 - vec2(5.0, 1.5), 8.0) let la = Finish.cover(hd, px) * lay var out = vec4(an_ochre() * la, la) if self.selected > 0.5 { let h = vec2((hi.x - lo.x) * 0.5 - 6.0, (hi.y - lo.y) * 0.5 - 1.5) let c = vec2(lo.x + h.x + 2.0, (lo.y + hi.y) * 0.5) let r = h.y let d = md_rbox(p, c, h, r) out = Finish.over(out, an_contact(p, c, h, r, 1.0)) let face = an_enamel(p, c, h - vec2(1.3, 1.3), r - 1.3, an_ochre(), 0.0, 0.0) let a = Finish.cover(d, px) out = Finish.over(out, vec4(face * a, a)) out = Finish.over(out, an_wire(d, an_nrm(p, c, h, r), 1.3, px, 1.0)) } let yb = hi.y let dk = step(yb - max(px, 0.6), p.y) * step(p.y, yb) * step(lo.x + 4.0, p.x) * step(p.x, hi.x - 8.0) * 0.30 * (1.0 - self.selected) out = Finish.over(out, vec4(an_line_c() * dk, dk)) return out } mod.widgets.ListItem.draw_text.color = #2a2116 // The open list of a drop-down: an ivory leaf in a bronze wire and a fine // gold line, with a soft shadow; the row under the pointer an ochre wash, // the chosen row a teal check. mod.widgets.PopupMenuFlat.clip_x = false mod.widgets.PopupMenuFlat.clip_y = false mod.widgets.PopupMenuFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 10.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 10.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let r = 8.0 let d = md_rbox(p, c, h, r) let sh = Finish.drop(p, c, h, r, vec2(0.5, 2.5), 2.2) * 0.22 var out = vec4(an_shade() * sh, sh) let face = an_parch(p, px, vec3(0.925, 0.878, 0.776)) let a = Finish.cover(d, px) out = Finish.over(out, vec4(face * a, a)) out = Finish.over(out, an_fine(d, 3.0, max(px, 0.6), px, 0.7)) return Finish.over(out, an_wire(d, an_nrm(p, c, h, r), 1.5, px, 1.0)) } mod.widgets.PopupMenuItem.padding.left = 18.0 mod.widgets.PopupMenuItem.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let c = self.rect_size * 0.5 let h = c - vec2(3.0, 0.5) let d = md_rbox(p, c, h, h.y) let live = 1.0 - self.disabled let wa = Finish.cover(d, px) * self.hover * live * 0.45 var out = vec4(an_ochre() * wa, wa) let m0 = vec2(5.0, c.y + 0.2) let m1 = vec2(7.2, c.y + 2.4) let m2 = vec2(10.6, c.y - 2.6) let mk = Finish.cover(min(md_seg(p, m0, m1), md_seg(p, m1, m2)) - 0.75, px) * self.active return Finish.over(out, vec4(an_teal_dk() * mk, mk)) } // The dock's tabs: the bar is the parchment with a bronze line at its foot; // the tab in use an ivory leaf rounded at the top in its wire, open at the // foot where it joins the panel below; the others lift under the pointer. mod.widgets.Tab.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.Tab.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 2.0, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o + vec2(0.0, 8.0) let h = (hi - lo) * 0.5 let c = lo + h let r = 7.0 let d = md_rbox(p, c, h, r) let face = an_parch(p, px, vec3(0.925, 0.878, 0.776)) let a = Finish.cover(d, px) * self.active var out = vec4(face * a, a) out = Finish.over(out, an_wire(d, an_nrm(p, c, h, r), 1.3, px, 1.0) * self.active) let lift = Finish.cover(d, px) * 0.18 * self.hover * (1.0 - self.active) return Finish.over(vec4(vec3(1.0, 0.97, 0.9) * lift, lift), out) } mod.widgets.TabBar.draw_fill.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let yb = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y let dk = step(yb - 1.3, p.y) * step(p.y, yb) * 0.9 return vec4(an_line_c() * dk, dk) } // --------------------------------------------------------------------------- // CHIPS AND BADGES. A chip is a small ivory pill (teal enamel while chosen); // a badge a small teal enamel pill with ivory figures. mod.widgets.ChipFlat.size = mod.widgets.ChipSize.Small mod.widgets.Chip.size = mod.widgets.ChipSize.Small mod.widgets.ChipFlat.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ChipFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let ch = min(22.0, self.rect_size.y) let dy = (self.rect_size.y - ch) * 0.5 let sz = vec2(self.rect_size.x, ch) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let pp = (p - vec2(0.0, dy) + vec2(m, m)) / (sz + vec2(2.0 * m, 2.0 * m)) let has_fill = step(0.05, self.color.a) * step(0.6, Finish.lum(self.ink.rgb)) let kind = mix(mix(6.0, 5.0, step(0.01, self.border_size)), 0.0, has_fill) let kk = mix(7.0, kind, step(0.5, has_fill + step(0.01, self.border_size))) return an_button(pp, sz, self.rect_pos + vec2(0.0, dy), px, self.hover, self.down, self.keyed, 0.0, 0.0, 0.0, vec4(99.0, 99.0, 99.0, 99.0), kk, m) } mod.widgets.ChipFlat.draw_text.text_style = mod.theme.font_label{font_size: 10.0} mod.widgets.BadgePalette.neutral = #46635a mod.widgets.BadgePalette.on_neutral = #f3e8cf mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_bold{font_size: 9.0} mod.widgets.Badge.draw_text.text_style = mod.theme.font_bold{font_size: 9.0} mod.widgets.BadgeFlat.clip_x = false mod.widgets.BadgeFlat.clip_y = false mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let bh = min(18.0, self.rect_size.y - 2.0) let y0 = Finish.snap(o.y + (self.rect_size.y - bh) * 0.5, px) - o.y let lo = vec2(Finish.snap(o.x + 1.0, px) - o.x, y0) let hi = vec2(Finish.snap(o.x + self.rect_size.x - 1.0, px) - o.x, Finish.snap(o.y + y0 + bh, px) - o.y) let h = (hi - lo) * 0.5 let c = lo + h let r = h.y let d = md_rbox(p, c, h, r) var out = an_contact(p, c, h, r, 1.0) let face = an_enamel(p, c, h - vec2(1.2, 1.2), r - 1.2, self.color.rgb, 0.0, 0.0) let a = Finish.cover(d, px) out = Finish.over(out, vec4(face * a, a)) out = Finish.over(out, an_wire(d, an_nrm(p, c, h, r), 1.2, px, 1.0)) return out * self.color.a } // The spinner keeps its stock turning face (it runs on the pass clock, which // a face may not read), in teal on a faint track. mod.widgets.SpinnerFlat.draw_bg.color = #46635a mod.widgets.SpinnerFlat.draw_bg.stroke_width = 2.5 mod.widgets.SpinnerFlat.draw_bg.track_alpha = 0.25 // Rules between groups: a bronze-gold hairline with a small lozenge at its // middle. mod.widgets.Hr.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y let ln = (1.0 - smoothstep(0.35, 0.35 + px, abs(p.y - y))) * 0.8 let lq = abs(p - vec2(self.rect_size.x * 0.5, y)) let lz = Finish.cover(lq.x * 0.6 + lq.y - 2.0, px) let a = max(ln, lz) return vec4(an_line_c() * a, a) } mod.widgets.Vr.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x let a = (1.0 - smoothstep(0.35, 0.35 + px, abs(p.x - x))) * 0.8 return vec4(an_line_c() * a, a) } // --------------------------------------------------------------------------- // THE TREE. Its rows stand on the parchment, clear; the open row is an // ochre enamel pill in its bronze wire, as the chosen row of a list. mod.widgets.FileTreeNode.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let lo = vec2(2.0, 1.5) let hi = self.rect_size - vec2(4.0, 1.5) let h = (hi - lo) * 0.5 let c = lo + h let r = h.y let d = md_rbox(p, c, h, r) let face = an_enamel(p, c, h - vec2(1.2, 1.2), r - 1.2, an_ochre(), 0.0, 0.0) let a = Finish.cover(d, px) var out = vec4(face * a, a) out = Finish.over(out, an_wire(d, an_nrm(p, c, h, r), 1.2, px, 1.0)) return out * self.active } // --------------------------------------------------------------------------- // STATES. Every held state is written as a plain value that cuts in at once: // the library writes some as snap() values, which a state held as a default // (the states page) never applies. Live, the cut is the same as the snap. mod.widgets.ButtonFlat.animator.hover.on.from = {all: Snap} mod.widgets.ButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.ButtonFlat.animator.hover.down.from = {all: Snap} mod.widgets.ButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.CheckBoxFlat.animator.hover.on.from = {all: Snap} mod.widgets.CheckBoxFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.CheckBoxFlat.animator.hover.down.from = {all: Snap} mod.widgets.CheckBoxFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.ToggleFlat.animator.hover.on.from = {all: Snap} mod.widgets.ToggleFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.ToggleFlat.animator.hover.down.from = {all: Snap} mod.widgets.ToggleFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.RadioButtonFlat.animator.hover.on.from = {all: Snap} mod.widgets.RadioButtonFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.RadioButtonFlat.animator.hover.down.from = {all: Snap} mod.widgets.RadioButtonFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.TextInputFlat.animator.hover.on.from = {all: Snap} mod.widgets.TextInputFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.TextInputFlat.animator.hover.down.from = {all: Snap} mod.widgets.TextInputFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.DropDownFlat.animator.hover.on.from = {all: Snap} mod.widgets.DropDownFlat.animator.hover.on.apply = {draw_bg: {down: 0.0 hover: 1.0} draw_text: {down: 0.0 hover: 1.0}} mod.widgets.DropDownFlat.animator.hover.down.from = {all: Snap} mod.widgets.DropDownFlat.animator.hover.down.apply = {draw_bg: {down: 1.0 hover: 1.0} draw_text: {down: 1.0 hover: 1.0}} mod.widgets.SliderFlat.animator.hover.on.from = {all: Snap} mod.widgets.SliderFlat.animator.hover.on.apply = {draw_bg: {hover: 1.0} draw_text: {hover: 1.0}} mod.widgets.SliderFlat.animator.drag.on.from = {all: Snap} mod.widgets.SliderFlat.animator.drag.on.apply = {draw_bg: {drag: 1.0} draw_text: {drag: 1.0}} // --------------------------------------------------------------------------- // THE WINDOW'S GROUND. Parchment, held in a frame of teal enamel bands edged // with bronze-gold wire: a band along the top where the window's caption // stands, with a parchment cartouche under the title and lily sprays in its // two ends; a header of parchment under it where the catalogue's toolbar // stands; a band under the header; the panes' parchment; and a band round // the window's other three edges. Between the panes the splitters draw the // same band. The heavy detail is all on the bands and in the corners; the // panes are calm, only the faint mottle and the fibre. // A lily spray along the top band, in the band's own frame: x along it from // its anchor, y down from the window's top edge, about 250 points long. A // whiplash stem in two arcs ending in a tendril's curl; a second stem with a // bud on it; a short one carrying a small dusty rose lily; leaves along // them, turned up and down by turns; a large lily at its root. let an_band_spray = fn(dst: vec4, q: vec2, px: float) -> vec4 { var out = dst if q.x > 262.0 { return out } let dk = vec3(0.420, 0.580, 0.486) let lt = vec3(0.616, 0.737, 0.627) let stem = vec3(0.500, 0.650, 0.560) // the curl and the two arcs of the whiplash out = an_lay(out, an_spiral(q, vec2(246.0, 12.0), 2.2, 2.0, PI) - 0.6, stem, 0.35, px) out = an_lay_stem(out, an_bez(q, vec2(2.0, 29.0), vec2(46.0, 0.0), vec2(118.0, 15.0)), 1.7, 1.1, stem, px) out = an_lay_stem(out, an_bez(q, vec2(118.0, 15.0), vec2(190.0, 30.0), vec2(241.6, 12.0)), 1.1, 0.6, stem, px) // the second stem and its bud; the short stem with the rose lily out = an_lay_stem(out, an_bez(q, vec2(58.0, 11.0), vec2(90.0, 31.0), vec2(150.0, 26.0)), 1.0, 0.6, stem, px) out = an_lay_stem(out, an_bez(q, vec2(146.0, 21.5), vec2(156.0, 9.0), vec2(174.0, 12.0)), 0.9, 0.6, stem, px) out = an_lay_leaf(out, an_leaf(q, vec2(28.0, 15.0), normalize(vec2(1.0, -0.42)), 21.0, 4.0, -1.8), dk, lt, 0.5, px) out = an_lay_leaf(out, an_leaf(q, vec2(66.0, 8.5), normalize(vec2(1.0, 0.45)), 19.0, 3.6, 1.8), dk, lt, 0.5, px) out = an_lay_leaf(out, an_leaf(q, vec2(96.0, 13.0), normalize(vec2(1.0, -0.55)), 16.0, 3.0, -1.4), dk, lt, 0.5, px) out = an_lay_leaf(out, an_leaf(q, vec2(104.0, 25.5), normalize(vec2(1.0, 0.15)), 15.0, 2.8, 1.2), dk, lt, 0.5, px) out = an_lay_leaf(out, an_leaf(q, vec2(136.0, 20.5), normalize(vec2(1.0, 0.42)), 17.0, 3.2, 1.6), dk, lt, 0.5, px) out = an_lay_leaf(out, an_leaf(q, vec2(196.0, 23.5), normalize(vec2(1.0, -0.62)), 16.0, 3.0, -1.4), dk, lt, 0.5, px) out = an_lay_leaf(out, an_leaf(q, vec2(214.0, 20.0), normalize(vec2(1.0, 0.5)), 12.0, 2.4, 1.0), dk, lt, 0.45, px) out = an_lay_leaf(out, an_leaf(q, vec2(8.0, 23.0), normalize(vec2(0.6, 1.0)), 9.0, 2.6, 1.0), dk, lt, 0.5, px) out = an_bud(out, q, vec2(150.0, 26.0), normalize(vec2(1.0, -0.1)), 9.0, px) out = an_lily(out, q, vec2(175.0, 12.5), 8.5, 0.7, px, 1.0) out = an_lily(out, q, vec2(16.0, 15.5), 13.0, 0.4, px, 0.0) return out } // The small ornament in a bottom corner of the window, `q` in points from // the corner along its two edges: a tendril's curl in the spandrel the // panes' rounded corner leaves, a leaf running out along each edge from it // and a gold bud on the diagonal. Nothing of it reaches the pane's hole. let an_foot_corner = fn(dst: vec4, q: vec2, px: float) -> vec4 { var out = dst let dk = vec3(0.420, 0.580, 0.486) let lt = vec3(0.616, 0.737, 0.627) let stem = vec3(0.500, 0.650, 0.560) out = an_lay_leaf(out, an_leaf(q, vec2(9.0, 3.0), vec2(1.0, 0.0), 30.0, 1.6, -0.5), dk, lt, 0.4, px) out = an_lay_leaf(out, an_leaf(q, vec2(3.0, 9.0), vec2(0.0, 1.0), 30.0, 1.6, 0.5), dk, lt, 0.4, px) out = an_lay(out, an_spiral(q, vec2(9.6, 9.6), 1.35, 2.0, PI * 1.25) - 0.45, stem, 0.3, px) out = an_lay(out, length(q - vec2(4.6, 4.6)) - 1.5, mix(an_bronze_c(), an_gold_hi(), 0.5), 0.35, px) return out } mod.widgets.Window.show_bg = true mod.widgets.Window.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let W = self.rect_size.x let H = self.rect_size.y var col = an_parch(p, px, an_parch_c()) // the two holes in the frame: the header and the panes let hc = vec2(W * 0.5, 60.0) let hh = vec2(W * 0.5 - 6.0, 27.0) let pc = vec2(W * 0.5, (95.0 + H - 6.0) * 0.5) let ph = vec2(W * 0.5 - 6.0, (H - 6.0 - 95.0) * 0.5) let dh = md_rbox(p, hc, hh, 14.0) // the panes' outer foot corners are wider arches: in the spandrels // they leave the frame carries its foot ornaments let pr = md_r4(p, pc, ph, vec4(12.0, 12.0, 26.0, 26.0)) let dp = md_rbox(p, pc, ph, pr) var dhole = dp var nh = an_nrm(p, pc, ph, pr) if dh < dp { dhole = dh nh = an_nrm(p, hc, hh, 14.0) } // the header is a lighter leaf of parchment col = mix(col, Finish.lift(col, 0.12), Finish.cover(dh, px)) // the parchment is aged toward the frame: a warm browning that fades // out some thirty points in, never a stain in the middle let age = 1.0 - smoothstep(0.0, 34.0, 0.0 - dhole) col = mix(col, col * vec3(0.84, 0.77, 0.66), age * age * 0.55) // the frame's shade on the parchment along each hole's upper and left // edges, three points deep let sh = (1.0 - smoothstep(0.0, 3.5, 0.0 - dhole)) * Finish.cover(dhole, px) * (0.5 + 0.5 * clamp(0.0 - nh.y - nh.x * 0.5, 0.0, 1.0)) col = mix(col, col * 0.80, sh * 0.6) // the teal bands let band = Finish.cover(0.0 - dhole, px) var teal = an_teal() * (1.0 + (Finish.noise(p * 0.11) - 0.5) * 0.05) + g3(Finish.grain(p / px, 0.008)) teal = mix(teal, teal * 1.12, (1.0 - smoothstep(0.0, 33.0, p.y)) * 0.4) col = mix(col, teal, band) // the window's outer edge: a darker teal lip and a bronze wire let dw = md_rbox(p, vec2(W, H) * 0.5, vec2(W, H) * 0.5, 0.0) let nw = an_nrm(p, vec2(W, H) * 0.5, vec2(W, H) * 0.5, 0.0) var out = vec4(col, 1.0) // THE CARTOUCHE under the title, and its whiplash scrolls let cc = vec2(W * 0.5, 16.0) let ch = vec2(84.0, 12.5) let cd = md_rbox(p, cc, ch, 12.5) if abs(p.x - cc.x) < 140.0 && p.y < 35.0 { let cw = an_parch(p, px, vec3(0.925, 0.878, 0.776)) out = an_lay(out, cd, cw, 0.0, px) out = Finish.over(out, an_fine(cd, 2.6, max(px, 0.6), px, 0.7)) out = Finish.over(out, an_wire(cd, an_nrm(p, cc, ch, 12.5), 1.5, px, 1.0)) let sx = abs(p.x - cc.x) let sq = vec2(sx, p.y) out = an_lay(out, an_spiral(sq, vec2(ch.x + 22.0, 13.0), 2.0, 2.0, 2.6) - 0.7, mix(an_bronze_c(), an_gold_hi(), 0.3), 0.0, px) out = an_lay_stem(out, an_bez(sq, vec2(ch.x - 2.0, 25.0), vec2(ch.x + 14.0, 30.0), vec2(ch.x + 18.6, 15.1)), 1.0, 0.6, mix(an_bronze_c(), an_gold_hi(), 0.3), px) } // THE SPRAYS in the top band's two ends, kept clear of the cartouche if p.y < 35.0 && p.x < min(270.0, W * 0.5 - 122.0) { out = an_band_spray(out, vec2(p.x - 6.0, p.y), px) } if p.y < 35.0 && p.x > max(W - 410.0, W * 0.5 + 122.0) && p.x < W - 138.0 { out = an_band_spray(out, vec2(W - 138.0 - p.x, p.y), px) } // the foot corners if p.y > H - 40.0 && (p.x < 40.0 || p.x > W - 40.0) { out = an_foot_corner(out, vec2(min(p.x, W - p.x), H - p.y), px) } // the bronze wire along every hole's edge, on the band's side out = Finish.over(out, an_wire(0.0 - dhole, nh * -1.0, 1.5, px, 1.0)) out = Finish.over(out, an_fine(0.0 - dhole, 2.6, max(px, 0.5), px, 0.35 * step(39.0, p.y))) out = Finish.over(out, an_wire(dw, nw, 1.6, px, 1.0)) return vec4(out.rgb, 1.0) } // A splitter is the frame's teal band run down between two panes, ten // points wide, its bronze wires turning into the header's band above and // the foot band below with rounded corners, as the panes' corners are. mod.widgets.Splitter.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 14.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Splitter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 14.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let v = self.is_vertical // work in the bar's own frame: `a` along it, `b` across it let a = mix(p.x, p.y, v) let b = mix(p.y, p.x, v) let along = mix(size.x, size.y, v) let thick = mix(size.y, size.x, v) let mid = thick * 0.5 let hw = 5.0 let r = 12.0 let a0 = 4.0 let a1 = along - 6.0 // the panes either side, as rounded boxes let q = vec2(b, a) let lc = vec2(mid - hw - 200.0, (a0 + a1) * 0.5) let rc = vec2(mid + hw + 200.0, (a0 + a1) * 0.5) let ph = vec2(200.0, (a1 - a0) * 0.5) let dl = md_rbox(q, lc, ph, r) let dr = md_rbox(q, rc, ph, r) var dh = dl var nh = an_nrm(q, lc, ph, r) if dr < dl { dh = dr nh = an_nrm(q, rc, ph, r) } // the header above: everything before -4 along the bar let dt = a + 4.0 if dt < dh { dh = dt nh = vec2(0.0, 1.0) } let inside = step(-8.0, a) * step(a, along - 1.6) * step(abs(b - mid), hw + r + 0.5) let band = Finish.cover(0.0 - dh, px) * inside var teal = an_teal() * (1.0 + (Finish.noise((p + self.rect_pos) * 0.11) - 0.5) * 0.05) let act = max(max(self.hover, self.drag), self.focus) teal = Finish.lift(teal, 0.08 * act) var out = vec4(teal * band, band) let nn = vec2(mix(nh.y, nh.x, v), mix(nh.x, nh.y, v)) out = Finish.over(out, an_wire(0.0 - dh, nn * -1.0, 1.5, px, 1.0) * inside) // under the hand, a gold line down the middle of the grip let inbar = 1.0 - smoothstep(self.bar_size * 0.5 - 2.0, self.bar_size * 0.5, abs(a - along * 0.5)) let gl = (1.0 - smoothstep(0.5, 0.5 + px, abs(b - mid))) * inbar * act out = Finish.over(out, vec4(an_gold_hi() * gl, gl)) return out } // ---------------------------------------------------------------- switches and meter // THE SWITCHES are the sheet's own toggle: the rocker carries a bronze disc // (the metal of a lever), the slide switch the ivory one, both riding in the // sunk pill, teal when on. mod.widgets.ToggleRocker.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 46.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleRocker.draw_bg.size = 14.0 mod.widgets.ToggleSlide.draw_bg.size = 14.0 mod.widgets.ToggleRocker.min_height = 24.0 mod.widgets.ToggleSlide.min_height = 24.0 mod.widgets.ToggleRocker.draw_bg.pill_aspect = 2.7143 mod.widgets.ToggleSlide.draw_bg.pill_aspect = 2.7143 mod.widgets.ToggleRocker.draw_bg.knob_inset = 7.0 mod.widgets.ToggleSlide.draw_bg.knob_inset = 7.0 mod.widgets.ToggleRocker.clip_x = false mod.widgets.ToggleRocker.clip_y = false mod.widgets.ToggleSlide.clip_x = false mod.widgets.ToggleSlide.clip_y = false mod.widgets.ToggleRocker.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex mod.widgets.ToggleSlide.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex mod.widgets.ToggleRocker.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return an_toggle(self.pos, self.rect_size, self.rect_pos, px, self.active, self.drag_pos, self.drag, self.hover, self.down, self.focus, self.disabled, self.error, self.mark_at_end, 1.0) } mod.widgets.ToggleSlide.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return an_toggle(self.pos, self.rect_size, self.rect_pos, px, self.active, self.drag_pos, self.drag, self.hover, self.down, self.focus, self.disabled, self.error, self.mark_at_end, 0.0) } // THE GLASS of the readouts, the screen and the meter: deep teal glass with // the faint unevenness of cathedral glass and a soft sheen across its upper // left, set behind a bronze wire, a dark gap and a fine gold line, its // corners rounded; the surround's shade falls in under its top edge. let an_glass = fn(p: vec2, lo: vec2, hi: vec2, r4: vec4, px: float, face: vec3) -> vec4 { let h = (hi - lo) * 0.5 let c = lo + h let r = md_r4(p, c, h, r4) let d = md_rbox(p, c, h, r) var out = an_drop(p, c, h, r, 0.8) let hf = h - vec2(1.8, 1.8) var g = face * (1.0 + (Finish.noise(p * 0.21 + vec2(4.0, 9.0)) - 0.5) * 0.035 + (Finish.noise(p * 0.6) - 0.5) * 0.025) let u = (p - lo) / max(hi - lo, vec2(1.0, 1.0)) let diag = u.x * 0.8 + u.y let sheen = smoothstep(0.0, 0.25, diag) * (1.0 - smoothstep(0.35, 0.75, diag)) * 0.06 g = g + vec3(0.95, 0.92, 0.80) * sheen g = mix(g, vec3(0.03, 0.05, 0.04), clamp(Finish.inset(p, c, hf, max(r - 1.8, 0.0), vec2(0.0, 2.2), 2.0), 0.0, 1.0) * 0.55) let a = Finish.cover(d, px) out = Finish.over(out, vec4(g * a, a)) out = Finish.over(out, an_fine(d, 3.0, max(px, 0.55), px, 0.5)) return Finish.over(out, an_wire(d, an_nrm(p, c, h, r), 1.8, px, 1.0)) } mod.widgets.ScreenView.clip_x = false mod.widgets.ScreenView.clip_y = false mod.widgets.ScreenView.padding = mod.turtle.Inset{top: 8.0 right: 11.0 bottom: 8.0 left: 11.0} mod.widgets.ScreenView.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ScreenView.draw_bg.pixel = fn() { let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos return an_glass(p, lo, hi, vec4(9.0, 9.0, 9.0, 9.0), px, self.color.rgb) } mod.widgets.ScreenView.bezel = 3.0 mod.widgets.ScreenView.sheen = 0.02 // THE METER sits in the same glass: its window arched at the top, the scale // a fine gold arc with gold ticks, the colour band under it running sage to // ochre to terracotta, the needle an ivory spear and its pivot a bronze boss. mod.widgets.NeedleMeter.clip_x = false mod.widgets.NeedleMeter.clip_y = false mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(9.0) mod.widgets.NeedleMeter.draw_bg.color = uniform(#213530) mod.widgets.NeedleMeter.draw_bg.color_needle = uniform(#xf3e8cf) mod.widgets.NeedleMeter.draw_bg.color_zone = uniform(#xd0603f) mod.widgets.NeedleMeter.draw_bg.color_scale = uniform(#xe6c47e) mod.widgets.NeedleMeter.draw_bg.vertex = fn() { let g = md_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.NeedleMeter.draw_bg.pixel = fn() { let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos let r = min(self.border_radius, min(hi.x - lo.x, hi.y - lo.y) * 0.5) let rt = min(34.0, min(hi.x - lo.x, hi.y - lo.y) * 0.45) var out = an_glass(p, lo, hi, vec4(rt, rt, r, r), px, an_glass_c()) let pv = self.pivot() let rr = self.scale_radius() let q = p - pv let rq = length(q) let ang = atan2(q.x, 0.0 - q.y) let sweep = self.sweep * 0.017453293 let t = ang / sweep + 0.5 let on_scale = step(-0.0001, t) * step(t, 1.0001) let inside = Finish.cover(md_rbox4(p, (lo + hi) * 0.5, (hi - lo) * 0.5 - vec2(2.0, 2.0), vec4(rt - 2.0, rt - 2.0, r - 2.0, r - 2.0)), px) // the colour band under the scale let bandr = Finish.cover(abs(rq - (rr - 2.6)) - 1.6, px) * on_scale let ramp = mix(mix(vec3(0.47, 0.66, 0.53), vec3(0.88, 0.68, 0.33), smoothstep(0.35, 0.7, t)), self.color_zone.rgb, step(self.zone, t)) let ba = bandr * 0.85 * inside out = Finish.over(out, vec4(ramp * ba, ba)) // the scale: a hairline arc and a tick at every division let arc = Finish.cover(abs(rq - rr) - px * 0.6, px) * on_scale let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0) let k = floor(clamp(t, 0.0, 1.0) * n + 0.5) let ak = (k / n - 0.5) * sweep let perp = abs(q.x * cos(ak) + q.y * sin(ak)) let mn = max(floor(self.minors + 0.5), 1.0) let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001)) let len = mix(self.tick_minor, self.tick_major, major) let radial = max(rr - len - rq, rq - rr) let tick = Finish.cover(perp - px * mix(0.55, 0.9, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak)) let sa = max(arc, tick) * inside out = Finish.over(out, vec4(self.color_scale.rgb * sa, sa)) // the needle: a slim spear from under the pivot to just past the scale let na = self.needle_angle(self.value) let nd = vec2(sin(na), 0.0 - cos(na)) let sp = an_leaf(p, pv - nd * 3.0, nd, rr + 6.0, 1.4, 0.0) let nf = mix(self.color_needle.rgb, an_gold_hi(), smoothstep(0.3, 1.0, sp.z) * 0.3) out = an_lay(out, sp.x, nf * inside + self.color_needle.rgb * (1.0 - inside), 0.4, px) let pd = rq - self.pivot_size - 0.8 let bl = clamp(0.5 - 0.6 * dot(q / max(self.pivot_size, 1.0), vec2(0.45, 0.85)), 0.0, 1.0) out = an_lay(out, pd, mix(an_bronze_dk(), an_gold_hi(), bl), 0.5, px) return out * self.color.a * self.opacity } mod.widgets.NeedleMeter.draw_label.color = #xe6c47e // ---------------------------------------------------------------- lamps // LAMPS: jewels of coloured glass, cabochons in polished bronze settings, as // the stained glass of the period. The emitter is the light: at full current // its middle burns toward white and its rim is its most saturated colour; at // lower current the middle is no longer white, the colour deeper, the glow // smaller; out, it is dark tinted glass, its colour barely there. Its light // is added, never painted: a tight hot halo and a short bloom in its own // hue, gone in about two radii; on the pale parchment its hue is also laid // round it as a tint that keeps the ground's luminance. A bar is the same // light drawn out into a short light pipe. let md_led_lamp_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 { let est = res.rgb + ground * (1.0 - res.a) let add = (vec3(1.0, 1.0, 1.0) - exp(0.0 - light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0)) return vec4(res.rgb + add, res.a) } let md_led_lamp_full = fn(ink: vec3) -> vec3 { return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01) } let md_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 { let f = md_led_lamp_full(ink) let mn = min(f.x, min(f.y, f.z)) let ch = 1.0 - mn let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001) return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch)) } let md_led_lamp = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float) -> vec4 { let rt = max(min(hb.x, hb.y), 0.4) let d = Material.sd_box(p, c, hb, rt) let bw = clamp(0.32 * rt, 0.8, 2.4) let ds = d - bw let L = clamp(lit, 0.0, 1.0) let b = md_led_lamp_full(ink) let hue = md_led_lamp_hue(ink, vivid) let pw = L * sqrt(L) let white = vec3(1.0, 1.0, 1.0) let lum = vec3(0.2126, 0.7152, 0.0722) 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) 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 setting's shadow on the parchment let shd = Finish.drop(p, c, hb + vec2(bw, bw), rt + bw, vec2(0.15, 0.12 * rt + 0.3), 0.10 * rt + 0.5) * 0.32 * (1.0 - 0.6 * pw) res = Finish.over(res, vec4(an_shade() * shd, shd)) if L > 0.001 { // the tint on the pale ground: its hue laid over, never darker let h = clamp(0.42 * pw * pow(2.0, 0.0 - x / max(0.4 * reach, 0.5)) * win, 0.0, 0.45) let est = res.rgb + ground * (1.0 - res.a) let w = clamp(dot(hue, lum) / max(dot(est, lum), 0.001), 0.0, 1.0) res = Finish.over(res, vec4(hue * h * w, h * w)) } // the bronze setting: a polished ring rounded over, lit from the upper // left, a dark seat line where it holds the stone 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 = an_bronze_c() * (0.92 + 0.45 * facing) rc = mix(rc, an_gold_hi(), smoothstep(0.5, 0.95, facing) * 0.75) rc = mix(rc, an_bronze_dk(), Finish.band(ds, 0.0, max(px, 0.45), px) * 0.7) rc = mix(rc, an_bronze_dk() * 0.6, Finish.ring_out(d, 0.0, max(px, 0.45), px) * 0.8) res = Finish.over(res, vec4(min(rc, white) * a_s, a_s)) if L > 0.001 { let xl = max(d, 0.0) let halo = exp(0.0 - xl / max(0.30 * rt, 0.4)) * 1.5 + exp(0.0 - xl / max(0.65 * rt, 0.7)) * 0.32 let hwin = 1.0 - smoothstep(1.1 * rt, 2.0 * rt + 1.0, xl) res = md_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground) } // THE STONE: a glossy cabochon of coloured glass, deeper at the top, // light pooling low inside; 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(0.0 - (q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30) var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top) on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5) on = mix(on, vec3(1.0, 0.99, 0.975), exp(0.0 - t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85) on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t)) on = on * (0.42 + 0.58 * L) // out: dark tinted glass, its colour barely there, a catch light along // its lower rim let offc = mix(vec3(0.075, 0.08, 0.07), b * 0.42, 0.24) var off = mix(offc * 0.62, offc * 1.15, 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.28, 0.24) * catchl var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L)) let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30)) let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0)) col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt)) col = min(col, white) let a_l = Finish.cover(d, px) return Finish.over(res, vec4(col * a_l, a_l)) } // The Lamp widget and the bar lamp. A round jewel takes the middle of its // cell; its halo's room is held in the cell (the library reserves a lens // thickness either side once a halo is asked for), so the light is never cut // at its edge and fades within half the gap. A bar is a short light pipe set // at the left of its cell, six points thick and twice that long, its glow // held to two and a half points round its setting -- the whole lamp takes // the first twenty points of its cell and leaves the rest free. mod.widgets.Lamp.halo = 0.3 mod.widgets.Lamp.size = 14.0 mod.widgets.Lamp.halo_reach = 0.3 mod.widgets.LampBar.size = 14.0 mod.widgets.LampBar.halo_reach = 0.3 mod.widgets.Lamp.draw_bg.color_accent = uniform(#xffa83a) mod.widgets.Lamp.draw_bg.color_success = uniform(#x34c79c) mod.widgets.Lamp.draw_bg.color_warning = uniform(#xf2c443) mod.widgets.Lamp.draw_bg.color_error = uniform(#xe5483a) mod.widgets.Lamp.draw_bg.color_plain = uniform(#xfff3d8) mod.widgets.Lamp.draw_bg.vertex = fn() { let m = 4.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 = 4.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let reach = clamp(self.reach, 0.0, 1.0) let t = min(self.rect_size.y / (1.0 + 2.0 * reach), 14.0) let room = (self.rect_size.y - t) * 0.5 let tb = min(t, 6.0) let lb = 14.0 let c = mix(self.rect_size * 0.5, vec2(1.6 + lb * 0.5, self.rect_size.y * 0.5), self.bar) let hb = mix(vec2(t * 0.5, t * 0.5), vec2(lb * 0.5, tb * 0.5), self.bar) let glow = mix(max(room - 2.5, 2.0), 2.2, self.bar) let res = md_led_lamp(vec4(0.0, 0.0, 0.0, 0.0), p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, an_parch_c(), glow) return res * self.opacity } true