use mod.prelude.widgets_internal.* // Cast concrete, lit from straight above. A control is a cast BLOCK (button, // thumb, knob, toggle cube, slab) or a POCKET sunk into the slab (field, // groove, check box, slot, chosen cell). Blocks: the slab's own grey with its // pores and sand, a chamfered shoulder lit at the top and dark at the foot, a // crisp light line along the top edge, a dark hairline, and a soft cast // shadow under them. Pockets: a darker floor, a shade falling in under the // top edge, a black line inside it, a light lip under the pocket. Light only // where a value is: a pale verdigris inlay set in dark glass. // A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and // handed to each field below. A field is a pocket and draws its seat and lip // inside its own box, since it clips its own text; so the box is 28 and the // visible pocket 26, the height of a button's block, whose shadow falls // outside its box. let field_height = 28.0 let field_pad_v = mod.theme.mspace_1.top let field_radius = 2.0 let field_border = 0.34 // The room a Fit field takes: where a field is Fit, the padding IS its // height. let field_room = fn(w) { w.min_height = field_height w.padding.top = field_pad_v w.padding.bottom = field_pad_v } // The box a field draws. Given the draw object and not the widget, because // not every field draws its own box. let field_face = fn(d) { d.border_radius = field_radius d.border_size = field_border } // A field that states a frame and lays its own parts out inside it takes // the height alone: a padding would displace its line. let field_frame = fn(w) { w.min_height = field_height } field_room(mod.widgets.TextInput) field_face(mod.widgets.TextInput.draw_bg) field_room(mod.widgets.TextInputFlat) field_room(mod.widgets.ComboBox) field_face(mod.widgets.ComboBox.draw_bg) field_room(mod.widgets.DropDown) field_face(mod.widgets.DropDown.draw_bg) field_room(mod.widgets.DropDown2) field_face(mod.widgets.DropDown2.draw_bg) field_room(mod.widgets.FieldWell) field_face(mod.widgets.FieldWell.draw_bg) field_room(mod.widgets.TagField) field_face(mod.widgets.TagField.draw_bg) field_frame(mod.widgets.NumberField) mod.widgets.NumberField.draw_bg.border_radius = field_radius field_frame(mod.widgets.ValueInput) field_frame(mod.widgets.DateField) field_frame(mod.widgets.TimeField) field_frame(mod.widgets.DatePicker) field_frame(mod.widgets.DateRangePicker) // The chrome-less input a well holds must impose no height of its own. mod.widgets.WellInput.min_height = 0.0 mod.widgets.Window.pass.clear_color = #8b8a86 // Plain views stop clipping, so a cast 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. `w` is a point in window space. let g3 = fn(v: float) -> vec3 { return vec3(v, v, v) } // A rounded box, true radius. let cc_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 cc_rbox4 = fn(p: vec2, c: vec2, h: vec2, r4: vec4) -> float { let q0 = p - c let rt = mix(r4.y, r4.x, step(q0.x, 0.0)) let rb = mix(r4.z, r4.w, step(q0.x, 0.0)) let r = clamp(mix(rb, rt, step(q0.y, 0.0)), 0.0, min(h.x, h.y)) let q = abs(q0) - h + vec2(r, r) return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0, 0.0))) - r } // The corners a button group asks for: a corner written as 0 is a square // join, any other the family's radius. let cc_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 cc_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 cc_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 cast's tone at window point w, as a signed share: broad blotches, the // vertical streaks the form water left, fine sand and a free grain. let cc_tex = fn(w: vec2, px: float) -> float { let mott = (Finish.fbm(w * 0.021) - 0.5) * 0.20 let streak = (Finish.fbm(vec2(w.x * 0.15, w.y * 0.009) + vec2(31.0, 7.0)) - 0.5) * 0.10 let sand = (Finish.noise(w * 0.7) - 0.5) * 0.06 return mott + streak + sand + Finish.grain(w / px, 0.03) } // One lattice of pores: a hole in some of the cells, dark, its lower rim // catching the light. `.x` the hole, `.y` the lit lip. let cc_pore_layer = fn(w: vec2, px: float, cell: float, dens: float, r0: float, r1: float, seed: vec2) -> vec2 { let g = floor(w / cell) let h0 = Finish.hash(g + seed) let h1 = Finish.hash(g + seed + vec2(17.31, 41.73)) let h2 = Finish.hash(g + seed + vec2(-9.17, 23.41)) let h3 = Finish.hash(g + seed + vec2(5.53, -3.37)) let ctr = (g + vec2(0.25 + 0.5 * h1, 0.25 + 0.5 * h2)) * cell let r = mix(r0, r1, h0 * h0 * h0) let q = w - ctr let d = length(q) - r let on = step(h3, dens) let hole = Finish.cover(d, px) * on let lip = Finish.ring_out(d, 0.0, px, px) * smoothstep(0.0, r, q.y) * on return vec2(hole, lip) } // Pores at two scales: a fine pepper and the odd air bubble. let cc_pores = fn(w: vec2, px: float, amt: float) -> vec2 { let a = cc_pore_layer(w, px, 6.0, 0.16 * amt, 0.28, 1.0, vec2(0.0, 0.0)) let b = cc_pore_layer(w, px, 37.0, 0.24 * amt, 0.8, 2.0, vec2(113.0, 57.0)) return vec2(max(a.x, b.x), max(a.y, b.y)) } // The material laid on a base colour: tone, then pores. let cc_stone = fn(base: vec3, w: vec2, px: float, tex: float, pore: float) -> vec3 { var col = base * (1.0 + cc_tex(w, px) * tex) let pr = cc_pores(w, px, pore) col = mix(col, base * 0.32, pr.x * 0.75) col = mix(col, Finish.lift(base, 0.30), pr.y * 0.45) return col } // The soft cast shadow under a block: a broad one cast down and a tight // contact one, both near black. `soft` and `tight` scale them. let cc_cast = fn(p: vec2, c: vec2, h: vec2, r: float, soft: float, tight: float) -> vec4 { let s1 = Finish.drop(p, c, h, r, vec2(0.0, 2.4), 1.9) * 0.78 * soft let s2 = Finish.drop(p, c, h, r, vec2(0.0, 0.7), 0.6) * 0.70 * tight let a = clamp(s1 + s2 * (1.0 - s1), 0.0, 1.0) return vec4(0.05 * a, 0.05 * a, 0.045 * a, a) } // A cast block's face, premultiplied: the material over a fall from the // lit top to the foot, the chamfered shoulder (light facing up, dark facing // down), the light line on the top edge, a dark hairline. Pressed, the block // sits lower: darker, a shade under its top edge, the shoulder quiet. let cc_cap = fn(p: vec2, c: vec2, h: vec2, r4: vec4, o: vec2, px: float, hover: float, press: float, dis: float, tint: vec4) -> vec4 { let d = cc_rbox4(p, c, h, r4) let r = max(max(r4.x, r4.y), max(r4.z, r4.w)) let yin = p.y - (c.y - h.y) let t = clamp(yin / max(2.0 * h.y, 1.0), 0.0, 1.0) let e = 0.5 let gx = cc_rbox4(p + vec2(e, 0.0), c, h, r4) - cc_rbox4(p - vec2(e, 0.0), c, h, r4) let gy = cc_rbox4(p + vec2(0.0, e), c, h, r4) - cc_rbox4(p - vec2(0.0, e), c, h, r4) let g = vec2(gx, gy) / max(length(vec2(gx, gy)), 0.0001) var base = mix(vec3(0.640, 0.636, 0.618), vec3(0.545, 0.541, 0.525), t) base = mix(base, tint.rgb * (Finish.lum(base) / 0.62), tint.a) var face = cc_stone(base, p + o + vec2(37.0, 11.0), px, 0.9, 0.45) face = face * (1.0 - 0.13 * press) face = mix(face, vec3(0.05, 0.05, 0.045), clamp(Finish.inset(p, c, h, r, vec2(0.0, 1.2), 1.4), 0.0, 1.0) * 0.50 * press) let bev = Finish.bevel(d, g, 2.6, vec2(0.0, -1.0)) * Finish.cover(d, px) face = Finish.lift(face, bev * mix(0.50, 0.06, press) * mix(1.0, 0.35, dis)) // the foot: the block's lower edge in its own shade face = mix(face, face * 0.78, smoothstep(2.0 * h.y - 3.5, 2.0 * h.y, yin) * (1.0 - press) * (1.0 - dis)) face = Finish.lift(face, 0.09 * hover * (1.0 - press)) face = mix(face, vec3(0.62, 0.617, 0.60), dis * 0.45) let across = 1.0 - smoothstep(h.x - r - 0.5, h.x - r * 0.2, abs(p.x - c.x)) let hl = (Finish.band(d, px, 2.0 * px, px) + 0.45 * Finish.band(d, 2.0 * px, 3.0 * px, px)) * mix(1.0, across, step(0.5, r)) * step(yin, max(r, 2.0 * px) + 2.0 * px) face = mix(face, vec3(0.93, 0.925, 0.91), clamp(hl, 0.0, 1.0) * mix(0.90, 0.25, press) * mix(1.0, 0.4, dis)) let olc = mix(vec3(0.36, 0.355, 0.34), vec3(0.13, 0.13, 0.125), smoothstep(0.15, 0.85, t)) face = mix(face, olc, Finish.band(d, 0.0, px, px) * mix(0.95, 0.5, dis)) let a = Finish.cover(d, px) return vec4(face * a, a) } // A pocket sunk into the slab, premultiplied: its floor (the material, a // little quieter), the shade falling in under the top edge, a black line // inside the top, a dark rim, and round it a dark seat above and a light // lip below. `fill` is the floor's grey, `shade` the depth. let cc_pocket = fn(p: vec2, c: vec2, h: vec2, r: float, o: vec2, px: float, fill: vec3, shade: float, lip: float) -> vec4 { let d = cc_rbox(p, c, h, r) let yin = p.y - (c.y - h.y) var col = cc_stone(fill, p + o + vec2(71.0, 5.0), px, 0.55, 0.35) let sh = clamp(Finish.inset(p, c, h, r, vec2(0.0, 1.5), 1.9), 0.0, 1.0) col = mix(col, vec3(0.03, 0.03, 0.028), sh * shade) let top = 1.0 - smoothstep(0.0, max(h.y, 1.0), yin) col = mix(col, vec3(0.05, 0.05, 0.048), Finish.band(d, px, 2.0 * px, px) * top * 0.55 * shade) col = mix(col, vec3(0.10, 0.10, 0.095), Finish.band(d, 0.0, px, px) * mix(0.55, 0.9, top)) let a = Finish.cover(d, px) let up = smoothstep(0.0, h.y * 0.85, c.y - p.y) let low = smoothstep(0.0, h.y * 0.85, p.y - c.y) let li = (Finish.ring_out(d, 0.0, px, px) * 0.75 + Finish.ring_out(d, px, 2.0 * px, px) * 0.25) * low * lip let sd = Finish.ring_out(d, 0.0, px, px) * up * 0.30 * lip var out = vec4(0.88 * li, 0.875 * li, 0.86 * li, li) out = Finish.over(out, vec4(0.05 * sd, 0.05 * sd, 0.045 * sd, sd)) return Finish.over(out, vec4(col * a, a)) } // Focus: a device pixel and a half of verdigris two points outside the edge. let cc_focus = fn(d: float, px: float, focus: float) -> vec4 { let f = Finish.ring_out(d, 1.5, 1.5 + 1.5 * px, px) * 0.95 * focus return vec4(0.184 * f, 0.490 * f, 0.384 * f, f) } // The lit inlay colour and its core. let cc_lit = fn(k: float) -> vec3 { return mix(vec3(0.80, 0.95, 0.85), vec3(0.435, 0.682, 0.533), smoothstep(0.0, 0.5, k)) } // A round glass inlay set into a block or the slab: lit, a pale core, the // verdigris body and a soft halo held within two points; dark, smoked glass // with a glint. `ink` is the lit colour. let cc_lamp = fn(p: vec2, lc: vec2, rad: float, px: float, ink: vec3, core: vec3, lit: float) -> vec4 { let q = p - lc let d = length(q) - rad let k = clamp(length(q) / max(rad, 0.001), 0.0, 1.0) let v = clamp(q.y / max(rad, 0.001) * 0.5 + 0.5, 0.0, 1.0) let on = mix(mix(core, ink, smoothstep(0.1, 0.75, k)), ink * 0.72, smoothstep(0.75, 1.0, k)) let off = mix(vec3(0.14, 0.145, 0.14), vec3(0.23, 0.235, 0.23), v) var col = mix(off, on, lit) let gl = Finish.cover(length(q - vec2(-0.3, -0.35) * rad) - rad * 0.28, px) col = mix(col, vec3(0.62, 0.63, 0.62), gl * (1.0 - lit) * 0.6) col = mix(col, mix(vec3(0.06, 0.06, 0.058), ink * 0.45, lit), Finish.band(d, 0.0, px, px) * 0.8) let a = Finish.cover(d, px) let ha = lit * 0.45 * exp(-max(d, 0.0) / 1.1) * step(0.0, d) * (1.0 - a) var out = vec4(ink * ha, ha) let lp = Finish.ring_out(d, 0.0, px, px) * smoothstep(-0.2, 0.5, q.y / max(rad, 0.001)) * 0.6 * (1.0 - lit) out = Finish.over(out, vec4(0.88 * lp, 0.875 * lp, 0.86 * lp, lp)) return Finish.over(out, vec4(col * a, a)) } // --------------------------------------------------------------------------- // BUTTONS. One face for the family; `kind` picks the dressing: 0 the cast // block, 1 the block with a lit verdigris inlay (primary), 2 the block with // its inlay dark (secondary), 4 the block with a lit red inlay (danger), 5 a // stamped frame that rises to a block under the pointer (outline), 6 a // shallow pocket (flatter). The quad is grown by `m` for the cast shadow and // the focus ring. let cc_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, dis: float, loading: float, anim: float, r4: vec4, kind: float, m: float, tint: vec4) -> vec4 { let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - dis let press = down * live let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = cc_rbox4(p, c, h, r4) let r = max(max(r4.x, r4.y), max(r4.z, r4.w)) var out = vec4(0.0, 0.0, 0.0, 0.0) if kind < 4.5 { out = cc_cast(p, c, h, r, (1.0 - press * 0.85) * (1.0 - dis) * (1.0 + 0.25 * hover * live), 1.0 - 0.6 * dis) out = Finish.over(out, cc_cap(p, c, h, r4, o, px, hover * live, press, dis, tint)) if kind > 0.5 { let lc = vec2(Finish.snap(o.x + lo.x + 9.5, px) - o.x, Finish.snap(o.y + c.y, px) - o.y) let red = step(3.5, kind) let ink = mix(vec3(0.435, 0.682, 0.533), vec3(0.878, 0.216, 0.157), red) let core = mix(vec3(0.88, 0.99, 0.92), vec3(1.0, 0.78, 0.70), red) let lit = (1.0 - step(1.5, kind) * step(kind, 2.5)) * live out = Finish.over(out, cc_lamp(p, lc + vec2(0.0, press * 0.5), 2.6, px, ink, core, lit)) } } else { if kind < 5.5 { let show = clamp(hover * live * 0.85 + press, 0.0, 1.0) out = cc_cast(p, c, h, r, show * (1.0 - press * 0.85), show) out = Finish.over(out, cc_cap(p, c, h, r4, o, px, 0.0, press, dis, tint) * show) // at rest a stamped frame: a dark groove a point wide, lit on // its lower inner side let rest = 1.0 - show let gd = abs(d + 0.6) - 0.6 let gr = Finish.cover(gd, px) * mix(0.55, 0.25, dis) * rest out = Finish.over(out, vec4(0.06 * gr, 0.06 * gr, 0.055 * gr, gr)) let li = Finish.band(d, 1.2, 1.2 + px, px) * smoothstep(0.0, h.y * 0.8, p.y - c.y) * 0.5 * rest let lo2 = Finish.ring_out(d, 0.0, px, px) * smoothstep(0.0, h.y * 0.8, p.y - c.y) * 0.55 * rest out = Finish.over(out, vec4(0.88 * (li + lo2), 0.875 * (li + lo2), 0.86 * (li + lo2), li + lo2)) } else { let fill = Finish.lift(vec3(0.537, 0.533, 0.518), 0.10 * hover * live - 0.06 * press) out = cc_pocket(p, c, h, r, o, px, mix(fill, vec3(0.58, 0.577, 0.56), dis), mix(0.55 + 0.25 * press, 0.2, dis), mix(1.0, 0.5, dis)) } } out = Finish.over(out, cc_focus(d, px, focus * live)) // The wait spinner, which the stock face drew and this one must too. if loading > 0.0 { let sdf = Sdf2d.viewport(p) let rr = max(min(h.x, h.y) - 5.0, 2.0) let a0 = anim * 2.0 * PI sdf.arc_round_caps(c.x, c.y, rr, a0, a0 + 1.5 * PI, 1.2) sdf.fill(vec4(0.16, 0.16, 0.15, loading)) out = Finish.over(out, sdf.result) } return out } // Every button template: the root and the sizes share its draw object, so // they wear the plain block; 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 = cc_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cc_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cc_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 2.5), 0.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0)), 6.0) } mod.widgets.Button.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cc_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cc_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 2.5), 0.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0)), 6.0) } mod.widgets.ButtonPrimary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cc_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cc_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 2.5), 1.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0)), 6.0) } mod.widgets.ButtonSecondary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cc_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cc_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 2.5), 2.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0)), 6.0) } mod.widgets.ButtonTertiary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cc_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cc_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 2.5), 0.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0)), 6.0) } mod.widgets.ButtonDanger.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cc_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cc_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 2.5), 4.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0)), 6.0) } mod.widgets.ButtonOutline.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cc_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cc_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 2.5), 5.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0)), 6.0) } mod.widgets.ButtonFlatter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(cc_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, cc_corners(self.border_radius_tl, self.border_radius_tr, self.border_radius_br, self.border_radius_bl, 2.5), 6.0, 6.0, vec4(0.0, 0.0, 0.0, 0.0)), 6.0) } // Templates that draw a face of their own on top of these roots keep the // plain quad: the grown one is only for the faces written here. mod.widgets.CloseButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.CopyButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.BurgerButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.ButtonFloating.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.ButtonFloatingSm.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.glass.CutButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } // Geometry: one 26 point row, 12 points either side of the label; the inlay // takes its room from the left padding of the faces that carry one. mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 19.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonSecondary.padding = mod.turtle.Inset{left: 19.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonDanger.padding = mod.turtle.Inset{left: 19.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 4.0 bottom: 4.0} mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 14.0 height: Fit} mod.widgets.ButtonFlat.min_height = 26.0 mod.widgets.Button.min_height = 26.0 mod.widgets.ButtonXs.min_height = 0.0 mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 0.0 bottom: 0.0} mod.widgets.ButtonXs.draw_text.text_style.font_size = 6.0 mod.widgets.ButtonSm.min_height = 0.0 // Label ink: the stamped near black on every block; the inlay says the role, // not the ink. Primary and danger set in the bold face. mod.widgets.ButtonPrimary.draw_text.color = #161615 mod.widgets.ButtonPrimary.draw_text.color_hover = #0e0e0d mod.widgets.ButtonPrimary.draw_text.color_down = #0e0e0d mod.widgets.ButtonPrimary.draw_text.color_focus = #161615 mod.widgets.ButtonPrimary.draw_text.color_disabled = #5e5d59 mod.widgets.ButtonPrimary.draw_icon.color = #161615 mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 8.5} mod.widgets.ButtonSecondary.draw_text.color = #161615 mod.widgets.ButtonSecondary.draw_text.color_hover = #0e0e0d mod.widgets.ButtonSecondary.draw_text.color_down = #0e0e0d mod.widgets.ButtonSecondary.draw_text.color_focus = #161615 mod.widgets.ButtonSecondary.draw_icon.color = #161615 mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.ButtonTertiary.draw_text.color = #161615 mod.widgets.ButtonTertiary.draw_text.color_hover = #0e0e0d mod.widgets.ButtonTertiary.draw_text.color_down = #0e0e0d mod.widgets.ButtonTertiary.draw_text.color_focus = #161615 mod.widgets.ButtonTertiary.draw_icon.color = #161615 mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_label{font_size: 8.5} mod.widgets.ButtonDanger.draw_text.color = #161615 mod.widgets.ButtonDanger.draw_text.color_hover = #0e0e0d mod.widgets.ButtonDanger.draw_text.color_down = #0e0e0d mod.widgets.ButtonDanger.draw_text.color_focus = #161615 mod.widgets.ButtonDanger.draw_icon.color = #161615 mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_bold{font_size: 8.5} mod.widgets.ButtonOutline.draw_text.color = #161615 mod.widgets.ButtonOutline.draw_text.color_hover = #0e0e0d mod.widgets.ButtonOutline.draw_text.color_down = #0e0e0d mod.widgets.ButtonOutline.draw_text.color_focus = #161615 mod.widgets.ButtonOutline.draw_text.color_disabled = #5e5d59 mod.widgets.ButtonOutline.draw_icon.color = #161615 mod.widgets.ButtonFlat.draw_bg.loading_color = #2a2927 // --------------------------------------------------------------------------- // CHECK BOXES. A 14 point pocket sunk into the slab; set, a stamped block // check stands in it, light on its upper edge. Under the pointer the pocket // lightens. The quad grows four points for the lip and the focus ring. mod.widgets.CheckBoxFlat.clip_x = false mod.widgets.CheckBoxFlat.clip_y = false mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() { let g = cc_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(2.0, h.y, step(20.0, self.border_radius)) let d = cc_rbox(p, c, h, r) let fill = Finish.lift(mix(vec3(0.33, 0.328, 0.318), vec3(0.50, 0.497, 0.485), self.disabled), 0.30 * self.hover * live - 0.08 * self.down * live) var out = cc_pocket(p, c, h, r, o, px, fill, mix(0.75 + 0.2 * self.down * live, 0.3, self.disabled), mix(1.0, 0.5, self.disabled)) let er = Finish.band(d, 0.0, 2.0 * px, px) * self.error out = Finish.over(out, vec4(0.78 * er, 0.20 * er, 0.16 * er, er)) // the check, or the mixed dash: a 1.6 point stroke, pale, its shadow // one device pixel under it let k = sz / 14.0 let m0 = lo + vec2(3.6, 7.2) * k let m1 = lo + vec2(6.0, 9.7) * k let m2 = lo + vec2(10.5, 4.4) * k let ck = min(cc_seg(p, m0, m1), cc_seg(p, m1, m2)) - 0.8 * k let ck_s = min(cc_seg(p - vec2(0.0, px), m0, m1), cc_seg(p - vec2(0.0, px), m1, m2)) - 0.8 * k let d0 = lo + vec2(4.0, 7.0) * k let d1 = lo + vec2(10.0, 7.0) * k let dash = cc_seg(p, d0, d1) - 0.8 * k let dash_s = cc_seg(p - vec2(0.0, px), d0, d1) - 0.8 * k let show_ck = self.active * (1.0 - self.mixed) let mk = mix(Finish.cover(ck, px) * show_ck, Finish.cover(dash, px), self.mixed) let mk_s = mix(Finish.cover(ck_s, px) * show_ck, Finish.cover(dash_s, px), self.mixed) * 0.7 out = Finish.over(out, vec4(0.0, 0.0, 0.0, mk_s)) let ink = mix(mix(vec3(0.925, 0.922, 0.898), vec3(0.95, 0.45, 0.40), self.error), vec3(0.70, 0.697, 0.68), self.disabled) out = Finish.over(out, vec4(ink * mk, mk)) return Finish.over(out, cc_focus(d, px, self.focus * live)) } // --------------------------------------------------------------------------- // TOGGLES. A 30 by 14 point slot sunk into the slab, two cells long, and a // 14 point cast cube riding in it with its shadow; on, the cell the cube has // left shows a lit verdigris inlay. mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 38.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.1429 mod.widgets.ToggleFlat.draw_bg.knob_inset = 1.0 mod.widgets.ToggleFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 4.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let tw = 30.0 let x0 = mix(1.0, size.x - tw - 1.0, self.mark_at_end) let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (size.y - 14.0) * 0.5, px)) - o let c = lo + vec2(tw * 0.5, 7.0) let h = vec2(tw * 0.5, 7.0) let d = cc_rbox(p, c, h, 2.0) let on = self.active let fill = Finish.lift(mix(vec3(0.33, 0.328, 0.318), vec3(0.50, 0.497, 0.485), self.disabled), 0.30 * self.hover * live) var out = cc_pocket(p, c, h, 2.0, o, px, fill, mix(0.75, 0.3, self.disabled), mix(1.0, 0.5, self.disabled)) let er = Finish.band(d, 0.0, 2.0 * px, px) * self.error out = Finish.over(out, vec4(0.78 * er, 0.20 * er, 0.16 * er, er)) let kt = clamp(mix(on, self.drag_pos, self.drag), 0.0, 1.0) // the lamp in the cell the cube leaves: lit only when on let lc = vec2(Finish.snap(o.x + lo.x + 7.5, px) - o.x, Finish.snap(o.y + c.y, px) - o.y) out = Finish.over(out, cc_lamp(p, lc, 2.5, px, vec3(0.435, 0.682, 0.533), vec3(0.88, 0.99, 0.92), on * live)) // the cube let kh = vec2(6.5, 6.5) let kx = Finish.snap(o.x + lo.x + 0.5 + kt * (tw - 14.0), px) - o.x let kc = vec2(kx + 6.5, c.y - 0.5) let press = self.down * live out = Finish.over(out, cc_cast(p, kc, kh, 2.0, (0.8 + 0.2 * self.hover * live - press * 0.5) * mix(1.0, 0.35, self.disabled), 1.0)) out = Finish.over(out, cc_cap(p, kc, kh, vec4(2.0, 2.0, 2.0, 2.0), o, px, self.hover * live, press * 0.6, self.disabled, vec4(0.0, 0.0, 0.0, 0.0))) return Finish.over(out, cc_focus(d, px, self.focus * live)) } // --------------------------------------------------------------------------- // RADIOS. A round cast pebble in the slab; chosen, it is pressed in flush and // a lit verdigris inlay shows at its centre. let cc_radio = fn(p: vec2, c: vec2, rad: float, o: vec2, px: float, hover: float, down: float, focus: float, active: float, dis: float) -> vec4 { let live = 1.0 - dis let q = p - c let d = length(q) - rad let sunk = clamp(active + down * live * 0.6, 0.0, 1.0) var out = cc_cast(p, c, vec2(rad, rad), rad, (1.0 - sunk * 0.85) * (1.0 - dis) * 0.9, 1.0 - 0.6 * dis) // the pebble: the block's face on a disc; its shoulder is the whole dome let t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0) var base = mix(vec3(0.70, 0.696, 0.676), vec3(0.54, 0.536, 0.52), smoothstep(0.1, 1.0, t)) var face = cc_stone(base, p + o + vec2(23.0, 61.0), px, 0.8, 0.5) let dome = clamp(-q.y / rad, -1.0, 1.0) * smoothstep(rad * 0.3, rad, length(q)) face = Finish.lift(face, dome * mix(0.28, 0.06, sunk)) face = face * (1.0 - 0.14 * sunk) face = mix(face, vec3(0.05, 0.05, 0.045), clamp(Finish.inset(p, c, vec2(rad, rad), rad, vec2(0.0, 1.0), 1.2), 0.0, 1.0) * 0.45 * sunk) face = Finish.lift(face, 0.20 * hover * live * (1.0 - sunk)) face = mix(face, vec3(0.62, 0.617, 0.60), dis * 0.45) let up = 1.0 - smoothstep(-0.3, 0.1, q.y / rad) face = mix(face, vec3(0.90, 0.895, 0.88), Finish.band(d, px, 2.0 * px, px) * up * 0.7 * (1.0 - sunk) * live) let olc = mix(vec3(0.36, 0.355, 0.34), vec3(0.13, 0.13, 0.125), t) face = mix(face, olc, Finish.band(d, 0.0, px, px) * mix(0.95, 0.5, dis)) let a = Finish.cover(d, px) out = Finish.over(out, vec4(face * a, a)) // chosen: the ground lip under the sunk pebble, and the inlay let lp = Finish.ring_out(d, 0.0, px, px) * smoothstep(0.0, rad * 0.8, q.y) * 0.6 * sunk out = Finish.over(out, vec4(0.88 * lp, 0.875 * lp, 0.86 * lp, lp)) out = Finish.over(out, cc_lamp(p, c, rad * 0.36, px, vec3(0.435, 0.682, 0.533), vec3(0.88, 0.99, 0.92), active * live) * active) return Finish.over(out, cc_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 = cc_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.glass.RadioButton.draw_bg.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos, self.rect_size) } mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0} mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 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 = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let sz = self.size let lo = vec2(Finish.snap(o.x + 1.0, px), Finish.snap(o.y + (self.rect_size.y - sz) * 0.5, px)) - o let c = lo + vec2(sz * 0.5, sz * 0.5) return cc_radio(p, c, sz * 0.5, o, px, self.hover, self.down, self.focus, self.active, self.disabled) } // The flatter radio is its word alone: chosen, the word sits in a shallow // pocket; under the pointer the slab lightens behind it. mod.widgets.RadioButtonFlatter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let 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 = cc_rbox(p, c, h, 2.0) let lift = Finish.cover(d, px) * 0.12 * self.hover * live * (1.0 - self.active) var out = vec4(lift, lift, lift, lift) out = Finish.over(out, cc_pocket(p, c, h, 2.0, o, px, vec3(0.537, 0.533, 0.518), 0.5, 1.0) * self.active) return Finish.over(out, cc_focus(d, px, self.focus * live)) } // Radio tabs: each a block of its own; the chosen one is held pressed into a // dark pocket, its word pale. mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 6.0 bottom: 2.0} mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.RadioButtonTabFlat.min_height = 26.0 mod.widgets.RadioButtonTabFlat.clip_x = false mod.widgets.RadioButtonTabFlat.clip_y = false mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } // A dark pocket the size of a cell: the chosen answer, pressed right in. let cc_deep = fn(p: vec2, c: vec2, h: vec2, r: float, o: vec2, px: float, hover: float) -> vec4 { return cc_pocket(p, c, h, r, o, px, Finish.lift(vec3(0.29, 0.288, 0.278), 0.08 * hover), 0.85, 1.0) } mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let o = self.rect_pos let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let held = clamp(max(self.active, self.down), 0.0, 1.0) var out = cc_button(self.pos, size, o, px, self.hover, self.down * (1.0 - self.active), self.focus, self.disabled, 0.0, 0.0, vec4(2.5, 2.5, 2.5, 2.5), 0.0, m, vec4(0.0, 0.0, 0.0, 0.0)) * (1.0 - self.active) 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 out = Finish.over(out, cc_deep(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.5, o, px, self.hover) * self.active) return out } mod.widgets.RadioButtonTabFlat.draw_text.color = #161615 mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #0e0e0d mod.widgets.RadioButtonTabFlat.draw_text.color_active = #ecebe5 mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #5e5d59 // --------------------------------------------------------------------------- // SLIDERS. A groove cut into the slab, 5 points deep in dark shadow, the // value a lit verdigris thread along its floor, and a round cast puck riding // on it with its shadow and a dark index line. // The groove: a dark rounded channel with a light lip under it; the lit // thread runs from `f0` to `f1`. let cc_groove = fn(p: vec2, c: vec2, h: vec2, o: vec2, px: float, f0: float, f1: float, dis: float, lit: float) -> vec4 { let r = h.y var out = cc_pocket(p, c, h, r, o, px, vec3(0.20, 0.198, 0.19), 0.8, mix(1.0, 0.5, dis)) let th = max(h.y - 1.4, 0.7) let fh = vec2(max((f1 - f0) * 0.5, 0.0), th) let fc = vec2((f0 + f1) * 0.5, c.y + 0.3) let fd = cc_rbox(p, fc, fh, th) let k = clamp(abs(p.y - fc.y) / th, 0.0, 1.0) let ink = mix(cc_lit(k * k), vec3(0.50, 0.53, 0.51), dis) let fa = Finish.cover(fd, px) * step(0.01, f1 - f0) * lit // the glow the thread throws on the groove's floor round it let gd = cc_rbox(p, c, h, r) let ga = exp(-max(fd, 0.0) / 1.3) * 0.35 * Finish.cover(gd, px) * (1.0 - fa) * step(0.01, f1 - f0) * lit * (1.0 - dis) out = Finish.over(out, vec4(0.435 * ga, 0.682 * ga, 0.533 * ga, ga)) return Finish.over(out, vec4(ink * fa, fa)) } // The puck: a short concrete cylinder seen from above: its shadow, a domed // top lit from above, a darker wall ring at its foot, and a dark index line // pointing along `dir`. let cc_puck = fn(p: vec2, c: vec2, rad: float, o: vec2, px: float, dir: vec2, hover: float, press: float, dis: float) -> vec4 { let live = 1.0 - dis var out = cc_cast(p, c, vec2(rad, rad), rad, (1.0 + 0.3 * hover * live - press * 0.7) * mix(1.0, 0.35, dis), 1.0) let q = p - c let d = length(q) - rad let t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0) var base = mix(vec3(0.74, 0.736, 0.716), vec3(0.56, 0.556, 0.54), smoothstep(0.0, 1.0, t)) var face = cc_stone(base, p + o + vec2(13.0, 29.0), px, 0.8, 0.45) let wall = Finish.band(d, 0.0, max(rad * 0.22, 1.2), px) face = mix(face, face * mix(1.08, 0.62, t), wall) face = Finish.lift(face, 0.22 * hover * live * (1.0 - press) - 0.16 * press) face = mix(face, vec3(0.62, 0.617, 0.60), dis * 0.45) // the index: a dark line with a light line beside it, cut into the top let a0 = c + dir * rad * 0.18 let a1 = c + dir * rad * 0.78 let id = cc_seg(p, a0, a1) - 0.55 let il = cc_seg(p - vec2(0.0, px), a0, a1) - 0.55 face = mix(face, vec3(0.88, 0.875, 0.86), Finish.cover(il, px) * 0.5 * live) face = mix(face, vec3(0.12, 0.12, 0.115), Finish.cover(id, px) * mix(0.9, 0.5, dis)) let up = 1.0 - smoothstep(-0.4, 0.0, q.y / rad) face = mix(face, vec3(0.92, 0.915, 0.90), Finish.band(d, px, 2.0 * px, px) * up * mix(0.8, 0.3, press) * live) let olc = mix(vec3(0.34, 0.335, 0.32), vec3(0.10, 0.10, 0.095), t) face = mix(face, olc, Finish.band(d, 0.0, px, px) * mix(0.95, 0.5, dis)) let a = Finish.cover(d, px) return Finish.over(out, vec4(face * a, a)) } mod.widgets.SliderMinimal.clip_x = false mod.widgets.SliderMinimal.clip_y = false mod.widgets.SliderMinimal.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } 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 = cc_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let cy = Finish.snap(o.y + size.y - 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 kr = 7.0 let span = w - 2.0 * kr let kx = x0 + kr + self.slide_pos * span let ox = x0 + kr + self.origin_pos * span let f0 = mix(x0 + 2.0, min(ox, kx), self.arc_origin) let f1 = mix(kx, max(ox, kx), self.arc_origin) let tc = vec2((x0 + x1) * 0.5, cy) var out = cc_groove(p, tc, vec2(w * 0.5, 2.5), o, px, f0, f1, self.disabled, 1.0) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy) out = Finish.over(out, cc_puck(p, kc, kr, o, px, vec2(0.0, -1.0), max(self.hover, self.focus * 0.5), self.drag * live, self.disabled)) return Finish.over(out, cc_focus(length(p - kc) - kr, px, self.focus * live)) } mod.widgets.SliderFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.75} // The minimal slider: an engraved hairline groove (a dark device pixel over a // light one), the lit thread along it, and a small puck. mod.widgets.SliderMinimal.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let cy = Finish.snap(o.y + size.y - 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 - 2.0 * px, p.y) * step(p.y, cy) * inx let light = step(cy, p.y) * step(p.y, cy + px) * inx let thread = step(cy - 2.0 * px, p.y) * step(p.y, cy) * step(p.x, kx) * inx * live var out = vec4(0.0, 0.0, 0.0, 0.0) out = Finish.over(out, vec4(0.88 * light * 0.7, 0.875 * light * 0.7, 0.86 * light * 0.7, light * 0.7)) let da = dark * 0.75 * mix(1.0, 0.5, self.disabled) out = Finish.over(out, vec4(0.05 * da, 0.05 * da, 0.045 * da, da)) out = Finish.over(out, vec4(0.435 * thread, 0.682 * thread, 0.533 * thread, thread)) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy - 0.5 * px) out = Finish.over(out, cc_puck(p, kc, kr, o, px, vec2(0.0, -1.0), self.hover, self.drag * live, self.disabled)) return Finish.over(out, cc_focus(length(p - kc) - kr, px, self.focus * live)) } mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.75} // The round slider: a pill groove beside its label, the lit thread in it and // a puck on the thread's end; the value reads beside the groove. mod.widgets.SliderRoundFlat.height = 16.0 mod.widgets.SliderRoundFlat.clip_x = false mod.widgets.SliderRoundFlat.clip_y = false mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderRoundFlat.text_input.margin = mod.turtle.Inset{left: 0.0 right: 8.0 top: 0.0 bottom: 0.0} mod.widgets.SliderRoundFlat.text_input.width = 30.0 mod.widgets.SliderRoundFlat.text_input.min_height = 0.0 mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.75} mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 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 x0 = Finish.snap(o.x + self.label_size, px) - o.x let x1 = Finish.snap(o.x + size.x - 44.0, px) - o.x let cy = Finish.snap(o.y + size.y * 0.5, px) - o.y let h = vec2((x1 - x0) * 0.5, 3.0) let c = vec2(x0 + h.x, cy) let kr = 6.0 let kx = x0 + kr + self.slide_pos * (2.0 * h.x - 2.0 * kr) var out = cc_groove(p, c, h, o, px, x0 + 2.0, kx, self.disabled, 1.0) let kc = vec2(Finish.snap(o.x + kx, px) - o.x, cy) out = Finish.over(out, cc_puck(p, kc, kr, o, px, vec2(0.0, -1.0), self.hover, self.drag * live, self.disabled)) return Finish.over(out, cc_focus(cc_rbox(p, c, h, h.y), px, self.focus * live)) } // --------------------------------------------------------------------------- // FADERS. A slot cut into the slab three points wide, dark, its lower (or // right) side lit; engraved ticks either side; the lit thread in the slot; // and a cast block riding in it, 22 points across and 12 along the travel, // with a line engraved across its middle. mod.widgets.SliderFader.cap_size = 12.0 mod.widgets.SliderFader.clip_x = false mod.widgets.SliderFader.clip_y = false mod.widgets.SliderFader.draw_bg.vertex = fn() { let g = cc_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 let sw = 3.0 let q = vec2(a, b) let sc = vec2(along * 0.5, mid) let sh = vec2(along * 0.5 - self.inset_px, sw * 0.5) let sd = cc_rbox(q, sc, sh, 1.0) var out = vec4(0.0, 0.0, 0.0, 0.0) let lip = Finish.ring_out(sd, 0.0, px, px) * step(sc.y, b) * 0.6 out = Finish.over(out, vec4(0.88 * lip, 0.875 * lip, 0.86 * lip, lip)) let sa = Finish.cover(sd, px) let scol = mix(vec3(0.10, 0.10, 0.095), vec3(0.42, 0.418, 0.405), self.disabled) out = Finish.over(out, vec4(scol * sa, sa)) // the value: the lit thread in the slot, from the origin (or the foot) let va0 = foot + self.fill_lo * travel let va1 = foot + self.fill_hi * travel let vl = step(va0, a) * step(a, va1) * (1.0 - smoothstep(0.5 - px * 0.5, 0.5 + px * 0.5, abs(b - mid))) * sa * live out = Finish.over(out, vec4(0.435 * vl, 0.682 * vl, 0.533 * vl, vl)) // ticks: a long one every quarter of the travel, three short ones // between, two points off the slot on both sides let nt = 16.0 let f = (a - foot) / travel * nt let fi = floor(f + 0.5) let da = abs(a - (foot + fi / nt * travel)) let major = 1.0 - step(0.1, abs(fi * 0.25 - floor(fi * 0.25 + 0.5))) let tl = mix(3.0, 5.0, major) let off = abs(b - mid) - sw * 0.5 - 2.0 let in_rng = step(-0.5, fi) * step(fi, nt + 0.5) let tk = (1.0 - smoothstep(px * 0.5 - 0.01, px * 0.5 + 0.01, da)) * step(0.0, off) * step(off, tl) * in_rng * 0.7 * mix(1.0, 0.5, self.disabled) out = Finish.over(out, vec4(0.07 * tk, 0.07 * tk, 0.065 * tk, tk)) // the block let at = foot + self.slide_pos * travel let cw = min(22.0, across) let cs = mix(vec2(at, mid), vec2(mid, size.y - at), vert) let ch = mix(vec2(self.cap_px * 0.5, cw * 0.5), vec2(cw * 0.5, self.cap_px * 0.5), vert) let cc = vec2(Finish.snap(o.x + cs.x, px) - o.x, Finish.snap(o.y + cs.y, px) - o.y) let cd = cc_rbox(p, cc, ch, 1.5) let press = self.drag * live out = Finish.over(out, cc_cast(p, cc, ch, 1.5, (1.0 - press * 0.6) * mix(1.0, 0.35, self.disabled), 1.0)) out = Finish.over(out, cc_cap(p, cc, ch, vec4(1.5, 1.5, 1.5, 1.5), o, px, max(self.hover, self.focus * 0.5) * live, press * 0.5, self.disabled, vec4(0.0, 0.0, 0.0, 0.0))) // the line engraved across the block's middle let ly = Finish.snap(o.y + cc.y, px) - o.y let lx = Finish.snap(o.x + cc.x, px) - o.x let lin_h = step(ly - px, p.y) * step(p.y, ly) * step(abs(p.x - cc.x), ch.x - 1.5) let lit_h = step(ly, p.y) * step(p.y, ly + px) * step(abs(p.x - cc.x), ch.x - 1.5) let lin_v = step(lx - px, p.x) * step(p.x, lx) * step(abs(p.y - cc.y), ch.y - 1.5) let lit_v = step(lx, p.x) * step(p.x, lx + px) * step(abs(p.y - cc.y), ch.y - 1.5) let dk = mix(lin_h, lin_v, 1.0 - vert) * 0.8 let lt = mix(lit_h, lit_v, 1.0 - vert) * 0.55 out = Finish.over(out, vec4(0.05 * dk, 0.05 * dk, 0.045 * dk, dk)) out = Finish.over(out, vec4(0.9 * lt, 0.895 * lt, 0.88 * lt, lt)) return Finish.over(out, cc_focus(cd, px, self.focus * live)) } // --------------------------------------------------------------------------- // KNOBS. A concrete cylinder standing in a round pit: the black gap ring // round its foot, its soft shadow, a top lit from above with the material's // pores, a darker wall band at its foot and a dark index line. Round it a // groove cut in the slab carries the lit verdigris value. let cc_knob = fn(p: vec2, c: vec2, rad: float, o: vec2, px: float, ang: float, hover: float, press: float, dis: float) -> vec4 { let dia = rad * 2.0 let live = 1.0 - dis let q = p - c let d = length(q) - rad // the pit: a black ring round the foot, fading out over two points let pit = (1.0 - smoothstep(0.0, max(dia * 0.07, 1.6), d)) * step(0.0, d) * mix(0.85, 0.4, dis) var out = vec4(0.03 * pit, 0.03 * pit, 0.028 * pit, pit) out = Finish.over(out, cc_cast(p, c, vec2(rad, rad), rad, (1.0 - press * 0.5) * mix(1.0, 0.4, dis), 0.6)) let t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0) var base = mix(vec3(0.76, 0.755, 0.735), vec3(0.54, 0.536, 0.52), clamp((t - 0.05) / 0.9, 0.0, 1.0)) var face = cc_stone(base, p + o + vec2(51.0, 3.0), px, 0.9, 0.7) let wall = Finish.band(d, 0.0, dia * 0.08, px) face = mix(face, face * mix(1.05, 0.55, t), wall) face = Finish.lift(face, 0.10 * hover * live * (1.0 - press) - 0.06 * press) face = mix(face, vec3(0.62, 0.617, 0.60), dis * 0.45) // the index: a dark line cut from near the centre to the rim, a light // line under it let dir = vec2(-sin(ang), cos(ang)) let a0 = c + dir * rad * 0.30 let a1 = c + dir * rad * 0.82 let lw = max(dia * 0.025, 0.6) let id = cc_seg(p, a0, a1) - lw let il = cc_seg(p - vec2(0.0, px), a0, a1) - lw face = mix(face, vec3(0.88, 0.875, 0.86), Finish.cover(il, px) * 0.45 * live) face = mix(face, vec3(0.10, 0.10, 0.095), Finish.cover(id, px) * mix(0.92, 0.5, dis)) let up = smoothstep(-0.2, -0.6, q.y / rad) face = mix(face, vec3(0.93, 0.925, 0.91), Finish.band(d, px, 2.0 * px, px) * up * 0.8 * live) let olc = mix(vec3(0.30, 0.297, 0.285), vec3(0.08, 0.08, 0.075), smoothstep(0.15, 0.85, t)) face = mix(face, olc, Finish.band(d, 0.0, px, px) * mix(1.0, 0.5, dis)) let a = Finish.cover(d, px) return Finish.over(out, vec4(face * a, a)) } // The value groove: `ar` its middle radius, `aw` its width; the gap is // centred at the bottom and the value runs clockwise from its left end, or // from the origin when the knob is bipolar. let cc_arc = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, v0: float, v1: float, gap: float, dis: float) -> vec4 { let start = gap * 0.5 let sweep = 2.0 * PI - gap let q = p - c var ang = atan2(-q.x, q.y) if ang < 0.0 { ang = ang + 2.0 * PI } let rd = abs(length(q) - ar) - aw * 0.5 let in_arc = step(start, ang) * step(ang, start + sweep) let e0 = c + vec2(-sin(start), cos(start)) * ar let e1 = c + vec2(-sin(start + sweep), cos(start + sweep)) * ar let track_d = min(mix(1000.0, rd, in_arc), min(length(p - e0), length(p - e1)) - aw * 0.5) let a0 = start + sweep * v0 let a1 = start + sweep * v1 let l0 = c + vec2(-sin(a0), cos(a0)) * ar let l1 = c + vec2(-sin(a1), cos(a1)) * ar let lit_in = step(a0, ang) * step(ang, a1) let lit_d = min(mix(1000.0, rd, lit_in), min(length(p - l0), length(p - l1)) - aw * 0.5 + 0.5) // the groove: dark, a light lip on its outer lower side let ta = Finish.cover(track_d, px) * mix(0.62, 0.35, dis) var out = vec4(0.06 * ta, 0.06 * ta, 0.055 * ta, ta) let lt = Finish.ring_out(track_d, 0.0, px, px) * step(0.0, q.y) * 0.45 * step(ar, length(q)) out = Finish.over(out, vec4(0.88 * lt, 0.875 * lt, 0.86 * lt, lt)) // the lit thread in it, pale at its core let k = clamp(abs(length(q) - ar) / max(aw * 0.5 - 0.5, 0.3), 0.0, 1.0) let la = Finish.cover(lit_d, px) * (1.0 - dis) let ink = cc_lit(k * k) return Finish.over(out, vec4(ink * la, la)) } mod.widgets.RotaryKnob.clip_x = false mod.widgets.RotaryKnob.clip_y = false mod.widgets.RotaryKnob.draw_bg.vertex = fn() { let g = cc_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 = 3.0 let ar = floor((rr - aw * 0.5) / px) * px let rad = floor(ar / 1.26 / 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 = cc_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, cc_knob(p, c, rad, o, px, ang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled)) return Finish.over(out, cc_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live)) } // Knob captions are stamped hardware words: small bold capitals. mod.widgets.RotaryKnob.draw_text.color = #262523 mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_bold{font_size: 7.1} mod.widgets.RotaryFlat.clip_x = false mod.widgets.RotaryFlat.clip_y = false mod.widgets.RotaryFlat.draw_bg.vertex = fn() { let g = cc_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 aw = 3.5 let ar = floor((rr - aw * 0.5) / px) * px let rad = floor(ar / 1.2 / 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 = cc_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, cc_knob(p, c, rad, o, px, ang, max(self.hover, self.focus * 0.5), self.drag * live, self.disabled)) return Finish.over(out, cc_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live)) } // The dial's readout: a small dark window cut into the slab, pale digits in // it. mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 2.0 bottom: 2.0} mod.widgets.RotaryFlat.text_input.min_height = 20.0 mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let size = self.rect_size 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) return cc_pocket(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 2.0, o, px, vec3(0.24, 0.238, 0.23), 0.85, 1.0) } mod.widgets.RotaryFlat.text_input.draw_text.color = #e4e3dd mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #ecebe5 mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #ecebe5 mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #b8b7b1 mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_label{font_size: 9.25} // --------------------------------------------------------------------------- // TEXT FIELDS. A pocket in the slab: its floor a shade under the slab, a // shade falling in under the top edge, a black line inside the top, a light // lip under it. A field clips its own text, so its seat and lip are drawn in // its own first and last device rows, and the focus line lies inside the rim. let cc_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, focus: float, dis: float, r: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + 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 d = cc_rbox(p, c, h, r) let fill = mix(Finish.lift(vec3(0.27, 0.268, 0.26), 0.16 * hover + 0.06 * focus), vec3(0.42, 0.418, 0.405), dis) var out = cc_pocket(p, c, h, r, o, px, fill, mix(0.85, 0.35, dis), mix(1.0, 0.5, dis)) let fl = Finish.band(d, px, 2.5 * px, px) * focus * (1.0 - dis) return Finish.over(out, vec4(0.184 * fl, 0.490 * fl, 0.384 * fl, fl)) } mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0} mod.widgets.TextInputFlat.draw_bg.pixel = fn() { // The value field inside a slider or a well draws no box of its own. if self.color.a < 0.01 { return vec4(0.0, 0.0, 0.0, 0.0) } let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cc_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.0) } // A field is a dark pocket, so its words are pale, as the picture has them. mod.widgets.TextInputFlat.draw_text.color = #e6e5df mod.widgets.TextInputFlat.draw_text.color_hover = #ecebe5 mod.widgets.TextInputFlat.draw_text.color_focus = #f2f1eb mod.widgets.TextInputFlat.draw_text.color_down = #ecebe5 mod.widgets.TextInputFlat.draw_text.color_empty = #aeada7 mod.widgets.TextInputFlat.draw_text.color_empty_hover = #b8b7b1 mod.widgets.TextInputFlat.draw_text.color_empty_focus = #b8b7b1 mod.widgets.TextInputFlat.draw_text.color_disabled = #9a9994 mod.widgets.TextInputFlat.draw_cursor.color = #f2f1eb mod.widgets.NumberField.input.draw_text.color = #e6e5df mod.widgets.NumberField.input.draw_text.color_hover = #ecebe5 mod.widgets.NumberField.input.draw_text.color_focus = #f2f1eb mod.widgets.NumberField.input.draw_text.color_empty = #aeada7 mod.widgets.NumberField.input.draw_cursor.color = #f2f1eb mod.widgets.ComboBox.input.draw_text.color = #e6e5df mod.widgets.ComboBox.input.draw_text.color_hover = #ecebe5 mod.widgets.ComboBox.input.draw_text.color_focus = #f2f1eb mod.widgets.ComboBox.input.draw_text.color_empty = #aeada7 mod.widgets.ComboBox.input.draw_cursor.color = #f2f1eb mod.widgets.ValueInput.text_input.draw_text.color = #e6e5df // NUMBER FIELDS. The pocket, and on its right a 14 point column of two small // cast blocks split by a joint, each with a stamped triangle; the half under // the pointer lifts, the held half is pressed in. mod.widgets.NumberField.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cc_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 2.0) } mod.widgets.NumberField.pad_left = 8.0 mod.widgets.NumberSpin.width = 14.0 mod.widgets.NumberSpin.draw_text.color = #00000000 mod.widgets.NumberSpin.draw_text.color_disabled = #00000000 let cc_tri = fn(p: vec2, c: vec2, hw: float, hh: float, dir: float) -> float { // a triangle hw wide either side, 2 hh tall, apex up (dir -1) or down (1) let y = (p.y - c.y) * dir let e = (abs(p.x - c.x) * 2.0 * hh + hw * (y - hh)) / sqrt(4.0 * hh * hh + hw * hw) return max(-hh - y, e) } mod.widgets.NumberSpin.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let live = 1.0 - self.disabled let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.0, 2.0 * px) let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o - vec2(2.0 * px, 3.0 * px) let mid = Finish.snap(o.y + (lo.y + hi.y) * 0.5, px) - o.y let upper = step(p.y, mid) let hot = mix(max(0.0, -self.hot), max(0.0, self.hot), upper) * live let dn = mix(max(0.0, -self.down), max(0.0, self.down), upper) * live let hc = vec2((lo.x + hi.x) * 0.5, mix((mid + px + hi.y) * 0.5, (lo.y + mid) * 0.5, upper)) let hh = vec2((hi.x - lo.x) * 0.5, mix((hi.y - mid - px) * 0.5, (mid - lo.y) * 0.5, upper)) var out = cc_cap(p, hc, hh, vec4(1.0, 1.0, 1.0, 1.0), o, px, hot, dn, self.disabled, vec4(0.0, 0.0, 0.0, 0.0)) let cx = (lo.x + hi.x) * 0.5 let tu = cc_tri(p, vec2(cx, (lo.y + mid) * 0.5 + dn * px * upper), 3.0, 1.6, -1.0) let td = cc_tri(p, vec2(cx, (mid + hi.y) * 0.5 + dn * px * (1.0 - upper)), 3.0, 1.6, 1.0) let tl = Finish.cover(min(cc_tri(p - vec2(0.0, px), vec2(cx, (lo.y + mid) * 0.5), 3.0, 1.6, -1.0), cc_tri(p - vec2(0.0, px), vec2(cx, (mid + hi.y) * 0.5), 3.0, 1.6, 1.0)), px) * 0.5 * live out = Finish.over(out, vec4(0.88 * tl, 0.875 * tl, 0.86 * tl, tl)) let ti = Finish.cover(min(tu, td), px) let ink = mix(vec3(0.12, 0.12, 0.115), vec3(0.42, 0.418, 0.405), self.disabled) return Finish.over(out, vec4(ink * ti, ti)) } // The drag-number: the pocket, its track a pale wash behind dark digits. mod.widgets.ValueInput.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size var out = cc_field(p, self.rect_size, self.rect_pos, px, max(self.hover, self.drag), self.focus, 0.0, 2.0) let d = cc_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(2.0 * px, 2.0 * px), 2.0) let wx = 2.0 * px + (self.rect_size.x - 4.0 * px) * max(self.fill, 0.0) let wash = step(0.0, self.fill) * step(p.x, wx) * Finish.cover(d, px) * 0.14 return Finish.over(out, vec4(0.435 * wash, 0.682 * wash, 0.533 * wash, wash)) } mod.widgets.ValueInput.draw_text.color = #e6e5df mod.widgets.ValueInput.draw_text.text_style = mod.theme.font_label{font_size: 8.5 line_spacing: 1.0} mod.widgets.ValueInput.arrow_color = #c8c7c1 mod.widgets.ValueInput.text_input.draw_bg.color = #00000000 // --------------------------------------------------------------------------- // DROP-DOWNS. The cast block with a 22 point arrow cell on the right, set off // by an engraved joint; the arrow is a stamped triangle. Open, the block is // held pressed. // `ins` 1: the drop-down stands in the field row, whose box is two points // taller than a block for the lip a pocket keeps inside; its block takes the // pocket's rows, so it stands as tall as the fields and the buttons beside it. let cc_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, px * ins) let size = size0 - vec2(0.0, 3.0 * px * ins) let o = o0 + vec2(0.0, px * ins) let pos = (p + vec2(m, m)) / (size + vec2(2.0 * m, 2.0 * m)) var out = cc_button(pos, size, o, px, hover, held, focus, dis, 0.0, 0.0, vec4(2.5, 2.5, 2.5, 2.5), 0.0, m, vec4(0.0, 0.0, 0.0, 0.0)) let live = 1.0 - dis let dx = Finish.snap(o.x + size.x - 22.0, px) - o.x let in_y = step(size.y * 0.18, p.y) * step(p.y, size.y * 0.82) let div_d = step(dx - px, p.x) * step(p.x, dx) * in_y * mix(0.8, 0.4, dis) let div_l = step(dx, p.x) * step(p.x, dx + px) * in_y * 0.6 out = Finish.over(out, vec4(0.06 * div_d, 0.06 * div_d, 0.055 * div_d, div_d)) out = Finish.over(out, vec4(0.88 * div_l, 0.875 * div_l, 0.86 * div_l, div_l)) let dn = held * live * px let cc = vec2(Finish.snap(o.x + size.x - 11.0, px) - o.x, size.y * 0.5 + dn) let lit = Finish.cover(cc_tri(p - vec2(0.0, px), cc, 3.5, 2.0, 1.0), px) * 0.5 * live out = Finish.over(out, vec4(0.88 * lit, 0.875 * lit, 0.86 * lit, lit)) let tri = Finish.cover(cc_tri(p, cc, 3.5, 2.0, 1.0), px) let ink = mix(vec3(0.12, 0.12, 0.115), vec3(0.42, 0.418, 0.405), dis) return Finish.over(out, vec4(ink * tri, tri)) } mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 8.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.DropDown.padding = mod.turtle.Inset{left: 8.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.DropDownFlat.clip_x = false mod.widgets.DropDownFlat.clip_y = false mod.widgets.DropDownFlat.draw_bg.vertex = fn() { let g = cc_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 cc_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, max(self.down, self.active), self.focus, self.disabled, 1.0) } // The select wears a button of its own, built before this sheet: it is given // the drop-down's face, its grown quad, and no clip, here by name. mod.widgets.Select.clip_x = false mod.widgets.Select.clip_y = false mod.widgets.MultiSelect.clip_x = false mod.widgets.MultiSelect.clip_y = false mod.widgets.Select.face.clip_x = false mod.widgets.Select.face.clip_y = false mod.widgets.MultiSelect.face.clip_x = false mod.widgets.MultiSelect.face.clip_y = false mod.widgets.Select.face.draw_bg.vertex = fn() { let g = cc_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 = cc_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 cc_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 cc_dropdown(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, 0.0) } mod.widgets.Select.face.padding.right = 30.0 mod.widgets.MultiSelect.face.padding.right = 30.0 // The combo box: the pocket one types into, with a small block for its // arrow set in at the right end. mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 8.0 right: 30.0 top: 4.0 bottom: 4.0} mod.widgets.ComboBox.input.min_height = 0.0 mod.widgets.ComboBox.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let size = self.rect_size let o = self.rect_pos let live = 1.0 - self.disabled var out = cc_field(p, size, o, px, self.hover, self.focus, self.disabled, 2.0) let lo = vec2(Finish.snap(o.x + size.x - 21.0, px), Finish.snap(o.y, px)) - o + vec2(0.0, 3.0 * px) let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(3.0 * px, 5.0 * px) let kh = (hi - lo) * 0.5 let kc = lo + kh let held = self.down * self.hover * live out = Finish.over(out, cc_cap(p, kc, kh, vec4(1.5, 1.5, 1.5, 1.5), o, px, self.hover * live, held, self.disabled, vec4(0.0, 0.0, 0.0, 0.0))) let cc = vec2(kc.x, kc.y + held * px) let lit = Finish.cover(cc_tri(p - vec2(0.0, px), cc, 3.5, 2.0, 1.0), px) * 0.5 * live out = Finish.over(out, vec4(0.88 * lit, 0.875 * lit, 0.86 * lit, lit)) let tri = Finish.cover(cc_tri(p, cc, 3.5, 2.0, 1.0), px) let ink = mix(vec3(0.12, 0.12, 0.115), vec3(0.42, 0.418, 0.405), self.disabled) return Finish.over(out, vec4(ink * tri, tri)) } // --------------------------------------------------------------------------- // SEGMENTS AND TABS. The strip is one cast block; the chosen segment is // pressed right in, a dark pocket from the strip's top to its foot with its // word pale, the neighbours' ends standing lit over it. let cc_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 var out = cc_cast(p, c, h, 2.5, 1.0, 1.0) return Finish.over(out, cc_cap(p, c, h, vec4(2.5, 2.5, 2.5, 2.5), o, px, 0.0, 0.0, 0.0, vec4(0.0, 0.0, 0.0, 0.0))) } // The chosen cell: a dark pocket filling the strip from one device pixel // under its top outline to one above its foot, square at its sides; the // ends of its neighbours stand over it as a lit edge and a dark joint. let cc_cell = fn(p: vec2, lo: vec2, hi: vec2, o: vec2, px: float) -> vec4 { let c = (lo + hi) * 0.5 let h = (hi - lo) * 0.5 let d = cc_rbox(p, c, h, 0.0) var col = cc_stone(vec3(0.29, 0.288, 0.278), p + o + vec2(71.0, 5.0), px, 0.5, 0.3) let top = exp(-max(p.y - lo.y, 0.0) / 2.2) let wl = 0.6 * exp(-max(p.x - lo.x, 0.0) / 1.4) let wr = 0.6 * exp(-max(hi.x - p.x, 0.0) / 1.4) let dk = 1.0 - (1.0 - top * 0.85) * (1.0 - wl) * (1.0 - wr) col = mix(col, vec3(0.02, 0.02, 0.018), dk) let a = Finish.cover(d, px) var out = vec4(col * a, a) let ey = step(lo.y + px, p.y) * (1.0 - step(hi.y - px, p.y)) let el = step(lo.x - px, p.x) * (1.0 - step(lo.x, p.x)) let er = step(hi.x, p.x) * (1.0 - step(hi.x + px, p.x)) let jn = (el + er) * ey return Finish.over(out, vec4(0.06 * jn, 0.06 * jn, 0.055 * jn, jn)) } mod.widgets.SegmentedControlFlat.segment_height = 22.0 mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 6.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 = 6.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return cc_strip(p, self.rect_size, self.rect_pos, px) } // The pill is the segment's own box, two points in from the strip on every // side: its quad grows two points up and down so the cell reaches the // strip's outline. mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() { self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(1.0, 2.0), self.rect_size + vec2(2.0, 4.0)) } mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let o = self.rect_pos let size = self.rect_size let p = self.pos * (size + vec2(2.0, 4.0)) - vec2(1.0, 2.0) let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y - 2.0, px) + px) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y + 2.0, px) - px) - o return cc_cell(p, lo, hi, o, px) } mod.widgets.SegmentedControlFlat.draw_text.color = #161615 mod.widgets.SegmentedControlFlat.draw_text_selected.color = #ecebe5 // The chosen word takes its ink from the group's palette: pale, for the pocket. mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #ecebe5 mod.widgets.SegmentedControl.palette.on_neutral_container = #ecebe5 // The tab strip: each tab a block of its own, joined to the next by an // engraved joint; the tab in use is pressed right in, dark, its word pale. mod.widgets.Tabs.height = 26.0 mod.widgets.Tabs.clip_x = false mod.widgets.Tabs.clip_y = false let cc_tab = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, active: float) -> vec4 { let m = 6.0 let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) 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 var out = cc_cast(p, c, h, 2.5, 1.0 - active, 1.0 - active) out = Finish.over(out, cc_cap(p, c, h, vec4(2.5, 2.5, 2.5, 2.5), o, px, hover, 0.0, 0.0, vec4(0.0, 0.0, 0.0, 0.0)) * (1.0 - active)) out = Finish.over(out, cc_deep(p, c, h, 2.5, o, px, hover) * active) return out } mod.widgets.Tabs.draw_tab.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_tab_hover.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_tab_active.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_bg.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_tab.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cc_tab(self.pos, self.rect_size, self.rect_pos, px, 0.0, 0.0) } mod.widgets.Tabs.draw_tab_hover.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cc_tab(self.pos, self.rect_size, self.rect_pos, px, 1.0, 0.0) } mod.widgets.Tabs.draw_tab_active.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return cc_tab(self.pos, self.rect_size, self.rect_pos, px, 0.0, 1.0) } mod.widgets.Tabs.draw_sep.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_mark.color = #161615 mod.widgets.Tabs.draw_mark.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let c = vec2(Finish.snap(o.x + self.rect_size.x * 0.5, px), Finish.snap(o.y + self.rect_size.y * 0.5, px)) - o // 7 points across, a 1.2 point stroke; the plus is the cross turned let a = mix(0.7853981634, 0.0, self.plus) let r = mix(3.4, 4.5, self.plus) let u = vec2(cos(a), sin(a)) * r let v = vec2(-sin(a), cos(a)) * r let dd = min(cc_seg(p, c - u, c + u), cc_seg(p, c - v, c + v)) - 0.6 let dl = min(cc_seg(p - vec2(0.0, px), c - u, c + u), cc_seg(p - vec2(0.0, px), c - v, c + v)) - 0.6 let l = Finish.cover(dl, px) * 0.5 * self.color.a let k = Finish.cover(dd, px) * self.color.a return Finish.over(vec4(0.88 * l, 0.875 * l, 0.86 * l, l), vec4(self.color.rgb * k, k)) } mod.widgets.Tabs.draw_text.color = #161615 mod.widgets.Tabs.draw_text_active.color = #ecebe5 // The strip centres a 14 point line box, which leaves the capitals high in // a 26 point block: they are dropped to the block's middle. mod.widgets.Tabs.draw_text.text_style.top_drop = 0.22 mod.widgets.Tabs.draw_text_active.text_style.top_drop = 0.22 // --------------------------------------------------------------------------- // PROGRESS. An 8 point channel cut into the slab, dark; the value the lit // verdigris bar in it, a pale core along its middle, its glow on the // channel's floor. The unknown value is a still hatch of lit bars across the // whole channel: a face may not run a clock. mod.widgets.ProgressBarFlat.height = 8.0 mod.widgets.ProgressBarFlat.draw_bg.thickness = uniform(8.0) mod.widgets.ProgressBarFlat.clip_x = false mod.widgets.ProgressBarFlat.clip_y = false mod.widgets.ProgressBarFlat.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 2.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 2.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let th = min(self.thickness, size.y) let top = Finish.snap(o.y + (size.y - th) * 0.5, px) - o.y let bot = Finish.snap(o.y + (size.y + th) * 0.5, px) - o.y let x0 = Finish.snap(o.x + size.x * self.start, px) - o.x let x1 = Finish.snap(o.x + size.x * self.end, px) - o.x let h = vec2((x1 - x0) * 0.5, (bot - top) * 0.5) let c = vec2(x0, top) + h let r = 2.0 let dis = step(4.5, self.intent) var out = cc_pocket(p, c, h, r, o, px, mix(vec3(0.22, 0.218, 0.21), vec3(0.45, 0.447, 0.435), dis), 0.8, 1.0) * self.track var v0 = x0 + 1.5 var v1 = x0 + 1.5 + (x1 - x0 - 3.0) * clamp(self.value, 0.0, 1.0) var hatch = 1.0 if self.value < 0.0 { v1 = x1 - 1.5 hatch = Finish.stripes(p + o, 0.785398, 7.0, 0.35, px) * 0.7 } let fh = vec2(max((v1 - v0) * 0.5, 0.0), max(h.y - 1.5, 0.8)) let fc = vec2((v0 + v1) * 0.5, c.y + 0.3) let fd = cc_rbox(p, fc, fh, 1.0) let k = clamp(abs(p.y - fc.y) / fh.y, 0.0, 1.0) var ink = cc_lit(k * k) var body = vec3(0.435, 0.682, 0.533) if self.intent > 0.5 { body = self.fill_color().rgb ink = mix(Finish.lift(body, 0.55), body, k * k) } ink = mix(ink, vec3(0.55, 0.547, 0.535), dis) let fa = Finish.cover(fd, px) * step(0.01, v1 - v0) * hatch let d = cc_rbox(p, c, h, r) let ga = exp(-max(fd, 0.0) / 1.4) * 0.35 * Finish.cover(d, px) * (1.0 - fa) * step(0.01, v1 - v0) * (1.0 - dis) * mix(0.4, 1.0, hatch) out = Finish.over(out, vec4(body * ga, ga)) out = Finish.over(out, vec4(ink * fa, fa)) return out * self.opacity } // --------------------------------------------------------------------------- // LEVEL METERS. A 6 point channel of cells 4 points long with a point // between: unlit cells dark glass, lit ones verdigris, then amber from 70 % // and red from 90 %, a pale line along their middle. The hold is a pale // hairline; the over lamp a red inlay, smoked glass until it latches. mod.widgets.LevelMeter.height = 8.0 mod.widgets.LevelMeterColumn.width = 8.0 mod.widgets.LevelMeter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let vert = self.vertical let along = mix(self.rect_size.x, self.rect_size.y, vert) let across = mix(self.rect_size.y, self.rect_size.x, vert) let a = mix(p.x, self.rect_size.y - p.y, vert) let b = mix(p.y, p.x, vert) let q = vec2(a, b) let th = min(6.0, across - 2.0) let lamp_room = self.lamp * (th + 4.0) let bar = max(4.0, along - lamp_room) let h = vec2(bar * 0.5, th * 0.5) let c = vec2(h.x, across * 0.5) let r = 1.5 let d = cc_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) * step(abs(b - c.y), h.y - 1.2) let lit = step(a, pad + span * self.level) * step(pad, a) * in_cell let ramp = mix(mix(vec3(0.435, 0.682, 0.533), vec3(0.918, 0.690, 0.235), step(0.7, share)), vec3(0.918, 0.278, 0.200), step(0.9, share)) let kk = clamp(abs(b - c.y) / max(h.y - 1.2, 0.5), 0.0, 1.0) let cellc = mix(mix(vec3(0.95, 0.98, 0.94), ramp, smoothstep(0.0, 0.8, kk)), vec3(0.19, 0.195, 0.19), 1.0 - lit) var out = cc_pocket(q, c, h, r, o, px, vec3(0.16, 0.16, 0.155), 0.6, 1.0) let unlit = in_cell * step(pad, a) * step(a, pad + span) * (1.0 - lit) * 0.6 out = Finish.over(out, vec4(0.21 * unlit, 0.215 * unlit, 0.21 * unlit, unlit)) out = Finish.over(out, vec4(cellc * lit, lit)) 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 - 1.0) out = Finish.over(out, vec4(0.85 * hm, 0.85 * hm, 0.82 * hm, hm)) } 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, cc_lamp(p, lc, 2.5, px, vec3(0.878, 0.216, 0.157), vec3(1.0, 0.78, 0.70), step(0.5, self.over))) } return out * self.opacity } // --------------------------------------------------------------------------- // SCROLL BARS. A 10 point groove the length of the view, and an 8 point cast // block riding in it with three engraved grip lines across its middle. mod.widgets.ScrollBar.bar_size = 10.0 mod.widgets.ScrollBar.bar_side_margin = 0.0 mod.widgets.ScrollBar.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let vert = self.is_vertical let a = mix(p.x, p.y, vert) let b = mix(p.y, p.x, vert) let along = mix(self.rect_size.x, self.rect_size.y, vert) let across = mix(self.rect_size.y, self.rect_size.x, vert) let q = vec2(a, b) let sh = vec2(along * 0.5, across * 0.5 - px) let sc = vec2(along * 0.5, across * 0.5) var out = cc_pocket(q, sc, sh, 2.0, o, px, vec3(0.44, 0.437, 0.425), 0.55, 0.0) // the block let t0 = along * self.norm_scroll let tl = along * self.norm_handle let th = vec2(max(tl * 0.5 - 1.5, 4.0), 3.5) let tc = vec2(t0 + tl * 0.5, sc.y) out = Finish.over(out, cc_cap(q, tc, th, vec4(1.5, 1.5, 1.5, 1.5), o, px, self.hover, self.drag * 0.6, 0.0, vec4(0.0, 0.0, 0.0, 0.0))) let fi = clamp(floor((a - tc.x) / 2.0 + 0.5), -1.0, 1.0) let rx = floor((tc.x + fi * 2.0) / px) * px let inb = step(abs(b - tc.y), 2.0) let dk = step(rx - px, a) * step(a, rx) * inb * 0.7 let lt = step(rx, a) * step(a, rx + px) * inb * 0.5 out = Finish.over(out, vec4(0.05 * dk, 0.05 * dk, 0.045 * dk, dk)) out = Finish.over(out, vec4(0.9 * lt, 0.895 * lt, 0.88 * lt, lt)) return out * self.opacity } // --------------------------------------------------------------------------- // VIEWS AND PANELS. A raised slab: a cast block at a larger scale with a // deeper soft shadow. The sunken panel is a broad pocket, its floor kept // pale enough for the stamped ink. let cc_slab = 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 if sunken > 0.5 { return cc_pocket(p, c, h, r, o, px, vec3(0.537, 0.533, 0.518), 0.55, 1.0) } let s1 = Finish.drop(p, c, h, r, vec2(0.0, 3.4), 2.8) * 0.70 let s2 = Finish.drop(p, c, h, r, vec2(0.0, 1.0), 0.9) * 0.60 let sa = s1 + s2 * (1.0 - s1) var out = vec4(0.05 * sa, 0.05 * sa, 0.045 * sa, sa) let d = cc_rbox(p, c, h, r) let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0) let e = 0.5 let g = vec2(cc_rbox(p + vec2(e, 0.0), c, h, r) - cc_rbox(p - vec2(e, 0.0), c, h, r), cc_rbox(p + vec2(0.0, e), c, h, r) - cc_rbox(p - vec2(0.0, e), c, h, r)) let gn = g / max(length(g), 0.0001) var face = cc_stone(mix(vec3(0.650, 0.646, 0.627), vec3(0.604, 0.600, 0.580), t), p + o + vec2(19.0, 83.0), px, 1.0, 0.9) face = Finish.lift(face, Finish.bevel(d, gn, 3.2, vec2(0.0, -1.0)) * Finish.cover(d, px) * 0.42) face = mix(face, face * 0.8, smoothstep(2.0 * h.y - 4.0, 2.0 * h.y, p.y - lo.y)) let up = 1.0 - smoothstep(-0.5, 0.5, p.y - (lo.y + r)) face = mix(face, vec3(0.90, 0.895, 0.88), Finish.band(d, px, 2.0 * px, px) * up * 0.7) face = mix(face, mix(vec3(0.36, 0.355, 0.34), vec3(0.12, 0.12, 0.115), smoothstep(0.1, 0.9, t)), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) return Finish.over(out, vec4(face * a, a)) } mod.widgets.PanelView.clip_x = false mod.widgets.PanelView.clip_y = false mod.widgets.PanelView.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 8.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.PanelView.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 8.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return cc_slab(p, self.rect_size, self.rect_pos, px, 3.0, self.sunken) } // The card is the raised slab too; its palette and shadow numbers from Rust // are not read. It lifts a little under the pointer. mod.widgets.Card.radius = 3.0 mod.widgets.Card.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 8.0) return self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Card.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 8.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) var out = cc_slab(p, self.rect_size, self.rect_pos, px, 3.0, 0.0) let d = cc_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5 - vec2(px, px), 3.0) let l = Finish.cover(d, px) * (0.08 * self.hover - 0.04 * self.down) return Finish.over(out, vec4(max(l, 0.0), max(l, 0.0), max(l, 0.0), abs(l))) } // The plain rounded view keeps the colours its caller gives it; the edge is // a snapped hairline at the true radius. mod.widgets.RoundedView.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size var fill = self.color if self.color_2.x > -0.5 { fill = mix(self.color, self.color_2, mix(self.pos.y, self.pos.x, self.gradient_fill_horizontal)) } let lo = vec2(self.border_inset.x, self.border_inset.y) let hi = self.rect_size - vec2(self.border_inset.z, self.border_inset.w) let h = (hi - lo) * 0.5 let c = lo + h let d = cc_rbox(p, c, h, max(self.border_radius, 1.0)) var col = fill if self.border_size > 0.0 { col = mix(col, self.border_color, Finish.band(d, 0.0, max(self.border_size, px), px) * self.border_color.a) } let a = Finish.cover(d, px) * col.a return vec4(col.rgb * a, a) } // --------------------------------------------------------------------------- // LISTS. Rows of 22 points on the slab, split by a faint engraved line; a // hovered row darkens a shade; the chosen row is pressed into the slab, a // pocket with a shade under its top edge, ending short of the scroll bar. mod.widgets.ListItemOne.height = 22.0 mod.widgets.ListItem.padding = mod.turtle.Inset{left: 10.0 right: 8.0 top: 0.0 bottom: 0.0} mod.widgets.ListItem.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let live = 1.0 - self.disabled let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o let lay = (self.hover * 0.06 + self.down * 0.08) * live var out = vec4(0.05 * lay, 0.05 * lay, 0.045 * lay, lay) if self.selected > 0.5 { let h = vec2((hi.x - lo.x) * 0.5 - 6.0, (hi.y - lo.y) * 0.5 - 2.0 * px) let c = vec2(lo.x + h.x + 1.0, (lo.y + hi.y) * 0.5 - 0.5 * px) out = Finish.over(out, cc_pocket(p, c, h, 2.0, o, px, vec3(0.537, 0.533, 0.518), 0.5, 1.0)) } let yb = hi.y let dk = step(yb - px, p.y) * step(p.y, yb) * 0.12 * (1.0 - self.selected) out = Finish.over(out, vec4(0.05 * dk, 0.05 * dk, 0.045 * dk, dk)) return out } mod.widgets.ListItem.draw_text.color = #161615 // The open list of a drop-down: a raised slab; the row under the pointer a // shallow pocket, the chosen row a stamped check. mod.widgets.PopupMenuFlat.clip_x = false mod.widgets.PopupMenuFlat.clip_y = false mod.widgets.PopupMenuFlat.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 8.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 = 8.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return cc_slab(p, self.rect_size, self.rect_pos, px, 3.0, 0.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 o = self.rect_pos let c = self.rect_size * 0.5 let h = c - vec2(2.0, 1.0) let live = 1.0 - self.disabled var out = cc_pocket(p, c, h, 2.0, o, px, vec3(0.537, 0.533, 0.518), 0.45, 0.0) * self.hover * live let m0 = vec2(6.0, c.y) let m1 = vec2(8.5, c.y + 2.5) let m2 = vec2(12.5, c.y - 3.0) let lit = Finish.cover(min(cc_seg(p - vec2(0.0, px), m0, m1), cc_seg(p - vec2(0.0, px), m1, m2)) - 0.75, px) * self.active * 0.5 out = Finish.over(out, vec4(0.88 * lit, 0.875 * lit, 0.86 * lit, lit)) let mk = Finish.cover(min(cc_seg(p, m0, m1), cc_seg(p, m1, m2)) - 0.75, px) * self.active return Finish.over(out, vec4(0.086 * mk, 0.086 * mk, 0.082 * mk, mk)) } // The dock's tabs: the bar is the slab with an engraved line at its foot; // the tab in use is a block rounded at the top and open at the foot, where it // joins the panel below; the others lighten a little under the pointer. mod.widgets.Tab.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.Tab.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 2.0, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o + vec2(0.0, 6.0) let h = (hi - lo) * 0.5 let c = lo + h let cap = cc_cap(p, c, h, vec4(2.5, 2.5, 2.5, 2.5), o, px, 0.0, 0.0, 0.0, vec4(0.0, 0.0, 0.0, 0.0)) * self.active let d = cc_rbox(p, c, h, 2.5) let lift = Finish.cover(d, px) * 0.12 * self.hover * (1.0 - self.active) return Finish.over(vec4(lift, lift, lift, lift), cap) } mod.widgets.TabBar.draw_fill.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let yb = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.45 let lt = step(yb - px, p.y) * step(p.y, yb) * 0.55 var out = vec4(0.05 * dk, 0.05 * dk, 0.045 * dk, dk) return Finish.over(out, vec4(0.88 * lt, 0.875 * lt, 0.86 * lt, lt)) } // --------------------------------------------------------------------------- // CHIPS AND BADGES. A chip is a small cast block (pressed in while chosen); // a badge is a small dark pocket with pale digits. mod.widgets.ChipFlat.size = mod.widgets.ChipSize.Small mod.widgets.Chip.size = mod.widgets.ChipSize.Small mod.widgets.ChipFlat.draw_bg.vertex = fn() { let g = cc_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)) // A filled chip (light ink) is a block dyed its role's colour; a tonal // one the grey block faintly dyed; an outlined one the stamped frame; a // ghost a shallow pocket. let light_ink = step(0.6, Finish.lum(self.ink.rgb)) let has_fill = step(0.05, self.color.a) let kind = mix(mix(6.0, 5.0, step(0.01, self.border_size)), 0.0, has_fill) let tint = vec4(self.color.rgb, has_fill * mix(0.35, 1.0, light_ink)) return cc_button(pp, sz, self.rect_pos + vec2(0.0, dy), px, self.hover, self.down, self.keyed, 0.0, 0.0, 0.0, vec4(2.5, 2.5, 2.5, 2.5), kind, m, tint) } mod.widgets.BadgePalette.neutral = #3a3936 mod.widgets.BadgePalette.on_neutral = #ecebe5 mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_bold{font_size: 6.5} mod.widgets.Badge.draw_text.text_style = mod.theme.font_bold{font_size: 6.5} mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let bh = min(16.0, self.rect_size.y - 2.0) let y0 = Finish.snap(o.y + (self.rect_size.y - bh) * 0.5, px) - o.y let lo = vec2(Finish.snap(o.x + 1.0, px) - o.x, y0) let hi = vec2(Finish.snap(o.x + self.rect_size.x - 1.0, px) - o.x, Finish.snap(o.y + y0 + bh, px) - o.y) let h = (hi - lo) * 0.5 let c = lo + h let r = mix(2.0, h.y, step(self.rect_size.x, self.rect_size.y + 0.5) * step(self.rect_size.y, 12.5)) let fill = mix(vec3(0.24, 0.238, 0.23), self.color.rgb, step(0.06, length(self.color.rgb - vec3(0.227, 0.224, 0.212)))) return cc_pocket(p, c, h, r, o, px, fill, 0.6, 1.0) * self.color.a } // The spinner keeps its stock turning face (it runs on the pass clock, which // a face may not read), in the stamped ink with a two point stroke on a // visible track ring, so a still frame reads as a working indicator. mod.widgets.SpinnerFlat.draw_bg.color = #2a2927 mod.widgets.SpinnerFlat.draw_bg.stroke_width = 3.0 mod.widgets.SpinnerFlat.draw_bg.track_alpha = 0.25 // Rules between groups: engraved, one dark device row over one light one. mod.widgets.Hr.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y let dk = step(y - px, p.y) * (1.0 - step(y, p.y)) * 0.45 let lt = step(y, p.y) * (1.0 - step(y + px, p.y)) * 0.55 return Finish.over(vec4(0.05 * dk, 0.05 * dk, 0.045 * dk, dk), vec4(0.88 * lt, 0.875 * lt, 0.86 * lt, lt)) } mod.widgets.Vr.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x let dk = step(x - px, p.x) * (1.0 - step(x, p.x)) * 0.45 let lt = step(x, p.x) * (1.0 - step(x + px, p.x)) * 0.55 return Finish.over(vec4(0.05 * dk, 0.05 * dk, 0.045 * dk, dk), vec4(0.88 * lt, 0.875 * lt, 0.86 * lt, lt)) } // --------------------------------------------------------------------------- // STAMPED WORDS. Every word is pressed into the cast, as the lettering on the // slabs of the picture: the light comes from the upper left, so the wall of // the letter facing it lies in shade (a dark rim inside the stroke on its // upper left and a faint shade just outside it) and the far wall catches the // light (a pale line one and a half device pixels wide outside the stroke on // its lower right). // The glyph body keeps its ink, dark enough to read at 4.5:1 or more. // Two more samples of the glyph, one device pixel up-left and one down-right, // on the small (single channel) and the large (multi channel) atlas alike. // Pale ink (words in the dark pockets) keeps the shade and only a trace of // the light line, so the letters do not grow a halo. // The function is written once and handed to every label below. let cc_stamp = fn() { if self.texture_index > 0.5 && self.texture_index < 1.5 { return self.sample_text_pixel() } if self.texture_index > 2.5 { return self.sample_text_pixel() } let c = self.get_color() let dxt = length(dFdx(self.t)) let dyt = length(dFdy(self.t)) let d = dFdx(self.t.xy) + dFdy(self.t.xy) var a0 = 0.0 var au = 0.0 var ad = 0.0 if self.texture_index < 0.5 { let tex_size = self.grayscale_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let ht = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) a0 = self.sdf(scale, clamp(self.t.xy, self.t_min + ht, self.t_max - ht), c) au = self.sdf(scale, clamp(self.t.xy - 1.5 * d, self.t_min + ht, self.t_max - ht), c) ad = self.sdf(scale, clamp(self.t.xy + d, self.t_min + ht, self.t_max - ht), c) } else { let tex_size = self.msdf_texture.size() let scale = (dxt + dyt) * tex_size.x * 0.5 let ht = vec2(0.5 / tex_size.x, 0.5 / tex_size.y) a0 = self.msdf(scale, clamp(self.t.xy, self.t_min + ht, self.t_max - ht), c) au = self.msdf(scale, clamp(self.t.xy - 1.5 * d, self.t_min + ht, self.t_max - ht), c) ad = self.msdf(scale, clamp(self.t.xy + d, self.t_min + ht, self.t_max - ht), c) } let pale = step(0.5, dot(c.rgb, vec3(0.299, 0.587, 0.114))) let rim = a0 * (1.0 - au) let lit = au * (1.0 - a0) * mix(0.75, 0.1, pale) let shade = ad * (1.0 - a0) * (1.0 - au) * mix(0.38, 0.55, pale) // On a cap the face is light enough to let the floor of the letter come // up a little toward the stone (stamp_lift, set per template), while the // rim facing the light stays at the ink: the pressed-in profile of the // picture, a dark edge on the upper left and a lighter floor beside it. // Only the rest ink lifts: the darker hover and held inks (#0e0e0d) and // the faint disabled ink keep their own value, so a held cap, whose face // is darker, still reads at 4.5:1. let ink_l = dot(c.rgb, vec3(0.299, 0.587, 0.114)) let dark = smoothstep(0.066, 0.078, ink_l) * (1.0 - smoothstep(0.12, 0.3, ink_l)) let fl = vec3(1.0, 1.0, 0.97) * self.stamp_lift * dark * au let body = (c.rgb + fl) * (1.0 - 0.55 * rim * (1.0 - pale)) let res = vec4(max(body, vec3(0.0, 0.0, 0.0)) * a0 * c.a, a0 * c.a) let edge = vec4(vec3(0.93, 0.925, 0.9) * lit, lit + shade) * c.a return res + edge * (1.0 - res.a) } mod.widgets.Label.draw_text.pixel = cc_stamp mod.widgets.Label.draw_text.stamp_lift = uniform(0.0) mod.widgets.H1.draw_text.pixel = cc_stamp mod.widgets.H1.draw_text.stamp_lift = uniform(0.0) mod.widgets.H2.draw_text.pixel = cc_stamp mod.widgets.H2.draw_text.stamp_lift = uniform(0.0) mod.widgets.H3.draw_text.pixel = cc_stamp mod.widgets.H3.draw_text.stamp_lift = uniform(0.0) mod.widgets.H4.draw_text.pixel = cc_stamp mod.widgets.H4.draw_text.stamp_lift = uniform(0.0) mod.widgets.H5.draw_text.pixel = cc_stamp mod.widgets.H5.draw_text.stamp_lift = uniform(0.0) mod.widgets.H6.draw_text.pixel = cc_stamp mod.widgets.H6.draw_text.stamp_lift = uniform(0.0) mod.widgets.ButtonFlat.draw_text.pixel = cc_stamp mod.widgets.ButtonFlat.draw_text.stamp_lift = uniform(0.04) mod.widgets.Button.draw_text.pixel = cc_stamp mod.widgets.Button.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonFlatter.draw_text.pixel = cc_stamp mod.widgets.ButtonFlatter.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonPrimary.draw_text.pixel = cc_stamp mod.widgets.ButtonPrimary.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonSecondary.draw_text.pixel = cc_stamp mod.widgets.ButtonSecondary.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonTertiary.draw_text.pixel = cc_stamp mod.widgets.ButtonTertiary.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonDanger.draw_text.pixel = cc_stamp mod.widgets.ButtonDanger.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonOutline.draw_text.pixel = cc_stamp mod.widgets.ButtonOutline.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonXs.draw_text.pixel = cc_stamp mod.widgets.ButtonXs.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonSm.draw_text.pixel = cc_stamp mod.widgets.ButtonSm.draw_text.stamp_lift = uniform(0.04) mod.widgets.ButtonLg.draw_text.pixel = cc_stamp mod.widgets.ButtonLg.draw_text.stamp_lift = uniform(0.04) mod.widgets.CheckBoxFlat.draw_text.pixel = cc_stamp mod.widgets.CheckBoxFlat.draw_text.stamp_lift = uniform(0.0) mod.widgets.CheckBox.draw_text.pixel = cc_stamp mod.widgets.CheckBox.draw_text.stamp_lift = uniform(0.0) mod.widgets.ToggleFlat.draw_text.pixel = cc_stamp mod.widgets.ToggleFlat.draw_text.stamp_lift = uniform(0.0) mod.widgets.Toggle.draw_text.pixel = cc_stamp mod.widgets.Toggle.draw_text.stamp_lift = uniform(0.0) mod.widgets.RadioButtonFlat.draw_text.pixel = cc_stamp mod.widgets.RadioButtonFlat.draw_text.stamp_lift = uniform(0.0) mod.widgets.RadioButton.draw_text.pixel = cc_stamp mod.widgets.RadioButton.draw_text.stamp_lift = uniform(0.0) mod.widgets.RadioButtonTabFlat.draw_text.pixel = cc_stamp mod.widgets.RadioButtonTabFlat.draw_text.stamp_lift = uniform(0.04) mod.widgets.RadioButtonTab.draw_text.pixel = cc_stamp mod.widgets.RadioButtonTab.draw_text.stamp_lift = uniform(0.04) mod.widgets.SliderFlat.draw_text.pixel = cc_stamp mod.widgets.SliderFlat.draw_text.stamp_lift = uniform(0.0) mod.widgets.Slider.draw_text.pixel = cc_stamp mod.widgets.Slider.draw_text.stamp_lift = uniform(0.0) mod.widgets.SliderMinimal.draw_text.pixel = cc_stamp mod.widgets.SliderMinimal.draw_text.stamp_lift = uniform(0.0) mod.widgets.SliderMinimalFlat.draw_text.pixel = cc_stamp mod.widgets.SliderMinimalFlat.draw_text.stamp_lift = uniform(0.0) mod.widgets.SliderRoundFlat.draw_text.pixel = cc_stamp mod.widgets.SliderRoundFlat.draw_text.stamp_lift = uniform(0.0) mod.widgets.SliderRound.draw_text.pixel = cc_stamp mod.widgets.SliderRound.draw_text.stamp_lift = uniform(0.0) mod.widgets.RotaryFlat.draw_text.pixel = cc_stamp mod.widgets.RotaryFlat.draw_text.stamp_lift = uniform(0.0) mod.widgets.Rotary.draw_text.pixel = cc_stamp mod.widgets.Rotary.draw_text.stamp_lift = uniform(0.0) mod.widgets.RotaryKnob.draw_text.pixel = cc_stamp mod.widgets.RotaryKnob.draw_text.stamp_lift = uniform(0.0) mod.widgets.DropDownFlat.draw_text.pixel = cc_stamp mod.widgets.DropDownFlat.draw_text.stamp_lift = uniform(0.04) mod.widgets.DropDown.draw_text.pixel = cc_stamp mod.widgets.DropDown.draw_text.stamp_lift = uniform(0.04) mod.widgets.SegmentedControlFlat.draw_text.pixel = cc_stamp mod.widgets.SegmentedControlFlat.draw_text.stamp_lift = uniform(0.04) mod.widgets.SegmentedControlFlat.draw_text_selected.pixel = cc_stamp mod.widgets.SegmentedControlFlat.draw_text_selected.stamp_lift = uniform(0.0) mod.widgets.SegmentedControl.draw_text.pixel = cc_stamp mod.widgets.SegmentedControl.draw_text.stamp_lift = uniform(0.04) mod.widgets.SegmentedControl.draw_text_selected.pixel = cc_stamp mod.widgets.SegmentedControl.draw_text_selected.stamp_lift = uniform(0.0) mod.widgets.Tabs.draw_text.pixel = cc_stamp mod.widgets.Tabs.draw_text.stamp_lift = uniform(0.04) mod.widgets.Tabs.draw_text_active.pixel = cc_stamp mod.widgets.Tabs.draw_text_active.stamp_lift = uniform(0.0) mod.widgets.ListItem.draw_text.pixel = cc_stamp mod.widgets.ListItem.draw_text.stamp_lift = uniform(0.0) mod.widgets.ListItemOne.draw_text.pixel = cc_stamp mod.widgets.ListItemOne.draw_text.stamp_lift = uniform(0.0) mod.widgets.ChipFlat.draw_text.pixel = cc_stamp mod.widgets.ChipFlat.draw_text.stamp_lift = uniform(0.04) mod.widgets.Chip.draw_text.pixel = cc_stamp mod.widgets.Chip.draw_text.stamp_lift = uniform(0.04) // --------------------------------------------------------------------------- // 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 SLAB under everything: the window's own ground. The cast grey with its // blotches, streaks, sand and pores, and a long fall of 0.03 from top to // bottom. mod.widgets.Window.show_bg = true mod.widgets.Window.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let t = clamp(self.pos.y, 0.0, 1.0) let base = vec3(0.545, 0.541, 0.525) * (1.035 - 0.08 * t) let col = cc_stone(base, p + self.rect_pos, px, 1.0, 1.0) return vec4(col, 1.0) } // ---------------------------------------------------------------- band // THE BAND is the sheet's own slider with two thumbs: the same track, the // same lit value and the same thumb as the Slider, the value lit between // the two thumbs. The widget's grab geometry is set to the thumbs' own, so // what is drawn is what is dragged. mod.widgets.RangeSliderFlat.clip_x = false mod.widgets.RangeSliderFlat.clip_y = false mod.widgets.RangeSliderFlat.track_inset = 1.0 mod.widgets.RangeSliderFlat.handle_size = 14.0 mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() { let g = cc_grow(self.rect_pos, self.rect_size, 5.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 5.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let 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 kr = 7.0 let span = w - 2.0 * kr let k0 = x0 + kr + self.start_pos * span let k1 = x0 + kr + self.end_pos * span let tc = vec2((x0 + x1) * 0.5, cy) var out = cc_groove(p, tc, vec2(w * 0.5, 2.5), o, px, k0, k1, self.disabled, 1.0) let c0 = vec2(Finish.snap(o.x + k0, px) - o.x, cy) let c1 = vec2(Finish.snap(o.x + k1, px) - o.x, cy) out = Finish.over(out, cc_puck(p, c0, kr, o, px, vec2(0.0, -1.0), max(self.hot_start, self.focus * 0.5), self.drag * self.hot_start * live, self.disabled)) out = Finish.over(out, cc_puck(p, c1, kr, o, px, vec2(0.0, -1.0), max(self.hot_end, self.focus * 0.5), self.drag * self.hot_end * live, self.disabled)) return Finish.over(out, cc_focus(length(p - c1) - kr, px, self.focus * live)) } // ---------------------------------------------------------------- switches and meter // THE SWITCHES are the sheet's own toggle, in proportion with the // labels and the controls beside them: the rocker and the slide switch // take the toggle's face, its size and shape, its quad and its label // gap (the library's own faces for them are flat boxes sized for // another face). The rocker turns on a click; the slide switch follows a // drag, as the toggle does. mod.widgets.ToggleRocker.label_walk.margin = mod.turtle.Inset{left: 38.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 38.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.1429 mod.widgets.ToggleSlide.draw_bg.pill_aspect = 2.1429 mod.widgets.ToggleRocker.draw_bg.knob_inset = 1.0 mod.widgets.ToggleSlide.draw_bg.knob_inset = 1.0 mod.widgets.ToggleRocker.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel mod.widgets.ToggleSlide.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel // THE METER sits in the same window as the readouts and the screen: the // library's display housing drawn round its face -- the bezel ring, a shade // of whatever ground it is set in with a device pixel of dark at its edge; // the face with the surround's shadow falling across it from the top and a // device pixel of black where the glass meets the bezel; the glass's quiet // diagonal band and the light line inside its top edge; the lip of light on // the ground under it -- with the same corner, so the meter and the // readouts read as one family. The scale, the red zone, the needle and its // pivot are the library's own. mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(theme.corner_radius * 0.5) mod.widgets.NeedleMeter.draw_bg.color_bezel = uniform(theme.color_d_05) mod.widgets.NeedleMeter.draw_bg.color_lip = uniform(theme.color_u_15) mod.widgets.NeedleMeter.draw_bg.pixel = fn() { let p = self.pos * self.rect_size let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos let c = (lo + hi) * 0.5 let ho = (hi - lo) * 0.5 let r_out = min(self.border_radius, min(ho.x, ho.y)) let outer = Material.sd_box(p, c, ho, r_out) let b = Finish.snap(2.0, px) let hf = max(ho - vec2(b, b), vec2(0.5, 0.5)) let rf = max(r_out - b, 0.0) let face_d = Material.sd_box(p, c, hf, rf) let pv = self.pivot() let rr = self.scale_radius() let q = p - pv let r = length(q) let ang = atan2(q.x, 0.0 - q.y) let sweep = self.sweep * 0.017453293 let t = ang / sweep + 0.5 let on_scale = step(-0.0001, t) * step(t, 1.0001) var face = self.color.rgb // The scale: a hairline arc and a tick at every division. let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0) let k = floor(clamp(t, 0.0, 1.0) * n + 0.5) let ak = (k / n - 0.5) * sweep let perp = abs(q.x * cos(ak) + q.y * sin(ak)) let mn = max(floor(self.minors + 0.5), 1.0) let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001)) let len = mix(self.tick_minor, self.tick_major, major) let radial = max(rr - len - r, r - rr) let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak)) face = mix(face, self.color_scale.rgb, max(arc, tick)) if self.zone < 0.999 { let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px) let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px) face = mix(face, self.color_zone.rgb, zone_t * zone_r) } // The needle, from under the pivot cover to just past the scale. let na = self.needle_angle(self.value) let nd = vec2(sin(na), 0.0 - cos(na)) let along = dot(q, nd) let across = abs(q.x * nd.y - q.y * nd.x) let needle = Finish.cover(across - px * 0.5, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - max(self.pivot_size - px, 0.0), px) face = mix(face, self.color_needle.rgb, needle) let pd = r - self.pivot_size face = mix(face, mix(self.color_needle.rgb, self.color.rgb, 0.35), Finish.cover(pd, px)) face = mix(face, face * 0.55, Finish.band(pd, 0.0, px, px)) // The housing: the surround's shadow across the face from the top, the // black device pixel at the glass's edge, and the glass over it all. let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8) face = face * (1.0 - 0.55 * insh) face = mix(face, face * 0.4, Finish.band(face_d, 0.0, px, px)) let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0)) let diag = u.x + u.y * 0.6 let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag)) let top = Finish.cover(abs(p.y - (c.y - hf.y) - px * 1.5) - px * 0.5, px) * 0.6 let ga = clamp(0.03 * (band + top), 0.0, 1.0) * Finish.cover(face_d + px, px) face = face * (1.0 - ga) + vec3(ga, ga, ga) let bz = self.color_bezel var col = vec4(bz.rgb * bz.a, bz.a) col = Finish.over(col, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px))) col = mix(col, vec4(face, 1.0), Finish.cover(face_d, px)) col = col * Finish.cover(outer, px) let lip = Finish.ring_out(outer, 0.0, px, px) * step(hi.y, p.y) let la = self.color_lip.a * lip return Finish.over(vec4(self.color_lip.rgb * la, la), col) * self.color.a * self.opacity } // ---------------------------------------------------------------- lamps // LAMPS: an LED, as the hardware in his pictures has them. The emitter is // the light: at full current its middle burns toward white and its rim is // its most saturated colour; at lower current the middle is no longer // white, the colour deeper, the glow smaller; out, it is dark tinted glass, // its colour barely there. Round it a bezel: a raised ring lit from the // upper left, or a socket cut into the surface, its upper wall in shadow // and a catch light on its lower rim. Over the lens one crisp highlight of // a dome. Its light is added, never painted: a tight hot halo and a short // bloom in its own hue, screened over whatever is there and gone in about // two radii; on a pale ground, where light can barely brighten, its hue is // also laid round it as a tint that keeps the ground's luminance (the // library's halo rule: at most 0.45 one device pixel out, halving every // 0.4 of its reach, gone at the reach), so it never darkens or greys its // ground. A bar is the same light drawn out into a light pipe. let cc_led_lamp_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 { let est = res.rgb + ground * (1.0 - res.a) let add = (vec3(1.0, 1.0, 1.0) - exp(-light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0)) return vec4(res.rgb + add, res.a) } // An LED's colour at full strength: its brightest channel at one. let cc_led_lamp_full = fn(ink: vec3) -> vec3 { return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01) } // The same colour at the emitter's own chroma: the hue kept and its // saturation raised by `vivid` (0 leaves a pale colour pale; a colour with // next to no hue, a white or a plain lamp, stays neutral either way). let cc_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 { let f = cc_led_lamp_full(ink) let mn = min(f.x, min(f.y, f.z)) let ch = 1.0 - mn let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001) return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch)) } // The lamp: its lens a rounded box `hb` either side of `c` (round when the // two are equal), `lit` its current 0..1, `ink` its colour and `vivid` how // far its chroma is raised, `ground` what shows through where nothing is // drawn, `room` how far its light may reach, `bez` and `raised` its bezel // (raised 0: a recess cut in what is under it; 1: a raised ring of `bez`; // 2: the same ring polished metal). Premultiplied over `dst`. let cc_led_lamp = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float, bez: vec3, raised: float) -> vec4 { let rt = max(min(hb.x, hb.y), 0.4) let d = Material.sd_box(p, c, hb, rt) let bw = clamp(0.3 * rt, 0.7, 2.4) let ds = d - bw let L = clamp(lit, 0.0, 1.0) let b = cc_led_lamp_full(ink) let hue = cc_led_lamp_hue(ink, vivid) let pw = L * sqrt(L) let white = vec3(1.0, 1.0, 1.0) let lum = vec3(0.2126, 0.7152, 0.0722) let pale = smoothstep(0.35, 0.75, dot(ground, lum)) var res = dst let x = max(ds, 0.0) let reach = max(room, 1.0) let win = 1.0 - smoothstep(0.55 * reach, reach, x) // The lens's own frame: `q` in lens radii from its centre line, `t` its // distance out, `nrm` the way its outline faces. let ex = max(hb.x - hb.y, 0.0) let ey = max(hb.y - hb.x, 0.0) let qx = p.x - c.x let qy = p.y - c.y let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt let t = clamp(length(q), 0.0, 1.0) let nrm = q / max(length(q), 0.001) // What the bead and its bezel throw on the ground: a soft shadow, down. let shd = Finish.drop(p, c, hb + vec2(bw, bw), rt + bw, vec2(0.0, 0.22 * rt + 0.3), 0.32 * rt + px) * mix(0.18, 0.34, min(raised, 1.0)) * (1.0 - 0.6 * pw) res = Finish.over(res, vec4(ground * 0.4 * shd, shd)) if L > 0.001 && pale > 0.001 { // The tint, on a pale ground only: its hue laid over, never darker. let h = clamp(0.42 * pw * pow(2.0, -x / max(0.4 * reach, 0.5)) * win, 0.0, 0.45) * pale let est = res.rgb + ground * (1.0 - res.a) let w = clamp(dot(hue, lum) / max(dot(est, lum), 0.001), 0.0, 1.0) res = Finish.over(res, vec4(hue * h * w, h * w)) } // The bezel: raised, a ring of `bez` rounded over, lit from the upper // left; or a socket cut into what is under it, its upper wall in shadow // and a catch light on its lower rim. let a_s = Finish.cover(ds, px) let u = clamp(d / max(bw, 0.01), 0.0, 1.0) let facing = dot(nrm * (u - 0.5) * 2.0, vec2(-0.45, -0.89)) let metal = clamp(raised - 1.0, 0.0, 1.0) var rc = bez * (0.94 + mix(0.12, 0.55, metal) * facing) rc = mix(rc, min(bez + vec3(0.30, 0.30, 0.30), white), smoothstep(mix(0.35, 0.55, metal), 0.9, facing) * mix(0.6, 0.85, metal)) rc = mix(rc, rc * 0.8, Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.0, 0.8, nrm.y) * 0.8) let under = res.rgb / max(res.a, 0.001) * step(0.001, res.a) + ground * (1.0 - step(0.001, res.a)) let gy = clamp(nrm.y * t, -1.0, 1.0) var sc = under * mix(0.55, 0.92, smoothstep(-0.9, 0.9, gy)) sc = mix(sc, min(under * 1.06 + vec3(0.28, 0.28, 0.28), white), Finish.band(ds, 0.0, max(px, 0.45), px) * smoothstep(0.2, 0.95, nrm.y) * 0.85) sc = min(mix(sc, rc, min(raised, 1.0)), white) res = Finish.over(res, vec4(sc * a_s, a_s)) // The light: a tight hot halo and a short bloom in its hue, gone in // about two radii, falling on the bezel too. if L > 0.001 { let xl = max(d, 0.0) let halo = exp(-xl / max(0.30 * rt, 0.4)) * 1.5 + exp(-xl / max(0.65 * rt, 0.7)) * 0.32 let hwin = 1.0 - smoothstep(1.1 * rt, 2.0 * rt + 1.0, xl) res = cc_led_lamp_add(res, mix(hue, white, 0.15 * pw) * halo * pw * hwin * win, ground) } // THE BEAD: a glossy dome of coloured glass. Its colour deepens toward // the top; light pools low inside, where it comes through the dome; lit, // its middle burns toward white at full current and its rim is its most // saturated colour. let top = smoothstep(-1.0, 0.6, q.y) let pool = exp(-(q.y - 0.55) * (q.y - 0.55) / 0.09 - q.x * q.x / 0.30) var on = mix(hue * mix(0.55, 0.8, L), mix(b, hue, 0.5), top) on = mix(on, min(mix(b, hue, 0.4) + vec3(0.35, 0.35, 0.35), white), pool * 0.5) on = mix(on, vec3(1.0, 0.99, 0.975), exp(-t * t / 0.20) * smoothstep(0.5, 1.0, L) * 0.85) on = mix(on, hue * (0.82 + 0.18 * L), smoothstep(0.62, 1.0, t)) on = on * (0.42 + 0.58 * L) // Out: dark tinted glass on a dark ground, a grey bead on a pale one, // its colour barely there, the same dome, a catch light along its lower // rim. let offc = mix(mix(vec3(0.085, 0.09, 0.115), ground * 0.62, pale), b * 0.4, 0.12) var off = mix(offc * 0.62, offc * 1.08, top) off = mix(off, min(offc + vec3(0.18, 0.18, 0.18), white), pool * 0.35) off = off * mix(1.0, 0.75, smoothstep(0.75, 1.0, t)) let catchl = smoothstep(0.62, 0.9, t) * (1.0 - smoothstep(0.9, 1.0, t)) * smoothstep(0.2, 0.9, nrm.y) off = off + vec3(0.30, 0.30, 0.32) * catchl * (1.0 - 0.5 * pale) var col = mix(off, min(on, white), smoothstep(0.0, 0.35, L)) // One crisp highlight: a white oval across its upper third. let e = length(vec2(q.x / 0.62, (q.y + 0.46) / 0.30)) let spec = (1.0 - smoothstep(0.55, 1.0, e)) * mix(1.0, 0.3, clamp((q.y + 0.76) / 0.6, 0.0, 1.0)) col = mix(col, white, spec * mix(0.8, 0.55, L) * smoothstep(0.9, 2.2, rt)) // Out, a faint seam where the dome meets its bezel; lit, none: the rim // is the colour. col = mix(col, col * 0.7, Finish.band(d, 0.0, px * 0.6, px) * 0.6 * (1.0 - smoothstep(0.0, 0.25, L))) col = min(col, white) let a_l = Finish.cover(d, px) return Finish.over(res, vec4(col * a_l, a_l)) } // The Lamp widget and the bar lamp: the lamp above, here in a socket cut into the concrete, its upper wall in shadow and a catch light on its lower rim. // The halo's room is held in the lamp's own box (the library reserves a // lens thickness either side once a halo is asked for), so the light is // never cut at its edge. mod.widgets.Lamp.halo = 0.3 mod.widgets.Lamp.halo_reach = 1.0 mod.widgets.Lamp.draw_bg.vertex = fn() { let m = 2.0 self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m)) } mod.widgets.Lamp.draw_bg.pixel = fn() { let m = 2.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let reach = clamp(self.reach, 0.0, 1.0) let t = self.rect_size.y / (1.0 + 2.0 * reach) let room = t * reach let c = self.rect_size * 0.5 let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5) // The lens is the lamp's size; its socket stands round it in the room. let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5) let res = cc_led_lamp(vec4(0.0, 0.0, 0.0, 0.0), p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, vec3(0.545, 0.541, 0.525), max(room - 0.5, 2.0), vec3(0.0, 0.0, 0.0), 0.0) return res * self.opacity } true