use mod.prelude.widgets_internal.* use mod.sdf.Cap // Turned and brushed aluminium on a pale anodised housing. // Metal parts stand on the housing with a hard contact line and a faint // ambient shade, drawn outside their boxes; tracks and wells are cut into // it, dark at the top edge with a bright lip under the lower rim. Every rim // is one device pixel, snapped. mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0} mod.widgets.Button.draw_bg.border_radius = 4.0 mod.widgets.Button.draw_bg.border_size = 0.5 // A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and // handed to each field below. Buttons take the same 24. let field_height = 24.0 let field_pad_v = mod.theme.mspace_1.top let field_radius = 4.0 let field_border = 0.5 // The room a Fit field takes: where a field is Fit, the padding IS its // height. let field_room = fn(w) { w.min_height = field_height w.padding.top = field_pad_v w.padding.bottom = field_pad_v } // The box a field draws. Given the draw object and not the widget, because // not every field draws its own box. let field_face = fn(d) { d.border_radius = field_radius d.border_size = field_border } // A field that states a frame and lays its own parts out inside it takes // the height alone: a padding would displace its line. let field_frame = fn(w) { w.min_height = field_height } field_room(mod.widgets.TextInput) field_face(mod.widgets.TextInput.draw_bg) field_room(mod.widgets.ComboBox) field_face(mod.widgets.ComboBox.draw_bg) field_room(mod.widgets.DropDown) field_face(mod.widgets.DropDown.draw_bg) field_room(mod.widgets.DropDown2) field_face(mod.widgets.DropDown2.draw_bg) field_room(mod.widgets.FieldWell) field_face(mod.widgets.FieldWell.draw_bg) field_room(mod.widgets.TagField) field_face(mod.widgets.TagField.draw_bg) field_frame(mod.widgets.NumberField) mod.widgets.NumberField.draw_bg.border_radius = field_radius field_frame(mod.widgets.ValueInput) field_frame(mod.widgets.DateField) field_frame(mod.widgets.TimeField) field_frame(mod.widgets.DatePicker) field_frame(mod.widgets.DateRangePicker) // The chrome-less input a well holds must impose no height of its own. mod.widgets.WellInput.min_height = 0.0 mod.widgets.Window.pass.clear_color = #cacacc // Plain views stop clipping, so a part's contact shadow may fall into the // gap beside it. Scroll views keep their own clip. mod.widgets.View.clip_x = false mod.widgets.View.clip_y = false // --------------------------------------------------------------------------- // SHARED PARTS. Distances in points, negative inside, y down; `px` is one // device pixel in points. let g3 = fn(v: float) -> vec3 { return vec3(v, v, v) } // A rounded box, true radius. let alu_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 disc laid on the pixel grid: a whole number of device pixels across, // its edge on a pixel boundary, so its one-pixel rings are one pixel. // `c` is quad-local; returns (centre.x, centre.y, radius). let alu_fit = fn(c: vec2, o: vec2, rad: float, px: float) -> vec3 { let dn = max(floor(2.0 * rad / px + 0.5), 2.0) let r2 = dn * px * 0.5 let ex = Finish.snap(o.x + c.x - r2, px) - o.x let ey = Finish.snap(o.y + c.y - r2, px) - o.y return vec3(ex + r2, ey + r2, r2) } // The housing: a fall of -0.05 from top to bottom of the window, a faint // lift in the upper third, and one-pixel grain at sd 0.010. let alu_ground = fn(p: vec2, size: vec2, px: float) -> vec4 { let t = clamp(p.y / max(size.y, 1.0), 0.0, 1.0) var col = mix(vec3(0.831, 0.831, 0.839), vec3(0.749, 0.749, 0.757), t) let cc = vec2(size.x * 0.5, size.y * 0.3) let reach = length(size) * 0.7 let lift = 1.0 - smoothstep(0.0, reach, length(p - cc)) col = col + g3(0.03 * lift * lift) col = col + g3(Finish.grain(p / px, 0.0245)) return vec4(col, 1.0) } // The brushed face of a raised part, by height t 0..1: bright top, a // darker waist, lighter again at the foot, with horizontal streaks. let alu_face = fn(t: float, q: vec2, px: float) -> vec3 { let top = vec3(0.925, 0.933, 0.941) let mid = vec3(0.847, 0.855, 0.867) let foot = vec3(0.788, 0.796, 0.808) var col = mix(mix(top, mid, clamp(t * 2.0, 0.0, 1.0)), foot, clamp(t * 2.0 - 1.0, 0.0, 1.0)) let s = Finish.brushed(q, vec2(1.0, 0.0), px * 1.4) - 0.5 return col + g3(s * 0.034) } // Turned metal: four lobes round the centre, brightest left and right, // darkest on the diagonals, over concentric lathe rings. `amp` scales the // lobes (1 a knob, 0.5 a thumb, 0.3 disabled). Returns a luminance. let alu_conic = fn(q: vec2, px: float, amp: float, turn: float) -> float { let r = max(length(q), 0.0001) let cs = q.x / r let sn = q.y / r // A small turn of the lobes, as the light catches a hovered cap. let c1 = cs * cos(turn) - sn * sin(turn) let s1 = sn * cos(turn) + cs * sin(turn) let c2 = c1 * c1 - s1 * s1 let c4 = 2.0 * c2 * c2 - 1.0 var l = 0.66 + amp * (0.09 * c2 + 0.12 * c4) let rings = Finish.noise(vec2(r / px * 0.75, 0.37)) - 0.5 let streak = Finish.noise(vec2(cs * 23.0 + 7.1, sn * 23.0 + 3.3)) - 0.5 l = l + rings * 0.03 + streak * 0.04 * smoothstep(0.0, 2.0, r) // the pinprick where the lobes meet l = l + 0.05 * (1.0 - smoothstep(0.0, px * 1.2, r)) return l } // A lamp: a lit lens in a chrome socket, never a light source. `ink` the // lens at its brightest, `lit` 0 dark .. 1 lit, `glow` 0 or the halo's // strength at one device pixel (held under the family ceiling of 0.30). let alu_lamp = fn(p: vec2, lc: vec2, rad: float, px: float, ink: vec3, dark: vec3, lit: float, glow: float) -> vec4 { let q = p - lc let d = length(q) - rad let v = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0) // Brightest just below the middle, darker at top and foot. let on = mix(mix(ink * 0.72, ink, smoothstep(0.1, 0.58, v)), ink * 0.45, smoothstep(0.62, 1.0, v)) let off = mix(dark * 1.25, dark, v) var col = mix(off, on, lit) // specular crescent at the top inside let spec = Finish.band(d, px, 2.0 * px, px) * (1.0 - smoothstep(-0.2, 0.25, q.y / rad)) col = mix(col, vec3(0.80, 0.88, 0.94), spec * 0.5) // dark rim col = mix(col, dark * 0.6, Finish.band(d, 0.0, px, px) * 0.9) let a = Finish.cover(d, px) var out = vec4(col * a, a) // socket: a dark hairline above, a bright lip below, outside the lens let above = Finish.ring_out(d, 0.0, px, px) * (1.0 - smoothstep(-0.3, 0.3, q.y / rad)) let below = Finish.ring_out(d, 0.0, px, px) * smoothstep(-0.3, 0.3, q.y / rad) out = Finish.over(vec4(0.0, 0.0, 0.0, above * 0.45), out) out = Finish.over(vec4(below * 0.85, below * 0.85, below * 0.85, below * 0.85), out) // halo: an exponential with a 1 point decay, at the core's hue let ha = glow * lit * exp(-max(d - px, 0.0) / 0.8) * (1.0 - a) * step(0.0, d) return Finish.over(vec4(ink * ha, ha), out) } // --------------------------------------------------------------------------- // BUTTONS. One face for the family; `kind` picks the dressing: // 0 flat plate, 1 chrome-ringed, 2 chrome with a lit blue lamp (primary), // 3 chrome with a dark lamp, 4 chrome with a lit red lamp (danger), // 5 engraved outline, 6 bare until hovered. // The quad is grown by `m` (6 points) on every side for the shade and the // focus ring. let alu_button = fn(pos: vec2, size: vec2, o: vec2, px: float, hover: float, down: float, focus: float, disabled: float, loading: float, anim: float, r: float, kind: float, m: float) -> vec4 { let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - disabled 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 + vec2(0.0, press * px) let h = (hi - lo) * 0.5 let d = alu_rbox(p, c, h, r) let raised = step(kind, 4.5) let bare = step(5.5, kind) let ring = step(0.5, kind) * raised var out = vec4(0.0, 0.0, 0.0, 0.0) // The ground round a raised part: lifted above it, shaded below it. let rest = (1.0 - press) * raised // Both stay over and under the part: the gap beside it stays clear, // and both fade out before the edge of the grown quad. let beside = 1.0 - smoothstep(-r, 0.5, abs(p.x - c.x) - h.x) let reach = clamp((m - (abs(p.y - c.y) - h.y)) / 2.0, 0.0, 1.0) let under = smoothstep(-2.0, 2.0, p.y - c.y) let lift = Finish.drop(p, c, h, r, vec2(0.0, -1.6), 2.2) * 0.40 * rest * beside * reach * (1.0 - under) * mix(1.0, 0.3, disabled) out = Finish.over(out, vec4(lift, lift, lift, lift)) let amb = Finish.drop(p, c, h, r, vec2(0.0, 2.0), 2.4) * mix(0.16, 0.20, hover) * rest * beside * reach * under * mix(1.0, 0.3, disabled) out = Finish.over(out, vec4(0.0, 0.0, 0.0, amb)) let con = Finish.drop(p, c, h, r, vec2(0.0, mix(0.9, 0.35, press)), 0.45) * mix(0.55, 0.4, press) * raised * mix(1.0, 0.3, disabled) out = Finish.over(out, vec4(vec3(0.078, 0.071, 0.082) * con, con)) // An engraved outline sits in the ground: a light lip under its lower rim. let lip = Finish.ring_out(d, 0.0, px, px) * step(c.y, p.y) * step(4.5, kind) * (1.0 - bare) * 0.8 out = Finish.over(out, vec4(lip, lip, lip, lip)) // The face. let t = clamp((p.y - (c.y - h.y)) / max(2.0 * h.y, 1.0), 0.0, 1.0) let tt = mix(t, 1.0 - t, press * 0.45) var face = alu_face(tt, p + o, px) face = Finish.lift(face, 0.2 * hover * live * (1.0 - press) - 0.08 * press) face = mix(face, mix(vec3(0.788, 0.792, 0.800), face, 0.4), disabled) var fa = 1.0 if kind > 4.5 { // outline and bare faces are the housing itself until touched fa = mix(0.0, 0.55, hover * live) + 0.25 * press } // chrome band, the inner dark line inside it let chrome = Finish.lift(mix(vec3(0.961, 0.965, 0.973), vec3(0.847, 0.851, 0.863), t), 0.15 * hover) face = mix(face, vec3(0.212, 0.212, 0.220), Finish.band(d, 3.0 * px, 4.0 * px, px) * 0.3 * ring * mix(1.0, 0.4, disabled)) let rim_in = mix(4.0, 1.0, 1.0 - ring) * px // lit rims on the inside of the face: bright top, quiet bottom let top_rim = Finish.band(d, rim_in, rim_in + px, px) * (1.0 - smoothstep(-0.5, 0.5, p.y - c.y)) * 0.85 * (1.0 - press) let bot_rim = Finish.band(d, rim_in, rim_in + px, px) * smoothstep(-0.5, 0.5, p.y - c.y) * 0.35 face = mix(face, vec3(0.988, 0.988, 0.996), (top_rim + bot_rim) * raised * live) var edge_a = fa if ring > 0.5 { let band = Finish.band(d, px, 3.0 * px, px) face = mix(face, chrome, band * mix(1.0, 0.4, disabled)) } // the outline, one device pixel let ol = Finish.band(d, 0.0, px, px) var ol_a = mix(0.85, 0.35, disabled) if kind > 4.5 { ol_a = mix(0.55, 0.3, disabled) * (1.0 - bare) + hover * live * 0.5 * bare edge_a = max(fa, ol * ol_a) } face = mix(face, vec3(0.247, 0.251, 0.267), ol * ol_a / max(edge_a, 0.001)) // the lamp of the primary, secondary and danger faces if kind > 1.5 && kind < 4.5 { let lc = vec2(lo.x + 9.5, c.y) let is_red = step(3.5, kind) let ink = mix(vec3(0.506, 0.812, 0.992), vec3(0.972, 0.235, 0.239), is_red) let dark = mix(vec3(0.173, 0.306, 0.412), vec3(0.353, 0.102, 0.110), is_red) let lit = (1.0 - step(2.5, kind) * step(kind, 3.5)) * live let lamp = alu_lamp(p, lc, 2.5, px, ink, dark, lit, 0.0) face = mix(face, lamp.rgb / max(lamp.a, 0.001), lamp.a) } let a = Finish.cover(d, px) * edge_a out = Finish.over(out, vec4(face * a, a)) // Focus: a flat ring two points out, never a glow. let fr = Finish.ring_out(d, 1.5, 3.0, px) * 0.55 * focus * live out = Finish.over(out, vec4(vec3(0.051, 0.631, 0.984) * fr, fr)) // 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.5) sdf.fill(vec4(0.235, 0.239, 0.251, loading)) out = Finish.over(out, sdf.result) } return out } let grow_vertex = 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) } // Button faces. The flat root and the bare face keep to their own box, so // every template derived from them with a face of its own is left as it is; // the raised faces grow their quad for the shade and the focus ring. mod.widgets.ButtonFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 0.0, 0.0), 0.0) } mod.widgets.ButtonFlatter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 6.0, 0.0), 0.0) } mod.widgets.Button.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Button.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0, 1.0, 6.0), 6.0) } mod.widgets.ButtonPrimary.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonPrimary.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0, 2.0, 6.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(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0, 3.0, 6.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(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0, 0.0, 6.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(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0, 4.0, 6.0), 6.0) } mod.widgets.ButtonOutline.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonOutline.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0, 5.0, 6.0), 6.0) } mod.widgets.ButtonXs.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonXs.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 1.0, 6.0), 6.0) } mod.widgets.ButtonSm.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonSm.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 3.0, 1.0, 6.0), 6.0) } mod.widgets.ButtonLg.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonLg.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 5.0, 1.0, 6.0), 6.0) } mod.widgets.ButtonXl.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonXl.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 6.0, 1.0, 6.0), 6.0) } mod.widgets.ButtonCompound.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonCompound.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(alu_button(self.pos, self.rect_size, self.rect_pos, px, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0, 1.0, 6.0), 6.0) } mod.widgets.Button.clip_x = false mod.widgets.Button.clip_y = false mod.widgets.ButtonPrimary.clip_x = false mod.widgets.ButtonPrimary.clip_y = false mod.widgets.ButtonOutline.clip_x = false mod.widgets.ButtonOutline.clip_y = false mod.widgets.ButtonXs.clip_x = false mod.widgets.ButtonXs.clip_y = false mod.widgets.ButtonSm.clip_x = false mod.widgets.ButtonSm.clip_y = false mod.widgets.ButtonLg.clip_x = false mod.widgets.ButtonLg.clip_y = false mod.widgets.ButtonXl.clip_x = false mod.widgets.ButtonXl.clip_y = false mod.widgets.ButtonCompound.clip_x = false mod.widgets.ButtonCompound.clip_y = false // Button geometry: one 24 point row, a small radius, the lamp's room on the // left of the faces that carry one. mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.0} mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonPrimary.padding = mod.turtle.Inset{left: 19.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonTertiary.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonOutline.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 3.0 bottom: 3.0} mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 3.0} mod.widgets.Button.icon_walk = mod.turtle.Walk{width: 14.0 height: Fit} // The smaller type must not shrink the rows: 24 points whatever the text. mod.widgets.ButtonFlat.min_height = 24.0 mod.widgets.Button.min_height = 24.0 mod.widgets.ButtonXs.min_height = 0.0 mod.widgets.ButtonSm.min_height = 0.0 // Label ink: engraved grey on every face; the lamp says the role, not the // ink. Primary and danger carry the semibold weight. mod.widgets.ButtonPrimary.draw_text.color = #3c3d40 mod.widgets.ButtonPrimary.draw_text.color_hover = #2e2f32 mod.widgets.ButtonPrimary.draw_text.color_down = #2e2f32 mod.widgets.ButtonPrimary.draw_text.color_focus = #3c3d40 mod.widgets.ButtonPrimary.draw_text.color_disabled = #8e8f93 mod.widgets.ButtonPrimary.draw_icon.color = #3c3d40 mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 8.25} mod.widgets.ButtonSecondary.draw_text.color = #3c3d40 mod.widgets.ButtonSecondary.draw_text.color_hover = #2e2f32 mod.widgets.ButtonSecondary.draw_text.color_down = #2e2f32 mod.widgets.ButtonSecondary.draw_text.color_focus = #3c3d40 mod.widgets.ButtonSecondary.draw_icon.color = #3c3d40 mod.widgets.ButtonSecondary.draw_text.text_style = mod.theme.font_regular{font_size: 8.25} mod.widgets.ButtonTertiary.draw_text.color = #3c3d40 mod.widgets.ButtonTertiary.draw_text.color_hover = #2e2f32 mod.widgets.ButtonTertiary.draw_text.color_down = #2e2f32 mod.widgets.ButtonTertiary.draw_text.color_focus = #3c3d40 mod.widgets.ButtonTertiary.draw_icon.color = #3c3d40 mod.widgets.ButtonTertiary.draw_text.text_style = mod.theme.font_regular{font_size: 8.25} mod.widgets.ButtonDanger.draw_text.color = #3c3d40 mod.widgets.ButtonDanger.draw_text.color_hover = #2e2f32 mod.widgets.ButtonDanger.draw_text.color_down = #2e2f32 mod.widgets.ButtonDanger.draw_text.color_focus = #3c3d40 mod.widgets.ButtonDanger.draw_icon.color = #3c3d40 mod.widgets.ButtonDanger.draw_text.text_style = mod.theme.font_bold{font_size: 8.25} mod.widgets.ButtonOutline.draw_text.color = #3c3d40 mod.widgets.ButtonOutline.draw_text.color_hover = #2e2f32 mod.widgets.ButtonOutline.draw_text.color_down = #2e2f32 mod.widgets.ButtonOutline.draw_text.color_focus = #3c3d40 mod.widgets.ButtonOutline.draw_text.color_disabled = #8e8f93 mod.widgets.ButtonOutline.draw_icon.color = #3c3d40 mod.widgets.ButtonFlat.draw_bg.loading_color = #3c3d40 // --------------------------------------------------------------------------- // WELLS. A slot or pocket cut into the housing: a dark hairline round its // mouth (darkest at the top), the housing's shadow falling in from the top // edge, and a bright lip under the lower rim drawn by the caller. let alu_recess = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, fill: vec3, depth: float, foot: vec3, shade: float) -> vec3 { let d = alu_rbox(p, c, h, r) let sh = Finish.inset(p, c, h, r, vec2(0.0, depth * 0.55), max(depth * 0.4, px * 0.6)) var col = mix(fill, vec3(0.118, 0.122, 0.133), clamp(sh, 0.0, 1.0) * shade) let low = smoothstep(-0.5, 0.5, p.y - c.y) let rim = mix(vec3(0.247, 0.251, 0.267), foot, low) col = mix(col, rim, Finish.band(d, 0.0, px, px) * mix(0.9, 0.75, low)) return col } // The bright lip under a recess's lower rim, outside it: premultiplied. let alu_lip = fn(p: vec2, c: vec2, h: vec2, r: float, px: float, w: float, a: float) -> vec4 { let d = alu_rbox(p, c, h, r) let l = Finish.ring_out(d, 0.0, w, px) * smoothstep(-0.5, 0.5, p.y - c.y) * a return vec4(0.980 * l, 0.984 * l, 0.992 * l, l) } // A flat focus ring two points out, never a glow. let alu_focus = fn(d: float, px: float, focus: float) -> vec4 { let f = Finish.ring_out(d, 1.5, 3.0, px) * 0.55 * focus return vec4(0.051 * f, 0.631 * f, 0.984 * f, f) } // A turned disc standing on the housing: contact line, faint ambient shade, // a thin ring and the four-lobed face over a vertical profile. // `amp` the lobes' strength; `hover`, `press`, `dis` its states. let alu_disc = fn(p: vec2, c: vec2, rad: float, px: float, amp: float, hover: float, press: float, dis: float) -> vec4 { let hh = vec2(rad, rad) let live = 1.0 - dis var out = vec4(0.0, 0.0, 0.0, 0.0) let amb = Finish.drop(p, c, hh, rad, vec2(0.0, rad * 0.16), rad * 0.2) * 0.16 * (1.0 - press) * mix(1.0, 0.3, dis) out = Finish.over(out, vec4(0.0, 0.0, 0.0, amb)) let con = Finish.drop(p, c, hh, rad, vec2(0.0, mix(0.9, 0.35, press)), 0.45) * mix(0.62, 0.4, press) * mix(1.0, 0.3, dis) out = Finish.over(out, vec4(vec3(0.078, 0.071, 0.082) * con, con)) let q = p - c let d = length(q) - rad let t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0) let top = vec3(0.965, 0.969, 0.976) let mid = vec3(0.647, 0.663, 0.675) let foot = vec3(0.843, 0.847, 0.863) var face = mix(mix(top, mid, smoothstep(0.02, 0.52, t)), foot, smoothstep(0.52, 0.95, t)) let l = alu_conic(q, px, 1.0, 0.105 * hover) face = mix(face, g3(l) * vec3(0.99, 1.0, 1.015), amp) face = Finish.lift(face, 0.18 * hover * live * (1.0 - press) - 0.06 * press) face = mix(face, mix(vec3(0.788, 0.792, 0.800), face, 0.4), dis) // the chrome ring: a dark hairline, a bright band two pixels wide, a // grey hairline, then the face; the band's foot falls a little darker let up = 1.0 - smoothstep(-0.3, 0.3, q.y / rad) let band = mix(vec3(0.80, 0.805, 0.815), vec3(0.937, 0.937, 0.945), smoothstep(-0.8, 0.2, -q.y / rad)) face = mix(face, vec3(0.604, 0.608, 0.616), Finish.band(d, 3.0 * px, 4.0 * px, px) * 0.8) face = mix(face, band, Finish.band(d, px, 3.0 * px, px) * mix(1.0, 0.4, dis)) face = mix(face, vec3(0.365, 0.365, 0.373), Finish.band(d, 0.0, px, px) * mix(0.95, 0.4, dis)) let a = Finish.cover(d, px) return Finish.over(out, vec4(face * a, a)) } // --------------------------------------------------------------------------- // CHECK BOXES. A 15 point pocket, 3 points in from the edge of the row so // its focus ring has room: grey when clear, blue when set, the mark white // with a dark hairline over it, as ink cut into colour. let alu_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) } mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 3.0 top: 3.0 bottom: 3.0} mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 24.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.CheckBoxFlat.draw_bg.size = uniform(15.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 p = self.pos * self.rect_size let o = self.rect_pos let sz = self.size let x0 = mix(3.0, self.rect_size.x - sz - 3.0, self.mark_at_end) let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (self.rect_size.y - sz) * 0.5, px)) - o let c = lo + vec2(sz * 0.5, sz * 0.5 - 0.5 * px) let h = vec2(sz * 0.5, sz * 0.5 - 0.5 * px) // the round box keeps its round pocket let r = mix(3.0, h.y, step(20.0, self.border_radius)) let d = alu_rbox(p, c, h, r) let live = 1.0 - self.disabled let on = max(self.active, self.mixed) let blue = mix(vec3(0.357, 0.655, 0.851), vec3(0.624, 0.702, 0.757), self.disabled) let grey = mix(vec3(0.659, 0.663, 0.678), vec3(0.765, 0.769, 0.780), self.disabled) var fill = mix(grey, blue, on) fill = Finish.lift(fill, 0.08 * self.hover * live * (1.0 - self.down) - 0.08 * self.down * live) let foot = mix(vec3(0.282, 0.286, 0.294), vec3(0.224, 0.298, 0.357), on) var col = alu_recess(p, c, h, r, px, fill, 2.2 + self.down, foot, mix(0.55, 0.25, self.disabled)) // error: the mouth of the pocket turns red col = mix(col, vec3(0.796, 0.090, 0.102), Finish.band(d, 0.0, px, px) * self.error) // the mark, or the mixed dash let k = sz / 15.0 let m0 = lo + vec2(4.0, 7.8) * k let m1 = lo + vec2(6.6, 10.3) * k let m2 = lo + vec2(11.2, 4.8) * k let ck = min(alu_seg(p, m0, m1), alu_seg(p, m1, m2)) - 0.85 * k let ck_dark = min(alu_seg(p + vec2(0.0, px), m0, m1), alu_seg(p + vec2(0.0, px), m1, m2)) - 0.85 * k let dash = alu_seg(p, lo + vec2(4.0, 7.5) * k, lo + vec2(11.0, 7.5) * k) - 0.85 * k let dash_dark = alu_seg(p + vec2(0.0, px), lo + vec2(4.0, 7.5) * k, lo + vec2(11.0, 7.5) * k) - 0.85 * k let show_ck = self.active * (1.0 - self.mixed) let dk = mix(Finish.cover(ck_dark, px) * show_ck, Finish.cover(dash_dark, px), self.mixed) let wk = mix(Finish.cover(ck, px) * show_ck, Finish.cover(dash, px), self.mixed) col = mix(col, vec3(0.118, 0.227, 0.322), dk * 0.5 * live) col = mix(col, mix(vec3(1.0, 1.0, 1.0), vec3(0.93, 0.94, 0.95), self.disabled), wk) let a = Finish.cover(d, px) var out = alu_lip(p, c, h, r, px, px, 0.9) out = Finish.over(out, vec4(col * a, a)) return Finish.over(out, alu_focus(d, px, self.focus * live)) } // --------------------------------------------------------------------------- // TOGGLES. A 34 by 18 slot, grey or lit blue, darkened toward the thumb it // carries; the thumb is a turned disc as tall as the slot. mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 43.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleFlat.draw_bg.size = uniform(18.0) mod.widgets.ToggleFlat.min_height = 24.0 mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(1.8889) mod.widgets.ToggleFlat.draw_bg.knob_inset = uniform(0.0) mod.widgets.ToggleFlat.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 sz = vec2(self.size * self.pill_aspect, self.size) let x0 = mix(3.0, self.rect_size.x - sz.x - 3.0, self.mark_at_end) let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + (self.rect_size.y - sz.y) * 0.5, px)) - o // the lip lies outside the box, so the slot keeps its whole 18 points let lip_w = 2.0 * px let h = vec2(sz.x * 0.5, sz.y * 0.5) let c = lo + h let r = h.y let d = alu_rbox(p, c, h, r) let live = 1.0 - self.disabled let knob_t = mix(self.active, self.drag_pos, self.drag) // the thumb as tall as the slot, its left edge on a pixel boundary let kr = h.y let ke = Finish.snap(o.x + lo.x + knob_t * (sz.x - 2.0 * kr), px) - o.x let kx = ke + kr let kc = vec2(kx, c.y) // lit slot: bright at the far end, shaded toward the thumb let near = clamp((p.x - lo.x) / max(kx - lo.x, 1.0), 0.0, 1.0) let lit = mix(vec3(0.510, 0.788, 0.969), vec3(0.349, 0.655, 0.847), near * near) let blue = mix(lit, vec3(0.624, 0.702, 0.757), self.disabled) let grey = mix(vec3(0.659, 0.663, 0.678), vec3(0.765, 0.769, 0.780), self.disabled) var fill = mix(grey, blue, self.active) fill = Finish.lift(fill, 0.12 * self.hover * live * (1.0 - self.down) - 0.06 * self.down * live) let foot = mix(vec3(0.282, 0.286, 0.294), vec3(0.224, 0.365, 0.467), self.active) var col = alu_recess(p, c, h, r, px, fill, 2.7, foot, mix(0.72, 0.3, self.disabled)) // error: the mouth of the slot turns red, as the check box's does col = mix(col, vec3(0.796, 0.090, 0.102), Finish.band(d, 0.0, px, px) * self.error) let a = Finish.cover(d, px) var out = alu_lip(p, c, h, r, px, lip_w, 0.9) out = Finish.over(out, vec4(col * a, a)) out = Finish.over(out, alu_focus(d, px, self.focus * live)) let disc = alu_disc(p, kc, kr, px, 0.85, self.hover * live * 1.5, self.down * live * 1.4, self.disabled) return Finish.over(out, disc) } // --------------------------------------------------------------------------- // RADIOS. A lamp in a chrome socket: a dark blue lens when clear, lit when // chosen. Lit lamps carry no halo; the lens is lit, not a light source. 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: 24.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.RadioButtonFlat.draw_bg.size = uniform(15.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 p = self.pos * self.rect_size let o = self.rect_pos let sz = self.size let lo = vec2(Finish.snap(o.x + 3.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) let rad = sz * 0.5 let live = 1.0 - self.disabled let q = p - c let d = length(q) - rad // the socket: dark at the top, the chrome lip at the foot let sock_t = clamp(q.y / rad * 0.5 + 0.5, 0.0, 1.0) var sock = mix(vec3(0.255, 0.259, 0.275), vec3(0.62, 0.63, 0.64), smoothstep(0.0, 0.45, sock_t)) sock = mix(sock, vec3(0.906, 0.972, 1.0), smoothstep(0.62, 0.95, sock_t)) let a = Finish.cover(d, px) var out = vec4(sock * a, a) // the lens, one ring in let ink = mix(vec3(0.506, 0.812, 0.992), vec3(0.62, 0.70, 0.76), self.disabled) let dark = mix(vec3(0.173, 0.306, 0.412), vec3(0.45, 0.48, 0.51), self.disabled) let lamp = alu_lamp(p, c, rad - 1.6, px, ink, dark, self.active, 0.0) let hov = Finish.cover(length(q) - rad + 1.6, px) * 0.10 * self.hover * live out = Finish.over(out, lamp) out = Finish.over(out, vec4(hov, hov, hov, hov)) let dn = Finish.cover(length(q) - rad + 1.6, px) * 0.22 * self.down * live out = Finish.over(out, vec4(0.0, 0.0, 0.0, dn)) return Finish.over(out, alu_focus(d, px, self.focus * live)) } // Radio tabs: a recessed slot holding the segment; the chosen one stands up // out of it as a brushed face. mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.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.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o let live = 1.0 - self.disabled let h = (hi - lo) * 0.5 - vec2(0.0, px * 0.5) let c = lo + h let r = 4.0 let d = alu_rbox(p, c, h, r) var well = mix(vec3(0.725, 0.729, 0.741), vec3(0.80, 0.80, 0.81), self.disabled) well = Finish.lift(well, 0.08 * self.hover * live * (1.0 - self.active) * (1.0 - self.down) - 0.09 * self.down * live) let col = alu_recess(p, c, h, r, px, well, 2.2, vec3(0.282, 0.286, 0.294), mix(0.5, 0.25, self.disabled)) let a = Finish.cover(d, px) var out = alu_lip(p, c, h, r, px, px, 0.9) out = Finish.over(out, vec4(col * a, a)) // the raised segment, two device pixels in let sh = h - vec2(2.0 * px, 2.0 * px) let sd = alu_rbox(p, c, sh, r - 1.0) let t = clamp((p.y - (c.y - sh.y)) / max(2.0 * sh.y, 1.0), 0.0, 1.0) var face = alu_face(t, p + o, px) face = Finish.lift(face, 0.2 * self.hover * live) face = mix(face, mix(vec3(0.788, 0.792, 0.800), face, 0.4), self.disabled) let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y) face = mix(face, vec3(0.988, 0.988, 0.996), Finish.band(sd, px, 2.0 * px, px) * mix(0.3, 0.85, up) * live) face = mix(face, vec3(0.247, 0.251, 0.267), Finish.band(sd, 0.0, px, px) * 0.7) let con = Finish.drop(p, c, sh, r - 1.0, vec2(0.0, 0.6), 0.4) * 0.45 * self.active out = Finish.over(out, vec4(0.0, 0.0, 0.0, con * a)) let fa = Finish.cover(sd, px) * self.active out = Finish.over(out, vec4(face * fa, fa)) return Finish.over(out, alu_focus(d, px, self.focus * live)) } mod.widgets.RadioButtonTabFlat.draw_text.color = #3c3d40 mod.widgets.RadioButtonTabFlat.draw_text.color_active = #2e2f32 mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #8e8f93 // --------------------------------------------------------------------------- // SLIDERS. A 6 point track cut into the housing, the blue fill inside it, // hairline ticks engraved above, and a 16 point turned thumb. The quad // grows by 6 points for the thumb's shade and the focus ring. mod.widgets.SliderMinimal.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderMinimal.clip_x = false mod.widgets.SliderMinimal.clip_y = false let alu_track = fn(p: vec2, lo: vec2, w: float, th: float, px: float, fx: float, dis: float) -> vec4 { let h = vec2(w * 0.5, th * 0.5) let c = lo + h let r = h.y let d = alu_rbox(p, c, h, r) let filled = step(p.x, fx) // the fill is shaded toward the thumb that stands in it let near = clamp((p.x - lo.x) / max(fx - lo.x, 1.0), 0.0, 1.0) let lit = mix(vec3(0.420, 0.710, 0.890), vec3(0.310, 0.600, 0.800), near * near) let blue = mix(lit, vec3(0.624, 0.702, 0.757), dis) let grey = mix(vec3(0.659, 0.663, 0.678), vec3(0.765, 0.769, 0.780), dis) var fill = mix(grey, blue, filled) // the fill's lit top line under the inner shadow let ytop = p.y - (c.y - h.y) fill = mix(fill, vec3(0.541, 0.812, 0.965), filled * (1.0 - dis) * Finish.band(-ytop + 2.0 * px, 0.0, px, px) * 0.6) let foot = mix(vec3(0.282, 0.286, 0.294), vec3(0.224, 0.298, 0.357), filled) let col = alu_recess(p, c, h, r, px, fill, th * 0.34, foot, mix(0.55, 0.25, dis)) let a = Finish.cover(d, px) var out = alu_lip(p, c, h, r, px, px, 0.9) return Finish.over(out, vec4(col * a, a)) } mod.widgets.SliderFlat.height = 38.0 mod.widgets.SliderFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let tr = 8.0 let cy = size.y - tr - 1.0 let th = 6.0 let top = Finish.snap(o.y + cy - th * 0.5, px) - o.y let x0 = Finish.snap(o.x + 1.0, px) - o.x let w = Finish.snap(o.x + size.x - 1.0, px) - o.x - x0 let kx = x0 + tr + self.slide_pos * (w - 2.0 * tr) var out = alu_track(p, vec2(x0, top), w, th, px, kx, self.disabled) // engraved ticks above the track at an 8 point pitch, a long one every // fifth; each is one device-pixel column, #5e5e60 at 0.85 let span = w - 2.0 * tr let nt = max(floor(span / 8.0 + 0.5), 1.0) let pitch = span / nt let fi = floor((p.x - x0 - tr) / pitch + 0.5) let tx = Finish.snap(o.x + x0 + tr + fi * pitch, px) - o.x let major = 1.0 - step(0.3, abs(fract(fi / 5.0 + 0.5) - 0.5)) let tl = mix(2.5, 4.0, major) let ty = Finish.snap(o.y + top - 3.0, px) - o.y let ty0 = Finish.snap(o.y + top - 3.0 - tl, px) - o.y let in_x = step(-0.5, fi) * step(fi, nt + 0.5) let tick = step(tx, p.x) * (1.0 - step(tx + px, p.x)) * step(ty0, p.y) * (1.0 - step(ty, p.y)) * in_x let ta = tick * 0.85 * mix(1.0, 0.4, self.disabled) out = Finish.over(vec4(0.369 * ta, 0.369 * ta, 0.376 * ta, ta), out) let kf = alu_fit(vec2(kx, top + th * 0.5), o, tr, px) let kc = kf.xy let disc = alu_disc(p, kc, kf.z, px, 0.8, max(self.hover, self.focus) * live, self.drag * live, self.disabled) out = Finish.over(out, disc) return Finish.over(out, alu_focus(length(p - kc) - kf.z, px, self.focus * live)) } // The minimal slider: a hairline groove, a blue thread in it, and a small // turned cap. mod.widgets.SliderMinimal.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let cy = Finish.snap(o.y + size.y - 5.0, px) - o.y let tr = mix(4.0, 5.0, max(self.hover, self.drag)) let kx = tr + self.slide_pos * (size.x - 2.0 * tr) let inx = step(0.0, p.x) * step(p.x, size.x) // groove: two dark rows over one light row 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 lit = step(p.x, kx) * dark var out = vec4(0.0, 0.0, 0.0, 0.0) out = Finish.over(out, vec4(light * 0.6, light * 0.6, light * 0.6, light * 0.6)) let g = mix(vec3(0.243, 0.243, 0.243), mix(vec3(0.357, 0.655, 0.851), vec3(0.624, 0.702, 0.757), self.disabled), lit) out = Finish.over(out, vec4(g * dark, dark)) let kf = alu_fit(vec2(kx, cy - px), o, tr, px) let disc = alu_disc(p, kf.xy, kf.z, px, 0.55, self.hover * live, self.drag * live, self.disabled) out = Finish.over(out, disc) return Finish.over(out, alu_focus(length(p - kf.xy) - kf.z, px, self.focus * live)) } mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.5} mod.widgets.SliderRoundFlat.height = 14.0 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.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.5} // The round slider: a pill slot beside its label, a blue capsule in it and // a small turned cap on the capsule's end; the value reads on the slot. mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let x0 = Finish.snap(o.x + self.label_size, px) - o.x let y0 = Finish.snap(o.y, px) - o.y let hi = vec2(Finish.snap(o.x + size.x, px) - o.x, Finish.snap(o.y + size.y, px) - o.y - px) let h = (hi - vec2(x0, y0)) * 0.5 let c = vec2(x0, y0) + h let r = h.y let d = alu_rbox(p, c, h, r) let well = Finish.lift(mix(vec3(0.725, 0.729, 0.741), vec3(0.80, 0.80, 0.81), self.disabled), 0.12 * self.hover * live) let col = alu_recess(p, c, h, r, px, well, 2.5, vec3(0.282, 0.286, 0.294), mix(0.5, 0.25, self.disabled)) let a = Finish.cover(d, px) var out = alu_lip(p, c, h, r, px, px, 0.9) out = Finish.over(out, vec4(col * a, a)) // the capsule let ins = 2.0 let ch = vec2(h.x - ins, h.y - ins) let cr = ch.y let kx = x0 + ins + cr + self.slide_pos * (2.0 * ch.x - 2.0 * cr) let cap_lo = x0 + ins let cap_c = vec2((cap_lo + kx) * 0.5, c.y) let cap_h = vec2((kx - cap_lo) * 0.5 + cr * 0.0, ch.y) let cd = alu_rbox(p, vec2(cap_c.x + cr * 0.5, c.y), vec2(cap_h.x + cr * 0.5, ch.y), cr) let blue = mix(vec3(0.357, 0.655, 0.851), vec3(0.624, 0.702, 0.757), self.disabled) let bcol = alu_recess(p, vec2(cap_c.x + cr * 0.5, c.y), vec2(cap_h.x + cr * 0.5, ch.y), cr, px, blue, 1.5, vec3(0.224, 0.298, 0.357), 0.4) let ba = Finish.cover(cd, px) out = Finish.over(out, vec4(bcol * ba, ba)) let kf = alu_fit(vec2(kx, c.y), o, cr, px) let disc = alu_disc(p, kf.xy, kf.z, px, 0.55, self.hover * live, self.drag * live, self.disabled) out = Finish.over(out, disc) return Finish.over(out, alu_focus(d, px, self.focus * live)) } // --------------------------------------------------------------------------- // KNOBS. A turned cap on a dark body that shows as a chamfer at its foot, // a hard contact line, a faint ambient shade; an engraved pointer; the value // as a blue arc outside the cap, the unlit part a pale engraved track. let alu_knob = fn(p: vec2, c: vec2, rad: float, px: float, val: float, gap: float, hover: float, press: float, dis: float) -> vec4 { let dia = rad * 2.0 let hh = vec2(rad, rad) let live = 1.0 - dis var out = vec4(0.0, 0.0, 0.0, 0.0) let amb = Finish.drop(p, c, hh, rad, vec2(0.0, dia * 0.07), dia * 0.08) * 0.16 * mix(1.0, 0.3, dis) out = Finish.over(out, vec4(0.0, 0.0, 0.0, amb)) let con = Finish.drop(p, c, hh, rad, vec2(0.0, dia * 0.055), 0.5) * 0.7 * mix(1.0, 0.3, dis) out = Finish.over(out, vec4(vec3(0.035, 0.027, 0.031) * con, con)) // the body, showing as a chamfer below the cap let bc = c + vec2(0.0, dia * 0.045) let bd = length(p - bc) - rad let bt = clamp((p.y - bc.y) / rad, 0.0, 1.0) let body = mix(vec3(0.357, 0.357, 0.357), vec3(0.212, 0.212, 0.212), bt) let ba = Finish.cover(bd, px) out = Finish.over(out, vec4(body * ba, ba)) // the cap let q = p - c let d = length(q) - rad var face = g3(alu_conic(q, px, 1.0, 0.105 * hover)) face = Finish.lift(face, 0.15 * hover * live * (1.0 - press) - 0.05 * press) face = mix(face, mix(vec3(0.76, 0.765, 0.775), face, 0.35), dis) // the chrome ring: dark hairline, a bright band of 0.05 D, a dark // hairline, then a lit rim on the top inside of the face let up = 1.0 - smoothstep(-0.3, 0.3, q.y / rad) let bw = max(floor(dia * 0.05 / px) * px, 2.0 * px) let chrome = mix(vec3(0.961, 0.965, 0.973), vec3(0.80, 0.805, 0.815), smoothstep(-0.9, 0.9, q.y / rad)) face = mix(face, vec3(0.929, 0.929, 0.929), Finish.band(d, 2.0 * px + bw, 3.0 * px + bw, px) * up * 0.4 * live) face = mix(face, vec3(0.176, 0.180, 0.188), Finish.band(d, px + bw, 2.0 * px + bw, px) * mix(0.8, 0.35, dis)) face = mix(face, chrome, Finish.band(d, px, px + bw, px) * mix(1.0, 0.45, dis)) face = mix(face, vec3(0.212, 0.212, 0.220), Finish.band(d, 0.0, px, px) * mix(0.9, 0.35, dis)) // the pointer, engraved: a dark line with a light edge below it let ang = gap * 0.5 + (2.0 * PI - gap) * val let dir = vec2(-sin(ang), cos(ang)) let pa = c + dir * rad * 0.42 let pb = c + dir * rad * 0.84 let pl = alu_seg(p, pa, pb) - max(dia * 0.022, 0.6) let pl_lit = alu_seg(p - vec2(0.0, px), pa, pb) - max(dia * 0.022, 0.6) face = mix(face, vec3(0.97, 0.97, 0.98), Finish.cover(pl_lit, px) * 0.5 * live) face = mix(face, mix(vec3(0.314, 0.314, 0.322), vec3(0.55, 0.55, 0.56), dis), Finish.cover(pl, px)) let a = Finish.cover(d, px) return Finish.over(out, vec4(face * a, a)) } // The value arc: `ar` its middle radius, `aw` its width. let alu_arc = fn(p: vec2, c: vec2, ar: float, aw: float, px: float, val: 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) // round caps at both stops let e0 = c + vec2(-sin(start), cos(start)) * ar let e1 = c + vec2(-sin(start + sweep), cos(start + sweep)) * ar let v1 = c + vec2(-sin(start + sweep * val), cos(start + sweep * val)) * ar let cap = min(length(p - e0), length(p - e1)) - aw * 0.5 let track_d = min(mix(1000.0, rd, in_arc), cap) let lit_in = step(start, ang) * step(ang, start + sweep * val) let lit_d = min(mix(1000.0, rd, lit_in), min(length(p - e0), length(p - v1)) - aw * 0.5) var out = vec4(0.0, 0.0, 0.0, 0.0) let ta = Finish.cover(track_d, px) // the unlit part: a shallow groove, darker than the housing var tcol = vec3(0.690, 0.694, 0.706) tcol = mix(tcol, vec3(0.247, 0.251, 0.267), Finish.band(track_d, 0.0, px, px) * 0.35 * step(ar, length(q))) out = vec4(tcol * ta, ta) let la = Finish.cover(lit_d, px) let blue = mix(vec3(0.051, 0.631, 0.984), vec3(0.624, 0.702, 0.757), dis) return Finish.over(out, vec4(blue * la, la)) } mod.widgets.RotaryKnob.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let dia = min(size.x, size.y) let rr = dia * 0.5 let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + size.y - rr, px) - o.y) let gap = self.gap * PI / 180.0 // whole device pixels: the cap's radius, the arc's width and its inner edge let rad = floor(rr * 0.68 / px) * px let aw = max(floor(2.0 * rad * 0.055 / px + 0.5) * px, 2.0 * px) let ar = floor((rad + rr * 0.07) / px + 0.5) * px + aw * 0.5 var out = alu_arc(p, c, ar, aw, px, self.slide_pos, gap, self.disabled) let lifted = max(self.hover, max(self.focus, self.drag)) * live out = Finish.over(out, alu_knob(p, c, rad, px, self.slide_pos, gap, lifted, self.drag * live, self.disabled)) return Finish.over(out, alu_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live)) } mod.widgets.RotaryFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let room = vec2(size.x, size.y - 20.0) let dia = min(room.x, room.y) let rr = dia * 0.5 let c = vec2(Finish.snap(o.x + size.x * 0.5, px) - o.x, Finish.snap(o.y + 20.0 + room.y * 0.5, px) - o.y) let gap = self.gap * PI / 180.0 let rad = floor(rr * 0.7 / px) * px let aw = max(floor(2.0 * rad * 0.055 / px + 0.5) * px, 2.0 * px) let ar = floor((rad + rr * 0.08) / px + 0.5) * px + aw * 0.5 var out = alu_arc(p, c, ar, aw, px, self.slide_pos, gap, self.disabled) let lifted = max(self.hover, max(self.focus, self.drag)) * live out = Finish.over(out, alu_knob(p, c, rad, px, self.slide_pos, gap, lifted, self.drag * live, self.disabled)) return Finish.over(out, alu_focus(length(p - c) - ar - aw * 0.5, px, self.focus * live)) } // The dial's readout: a small window of dark glass in a thin chrome bezel, // a reflection across its top, amber digits with no halo. mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 2.35 bottom: 2.35} mod.widgets.RotaryFlat.text_input.min_height = 22.0 mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let d = alu_rbox(p, c, h, 2.5) let gd = d + 4.0 * px var col = vec3(0.039, 0.043, 0.063) // the reflection: a band across the top with a hard lower edge let yt = p.y - (c.y - h.y) - 4.0 * px col = col + g3(0.04 * step(0.0, yt) * step(yt, 3.0) * (1.0 - yt / 4.0)) col = mix(col, vec3(0.09, 0.095, 0.11), Finish.band(gd, 0.0, px, px)) // the bezel: dark, chrome, dark let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0) let chrome = mix(vec3(0.94, 0.94, 0.95), vec3(0.72, 0.73, 0.74), t) col = mix(col, vec3(0.05, 0.05, 0.06), Finish.band(d, 3.0 * px, 4.0 * px, px)) col = mix(col, chrome, Finish.band(d, px, 3.0 * px, px)) col = mix(col, vec3(0.212, 0.212, 0.220), Finish.band(d, 0.0, px, px)) let a = Finish.cover(d, px) return vec4(col * a, a) } mod.widgets.RotaryFlat.text_input.draw_text.color = #f2e198 mod.widgets.RotaryFlat.text_input.draw_text.color_hover = #f2e198 mod.widgets.RotaryFlat.text_input.draw_text.color_focus = #f2e198 mod.widgets.RotaryFlat.text_input.draw_text.color_empty = #8a7f55 // digits of 13 points from the tabular monospace that ships (no condensed // face does) mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 9.75 font_family: mod.theme.font_regular.font_family{ibm_plex_text := FontMember{res: crate_resource("makepad_widgets:resources/jetbrains_mono_variable.ttf") weight: 500.0 asc: 0.0 desc: 0.0}}} // --------------------------------------------------------------------------- // FADERS. The slider bench's flat bar handle in aluminium (the shape the // lacquer-red sheet's faders wore before; it took this sheet's crossfader block // in exchange), drawn by the cap engine (mod.sdf.Cap): a thin bar 12 points // along the travel and 0.88 of the fader across, 4 points high, its top a // dome along the track, level across, meeting the outline in a short // shoulder. It runs a 5 point slot held 8 points off the fader's ends, the // value a bar in his blue from the stop to the cap, twenty black ticks on // the far side, and it throws a soft shadow down and to the right. The // material and the inks are his crossfader preset number for number; the // ground under them is this sheet's housing, and the floor its metal // reflects is the bench's #8a8a8a. Under the hand or with focus the face // lifts toward white, as every face here does; held, the cap sinks half a // point, its shadow shortens and its face darkens a little; disabled, the // face fades toward the sheet's disabled metal, the slot greys and the // shadows thin. Focus also draws the sheet's flat blue ring round the cap. // The lit face is brushed along the bar's length, after his picture // (alu_brush). mod.widgets.SliderFader.cap_size = 12.0 mod.widgets.SliderFader.track_inset = 8.0 mod.widgets.SliderFader.draw_bg.track_size = uniform(5.0) mod.widgets.SliderFader.draw_bg.cap_cross = uniform(0.88) mod.widgets.SliderFader.clip_x = false mod.widgets.SliderFader.clip_y = false // The material, tunable live: the key light (x right, y down, z toward the // viewer) and its intensity; the highlight, its roughness and the sheen; the // rim toward grazing; the occlusion and how far up the walls it reaches; the // plain sky's reflection (no environment map) and how near the eye is over // the cap; metal; spun grain and glow; the exposure in EV and the highlight // roll-off; the cast shadow's strength and blur, the contact shadow and the // slot's inner shadow. mod.widgets.SliderFader.draw_bg.cap_light = uniform(vec3(-0.25, -0.65, 0.9)) mod.widgets.SliderFader.draw_bg.cap_light_intensity = uniform(1.0) mod.widgets.SliderFader.draw_bg.cap_specular = uniform(0.9) mod.widgets.SliderFader.draw_bg.cap_roughness = uniform(0.3) mod.widgets.SliderFader.draw_bg.cap_gloss = uniform(0.5) mod.widgets.SliderFader.draw_bg.cap_rim = uniform(0.3) mod.widgets.SliderFader.draw_bg.cap_ao = uniform(0.14) mod.widgets.SliderFader.draw_bg.cap_ao_reach = uniform(0.9) mod.widgets.SliderFader.draw_bg.cap_reflection = uniform(1.0) mod.widgets.SliderFader.draw_bg.cap_reflection_perspective = uniform(0.35) mod.widgets.SliderFader.draw_bg.cap_metal = uniform(1.0) mod.widgets.SliderFader.draw_bg.cap_spun = uniform(0.0) mod.widgets.SliderFader.draw_bg.cap_glow = uniform(0.0) mod.widgets.SliderFader.draw_bg.cap_exposure = uniform(0.0) mod.widgets.SliderFader.draw_bg.cap_rolloff = uniform(0.6) mod.widgets.SliderFader.draw_bg.cap_shadow = uniform(0.55) mod.widgets.SliderFader.draw_bg.cap_shadow_blur = uniform(9.0) mod.widgets.SliderFader.draw_bg.cap_contact = uniform(0.55) mod.widgets.SliderFader.draw_bg.cap_inner = uniform(0.54) // The inks, his: the body, the dark ink of a mark (none is painted), the // slot, the value's bar, the ticks, the light and the shade. mod.widgets.SliderFader.draw_bg.ground_color = uniform(theme.color_bg_app) mod.widgets.SliderFader.draw_bg.handle_color = uniform(#c9ccd0) mod.widgets.SliderFader.draw_bg.mark_color = uniform(#1f2226) mod.widgets.SliderFader.draw_bg.track_color = uniform(#949494) mod.widgets.SliderFader.draw_bg.fill_color = uniform(#70b5ff) mod.widgets.SliderFader.draw_bg.tick_color = uniform(#000000) mod.widgets.SliderFader.draw_bg.light_color = uniform(#f7f8fa) mod.widgets.SliderFader.draw_bg.shadow_color = uniform(#5c5f63) // The floor of the sky the metal mirrors: the bench's own ground for this // preset. At metal 1 and reflection 0.96 the cap is mostly that reflection. mod.widgets.SliderFader.draw_bg.sky_ground_color = uniform(#5a5d62) // Standing, the bar's lower half faces down the screen into the plain // sky's floor while a lying bar's halves face its horizon, so a standing // bar mirrors a lighter floor: the same satin aluminium, not chrome. mod.widgets.SliderFader.draw_bg.sky_ground_standing = uniform(#8a8d92) // How far the fader's quad reaches past its rect, in points: the throw of // the cap's cast shadow from its 4 point height along the key light, the // blur at the shadow's tip (the engine holds it between 0.35 and 1 times the // height), two points for the contact shadow's tail, and the glow's reach // when the cap emits. The cap itself stays inside: never wider than the // fader, stopped half a cap in from the track's ends. let alu_fader_room = fn(lgt: vec3, blur: float, glo: float) -> float { let ln = normalize(lgt) return 6.0 * 0.7 * length(ln.xy) / max(ln.z, 0.25) + clamp(blur, 2.1, 6.0) + 30.0 * step(0.001, glo) + 2.0 } // The fader's ground laid over the housing without painting it: the share of // the housing it darkens away and the light it puts back, premultiplied, so // the housing's own fall and grain show through the shadows and round the // slot. Exact for any colour; Cap.on_ground takes the ground away by the // darkest channel's share and puts back only what each channel has above // the ground, which loses a little of the ground in a channel lighter than // the others. let alu_on_ground = fn(col: vec3, gnd: vec3) -> vec4 { let dk = max(gnd - col, vec3(0.0, 0.0, 0.0)) / max(gnd, vec3(0.01, 0.01, 0.01)) let k = clamp(max(dk.x, max(dk.y, dk.z)), 0.0, 1.0) return vec4(max(col - gnd * (1.0 - k), vec3(0.0, 0.0, 0.0)), k) } // The flat bar's solid at `w` (cap-local points in the fader's frame), the // bench's curves with their handles resolved: along, a dome from the middle // to the ends with a long handle at the end, drawn from the centre to one // end and mirrored; across, level with a long handle; the edge level at the // full height, so the top is flat and the blend makes its shoulder. No // ribs, no mark, no cut. let alu_bar = fn(w: vec2, hs: vec2, fp: vec3, shape: vec4) -> vec4 { let c_along = Cap.mirror(Cap.curve(abs(w.x) / hs.x, 2.0, vec2(0.0, 1.0), vec2(0.5523, 0.0), vec2(0.0, 0.5523), vec2(1.0, 0.0), vec2(0.1667, -0.1667), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 0.0)), w.x) let c_across = Cap.curve(abs(w.y) / hs.y, 2.0, vec2(0.0, 1.0), vec2(0.7331, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0)) let c_edge = Cap.curve(Cap.edge_x(fp.x, hs, shape.y), 2.0, vec2(0.0, 1.0), vec2(0.0, 0.0), vec2(-0.1, 0.0), vec2(1.0, 1.0), vec2(0.1, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0)) return Cap.field(w, hs, fp, c_along, c_across, c_edge, vec2(1.0, 0.0), shape, vec4(0.0, 0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0), vec4(0.0, 0.0, 0.0, 0.0)) } // The bar's brushed grain, after his picture: one line per device pixel // across the travel, so every line runs the bar's length. `t` counts device // pixels from the cap's leading end, which sits on the pixel grid, so a line // is one whole pixel row (a column, lying) and rides with the cap; `s` is // the point along the line. Each line's brightness is hashed from its // index: over half the lines within a tenth of the metal's own, one in five // darker than 0.8 of it, the darkest 0.69; over it a slow drift of its own // along each line, up to about an eighth either way over a cell 24 points // long. A factor for the toned face; what passes white stays white. let alu_brush = fn(t: float, s: float) -> float { let i = floor(t) let k = 1.0 - Finish.hash(vec2(i, 283.0)) let drift = Finish.noise(vec2(s / 24.0 + 5.3, i + 41.0)) - 0.5 return 1.0 - 0.31 * k * k + 0.3 * drift } mod.widgets.SliderFader.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, alu_fader_room(self.cap_light, self.cap_shadow_blur, self.cap_glow)) 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 = alu_fader_room(self.cap_light, self.cap_shadow_blur, self.cap_glow) let sz = self.rect_size let o = self.rect_pos let p = self.pos * (sz + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let vert = self.is_vertical let live = 1.0 - self.disabled // track space: `a` along the travel from its zero (up the screen when the // fader stands), `b` across let a = mix(p.x, sz.y - p.y, vert) let b = mix(p.y, p.x, vert) let ab = vec2(a, b) let along = mix(sz.x, sz.y, vert) let across = mix(sz.y, sz.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 // The slot's walls and the cap's leading end (the left one lying, the top // one standing) on the device pixel grid: the slot's two walls antialias // alike, and the cap keeps one shape as it moves. let tw = self.track_size let oa = mix(o.y, o.x, vert) let mid = Finish.snap(oa + (across - tw) * 0.5, px) - oa + tw * 0.5 let hs = vec2(self.cap_px * 0.5, min(max(across * self.cap_cross, 4.0), across) * 0.5) let at_free = foot + self.slide_pos * travel let lead = Finish.snap(mix(o.x + at_free - hs.x, o.y + sz.y - at_free - hs.x, vert), px) let at = mix(lead - o.x + hs.x, o.y + sz.y - lead - hs.x, vert) // The key light on the screen, and turned into the fader's frame. let l = normalize(self.cap_light) let lt = Cap.light(l, vert) let hot = max(self.hover, self.focus) * live let press = self.drag * live let ground = self.ground_color.rgb let light_ink = self.light_color.rgb let shadow_ink = self.shadow_color.rgb let body = self.handle_color.rgb // THE GROUND: his slot, track_size wide with round ends, held track_inset // off the fader's ends; the value a bar from the stop (or the origin) to // the cap; twenty ticks on the far side. Disabled, the slot and the bar // grey. let tlen = max(along - self.inset_px * 2.0, 1.0) let tbox = vec4(self.inset_px + tlen * 0.5, mid, tlen * 0.5, tw * 0.5) let track_ink = mix(self.track_color.rgb, vec3(0.72, 0.72, 0.73), self.disabled) let fill_ink = mix(self.fill_color.rgb, vec3(0.70, 0.71, 0.72), self.disabled) var lo = foot + self.fill_lo * travel var hi = foot + self.fill_hi * travel if self.fill_lo < 0.0005 { lo = self.inset_px } if self.fill_hi > 0.9995 { hi = along - self.inset_px } var col = Cap.slot(ground, ab, tbox, tw * 0.5, 1.0, lo, hi, at, hs.x, lt, px, 0.0, 0.0, track_ink, fill_ink, light_ink, shadow_ink, self.cap_inner, self.cap_ao) col = Cap.ticks(col, ab, mid, foot, travel, 20.0, 1.0, 6.0, tw, 0.0, px, self.tick_color.rgb) // THE CAP, the bench's flat bar: corner 0.15 of the smaller half, edge // 0.3 of it met over 0.02, 4 points high, no perspective. let q = ab - vec2(at, mid) let shape = vec4(0.15, 0.3, 0.02, 6.0) let r = shape.x * min(hs.x, hs.y) // The top outline the cast shadow is thrown from: 0.6863 of the length, // the whole width, its corner by the bench's rule at this bar's size. let top_x = hs.x * 0.6863 let top_r = min(r + max(hs.x - top_x, 0.0), min(top_x, hs.y)) let top = vec3(0.6863, 1.0, top_r / max(min(top_x, hs.y), 0.01)) var cap = body var cov = 0.0 var under = 0.0 var cd = 1000.0 var brush = 1.0 // Only near the cap: the rest of a long track skips it. let far = abs(q) - hs if max(far.x, far.y) < m { let fp = Cap.sd_cap(q, hs, r, vec2(1.0, 0.0)) cd = fp.x if max(far.x, far.y) < px { let f = alu_bar(q, hs, fp, shape) let n = Cap.normal(f.yz, shape.w, vert) let v = Cap.view(q, hs, self.cap_reflection_perspective, vert) cap = Cap.shade(n, l, v, f.x, body, 0.0, 0.0, 1.0, self.cap_specular, self.cap_roughness, self.cap_gloss, self.cap_rim, self.cap_ao, self.cap_ao_reach, self.cap_reflection, self.cap_metal, self.cap_light_intensity, self.mark_color.rgb, light_ink, shadow_ink, mix(self.sky_ground_color.rgb, self.sky_ground_standing.rgb, vert)) if self.cap_spun > 0.001 { let grain = Cap.spun(q, hs, lt.xy) * self.cap_spun cap = Cap.in_shadow(Cap.in_light(cap, clamp(grain, 0.0, 1.0) * 0.55, light_ink), clamp(-grain, 0.0, 1.0) * 0.42, shadow_ink) } if self.cap_glow > 0.001 { cap = Cap.emit(cap, f.x, body, light_ink, self.cap_glow) } brush = alu_brush(mix(q.x + hs.x, hs.x - q.x, vert) / px, q.y) cov = Cap.cover(fp.x, px) } // Under it the cast shadow, thrown from the bar's top outline and // half a point shorter while the cap is pressed, and the contact // shadow; disabled, both thin as every shade here does. let rise = shape.w - 0.5 * press let dim = mix(1.0, 0.3, self.disabled) let sh = Cap.shadow(q, hs, r, top, lt, rise, self.cap_shadow_blur, self.cap_shadow * dim, px) let ct = Cap.contact(fp.x, max(rise * 0.3, 1.0), self.cap_contact * dim) under = clamp(sh + ct, 0.0, 1.0) } col = mix(col, col * shadow_ink, under) if self.cap_glow > 0.001 { col = col + body * Cap.glow(cd, hs, self.cap_glow) * 0.9 } // Toned as the bench tones its output. The ground laid over the housing, // premultiplied as every face here; the cap over it, opaque where it // covers, so the housing's grain never shows through the metal. let ex = pow(2.0, self.cap_exposure) var out = alu_on_ground(Cap.tone(col, ex, self.cap_rolloff), Cap.tone(ground, ex, self.cap_rolloff)) var face = Cap.tone(cap, ex, self.cap_rolloff) // The grain shows where the metal catches the light: full on the lit // face, gone on the dark lower band and the lip under it. let caught = smoothstep(0.45, 0.62, Finish.lum(face)) face = min(face * mix(1.0, brush, caught), vec3(1.0, 1.0, 1.0)) face = Finish.lift(face, 0.2 * hot * (1.0 - press) - 0.04 * press) face = mix(face, mix(vec3(0.788, 0.792, 0.800), face, 0.4), self.disabled) out = Finish.over(out, vec4(face * cov, cov)) return Finish.over(out, alu_focus(cd, px, self.focus * live)) } // --------------------------------------------------------------------------- // TEXT FIELDS. A milled pocket lighter than the housing: dark hairline // mouth, a short shadow under the top edge, the bright lip on the last row // of the box. The focus ring is drawn inside the box as a blue mouth, so a // field needs no room outside itself. let alu_field = fn(p: vec2, size: vec2, o: vec2, px: float, hover: float, focus: float, dis: float, r: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(0.0, px) let h = (hi - lo) * 0.5 let c = lo + h let d = alu_rbox(p, c, h, r) var well = mix(vec3(0.914, 0.918, 0.925), vec3(0.965, 0.965, 0.969), 0.35) well = Finish.lift(well, 0.25 * hover * (1.0 - dis)) well = mix(well, vec3(0.839, 0.843, 0.851), dis) let sh = Finish.inset(p, c, h, r, vec2(0.0, 1.1), 0.9) var col = mix(well, vec3(0.271, 0.271, 0.278), clamp(sh, 0.0, 1.0) * mix(0.35, 0.15, dis)) let low = smoothstep(-0.5, 0.5, p.y - c.y) // a bright hairline at the inner foot col = mix(col, vec3(0.992, 0.996, 1.0), Finish.band(d, px, 2.0 * px, px) * low * 0.8) let mouth = mix(vec3(0.247, 0.251, 0.267), vec3(0.28, 0.29, 0.30), low) col = mix(col, mouth, Finish.band(d, 0.0, px, px) * mix(mix(0.75, 0.95, hover), 0.35, dis)) // focus: the mouth turns blue and a second blue row lies inside it let blue = vec3(0.051, 0.631, 0.984) col = mix(col, blue, Finish.band(d, 0.0, 2.0 * px, px) * focus * 0.85) let a = Finish.cover(d, px) let lip = Finish.ring_out(d, 0.0, px, px) * low * 0.9 var out = vec4(0.980 * lip, 0.984 * lip, 0.992 * lip, lip) return Finish.over(out, vec4(col * a, a)) } mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 3.0 bottom: 3.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 alu_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 4.0) } mod.widgets.TextInputFlat.draw_text.color_empty = #66676b mod.widgets.TextInputFlat.draw_text.color_disabled = #8e8f93 mod.widgets.TextInputFlat.draw_cursor.color = #0da1fb mod.widgets.NumberField.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return alu_field(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, self.hover, self.focus, self.disabled, 4.0) } // The stepper: a raised brushed column, split by an engraved line. 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 lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(0.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(px, 2.0 * px) let h = (hi - lo) * 0.5 let c = lo + h let d = alu_rbox(p, c, h, 3.0) let live = 1.0 - self.disabled let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0) var face = alu_face(t, p + o, px) let upper = step(p.y, c.y) 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 face = Finish.lift(face, 0.04 * hot - 0.07 * dn) face = mix(face, mix(vec3(0.788, 0.792, 0.800), face, 0.4), self.disabled) let mid = Finish.snap(o.y + c.y, px) - o.y face = mix(face, vec3(0.247, 0.251, 0.267), step(mid - px, p.y) * step(p.y, mid) * 0.45) face = mix(face, vec3(0.99, 0.99, 1.0), step(mid, p.y) * step(p.y, mid + px) * 0.6) face = mix(face, vec3(0.99, 0.99, 1.0), Finish.band(d, px, 2.0 * px, px) * (1.0 - t) * 0.7) face = mix(face, vec3(0.247, 0.251, 0.267), Finish.band(d, 0.0, px, px) * 0.6) let a = Finish.cover(d, px) return vec4(face * a, a) } mod.widgets.NumberSpin.draw_text.color = #3c3d40 // --------------------------------------------------------------------------- // DROP-DOWNS. The chrome-ringed face with a chevron zone at the right, set // off by an engraved divider; the chevron is a stroked hairline angle. let alu_chevron = fn(p: vec2, c: vec2, px: float) -> float { let a = c + vec2(-3.5, -1.5) let b = c + vec2(0.0, 2.0) let e = c + vec2(3.5, -1.5) return min(alu_seg(p, a, b), alu_seg(p, b, e)) - 0.75 } mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 26.0 top: 3.0 bottom: 3.0} mod.widgets.DropDownFlat.clip_x = false mod.widgets.DropDownFlat.clip_y = false mod.widgets.DropDownFlat.draw_bg.vertex = fn() { let g = grow_vertex(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) let m = 6.0 let size = self.rect_size let o = self.rect_pos let held = max(self.down * self.hover, self.active) var out = alu_button(self.pos, size, o, px, self.hover, held, self.focus, self.disabled, 0.0, 0.0, 4.0, 1.0, m) let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let live = 1.0 - self.disabled let dx = Finish.snap(o.x + size.x - 19.0, px) - o.x let in_y = step(4.0, p.y) * step(p.y, size.y - 4.0) let div_d = step(dx - px, p.x) * step(p.x, dx) * in_y * 0.35 let div_l = step(dx, p.x) * step(p.x, dx + px) * in_y * 0.6 let dn = held * live * px out = Finish.over(out, vec4(0.247 * div_d, 0.251 * div_d, 0.267 * div_d, div_d)) out = Finish.over(out, vec4(0.99 * div_l, 0.99 * div_l, 1.0 * div_l, div_l)) let cc = vec2(size.x - 9.5, size.y * 0.5 + dn) let lit = Finish.cover(alu_chevron(p - vec2(0.0, px), cc, px), px) * 0.45 * live out = Finish.over(out, vec4(lit, lit, lit, lit)) let ch = Finish.cover(alu_chevron(p, cc, px), px) let ink = mix(vec3(0.235, 0.239, 0.251), vec3(0.557, 0.561, 0.576), self.disabled) return Finish.over(out, vec4(ink * ch, ch)) } mod.widgets.DropDown.draw_bg.color_disabled = #d3d4d6 // The combo box: the milled pocket with a brushed chevron key joined at its // right end. mod.widgets.ComboBox.padding = mod.turtle.Inset{left: 8.0 right: 26.0 top: 3.0 bottom: 3.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 = alu_field(p, size, o, px, self.hover, self.focus, self.disabled, 4.0) // the key let kx = Finish.snap(o.x + size.x - 19.0, px) - o.x let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(px, 2.0 * px) let klo = vec2(kx, Finish.snap(o.y, px) - o.y + px) let kh = (hi - klo) * 0.5 let kc = klo + kh let kd = max(alu_rbox(p, kc + vec2(2.0, 0.0), kh + vec2(2.0, 0.0), 3.0), -(p.x - kx)) let t = clamp((p.y - klo.y) / max(2.0 * kh.y, 1.0), 0.0, 1.0) let held = self.down * self.hover * live var face = alu_face(mix(t, 1.0 - t, held * 0.3), p + o, px) face = Finish.lift(face, 0.2 * self.hover * live * (1.0 - held) - 0.06 * held) face = mix(face, mix(vec3(0.788, 0.792, 0.800), face, 0.4), self.disabled) face = mix(face, vec3(0.99, 0.99, 1.0), step(klo.y, p.y) * step(p.y, klo.y + px) * 0.7) face = mix(face, vec3(0.247, 0.251, 0.267), step(kx, p.x) * step(p.x, kx + px) * 0.55) let ka = Finish.cover(kd, px) out = Finish.over(out, vec4(face * ka, ka)) let cc = vec2(size.x - 10.0, size.y * 0.5) let ch = Finish.cover(alu_chevron(p, cc, px), px) let ink = mix(vec3(0.235, 0.239, 0.251), vec3(0.557, 0.561, 0.576), self.disabled) return Finish.over(out, vec4(ink * ch, ch)) } // --------------------------------------------------------------------------- // SEGMENTS AND TABS. One recessed slot holding metal segments: the chosen // one stands up as a brushed face, the others are engraved words on the // well, split by engraved hairlines. let alu_slot = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, well: vec3) -> 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 - vec2(0.0, px) let h = (hi - lo) * 0.5 let c = lo + h let d = alu_rbox(p, c, h, r) let col = alu_recess(p, c, h, r, px, well, 2.4, vec3(0.282, 0.286, 0.294), 0.5) let a = Finish.cover(d, px) var out = alu_lip(p, c, h, r, px, px, 0.9) return Finish.over(out, vec4(col * a, a)) } // A brushed segment standing in a slot, `ins` in from its box. let alu_segment = fn(p: vec2, size: vec2, o: vec2, px: float, ins: float, r: float, hover: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(ins, ins) let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o - vec2(ins, ins) let h = (hi - lo) * 0.5 let c = lo + h let d = alu_rbox(p, c, h, r) let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0) var face = alu_face(t, p + o, px) face = Finish.lift(face, 0.2 * hover) let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y) face = mix(face, vec3(0.988, 0.988, 0.996), Finish.band(d, px, 2.0 * px, px) * mix(0.3, 0.85, up)) face = mix(face, vec3(0.247, 0.251, 0.267), Finish.band(d, 0.0, px, px) * 0.7) let con = Finish.drop(p, c, h, r, vec2(0.0, 0.6), 0.4) * 0.45 let a = Finish.cover(d, px) return Finish.over(vec4(0.0, 0.0, 0.0, con), vec4(face * a, a)) } mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return alu_slot(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 4.0, vec3(0.725, 0.729, 0.741)) } mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return alu_segment(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 2.0 * px, 3.0, 0.0) } mod.widgets.SegmentedControlFlat.draw_text.color = #3c3d40 mod.widgets.SegmentedControlFlat.draw_text_selected.color = #2e2f32 // The tab strip: the same slot; the tab in use is the raised segment, a // hovered one lifts a little, quiet ones are words on the well. mod.widgets.Tabs.height = 24.0 mod.widgets.Tabs.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return alu_slot(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 4.0, vec3(0.725, 0.729, 0.741)) } mod.widgets.Tabs.draw_tab.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } mod.widgets.Tabs.draw_tab_hover.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let h = self.rect_size * 0.5 - vec2(2.0 * px, 2.0 * px) let d = alu_rbox(p, self.rect_size * 0.5, h, 3.0) let a = Finish.cover(d, px) * 0.12 return vec4(a, a, a, a) } mod.widgets.Tabs.draw_tab_active.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return alu_segment(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 2.0 * px, 3.0, 0.0) } mod.widgets.Tabs.draw_sep.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let x = self.pos.x * self.rect_size.x let dk = step(x, px) * 0.35 let lt = step(px, x) * step(x, 2.0 * px) * 0.5 return vec4(0.247 * dk + lt, 0.251 * dk + lt, 0.267 * dk + lt, dk + lt) } mod.widgets.Tabs.draw_mark.color = #3c3d40 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 point stroke; the plus is the cross turned let a = mix(0.7853981634, 0.0, self.plus) let r = mix(4.2, 3.0, self.plus) let u = vec2(cos(a), sin(a)) * r let v = vec2(-sin(a), cos(a)) * r let dd = min(alu_seg(p, c - u, c + u), alu_seg(p, c - v, c + v)) - 0.5 let k = Finish.cover(dd, px) * self.color.a return vec4(self.color.rgb * k, k) } mod.widgets.Tabs.draw_text.text_style = mod.theme.font_regular{font_size: 7.5} mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_regular{font_size: 7.5} mod.widgets.Tabs.draw_text.color = #4a4b4f mod.widgets.Tabs.draw_text_active.color = #2e2f32 // --------------------------------------------------------------------------- // PROGRESS. The slider's track: a 6 point slot with the blue fill, its lit // top line under the inner shadow. The unknown value is shown by still // diagonal stripes across the whole fill: a face may not run a clock. mod.widgets.ProgressBarFlat.height = 7.0 mod.widgets.ProgressBarFlat.draw_bg.thickness = uniform(7.0) mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let w = self.rect_size.x let th = min(self.thickness, self.rect_size.y) - px let top = Finish.snap(o.y + (self.rect_size.y - th - px) * 0.5, px) - o.y let x0 = Finish.snap(o.x + w * self.start, px) - o.x let x1 = Finish.snap(o.x + w * self.end, px) - o.x let h = vec2((x1 - x0) * 0.5, th * 0.5) let c = vec2(x0, top) + h let r = h.y let d = alu_rbox(p, c, h, r) let dis = step(4.5, self.intent) var ink = vec3(0.357, 0.655, 0.851) if self.intent > 0.5 { ink = self.fill_color().rgb } ink = mix(ink, vec3(0.624, 0.702, 0.757), dis) let xv = x0 + (x1 - x0) * clamp(self.value, 0.0, 1.0) var filled = step(p.x, xv) * step(0.0, self.value) var stripe = 0.0 if self.value < 0.0 { filled = 1.0 stripe = Finish.stripes(p + o, 0.785398, 8.0, 0.5, px) } let grey = vec3(0.659, 0.663, 0.678) var fill = mix(grey, ink, filled) fill = Finish.lift(fill, 0.08 * stripe) let ytop = p.y - top fill = mix(fill, Finish.lift(ink, 0.35), filled * Finish.band(-ytop + 2.0 * px, 0.0, px, px) * 0.6) let foot = mix(vec3(0.282, 0.286, 0.294), vec3(0.224, 0.298, 0.357), filled) let col = alu_recess(p, c, h, r, px, fill, th * 0.34, foot, 0.55) let a = Finish.cover(d, px) * self.track var out = alu_lip(p, c, h, r, px, px, 0.9 * self.track) out = Finish.over(out, vec4(col * a, a)) return out * self.opacity } // --------------------------------------------------------------------------- // LEVEL METERS. A ladder of cells in a slot: lit cells run yellow through // orange to red with no hard zones, unlit cells are the well with a dark // top line. The over lamp is a red lens, dark 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 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 lamp_room = self.lamp * (across + 3.0) let bar = max(4.0, along - lamp_room) let h = vec2(bar * 0.5, across * 0.5 - px * 0.5) let c = h let r = 1.5 let d = alu_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) // cells: 3 points long with a point and a half between let pitch = 4.5 let cell = fract((a - pad) / pitch) let in_cell = step(cell, 3.0 / pitch) let lit = step(a, pad + span * self.level) * step(pad, a) * in_cell let ramp = mix(mix(vec3(0.914, 0.867, 0.396), vec3(0.929, 0.592, 0.196), smoothstep(0.55, 0.85, share)), vec3(0.906, 0.165, 0.039), smoothstep(0.85, 1.0, share)) let well = vec3(0.60, 0.605, 0.62) var fill = mix(well, ramp, lit) // unlit cells: the well a little darker, a dark hairline on their top let cell_row = in_cell * step(pad, a) * step(a, pad + span) fill = mix(fill, well * 0.95, cell_row * (1.0 - lit)) // a lit cell keeps a lighter line along its top let bt = b - (c.y - h.y) fill = mix(fill, Finish.lift(ramp, 0.3), lit * step(pad, bt) * step(bt, pad + px) * 0.7) var col = alu_recess(q, c, h, r, px, fill, 1.4, vec3(0.282, 0.286, 0.294), 0.45) // the hold mark: a dark hairline pair if self.hold > 0.002 { let at = pad + span * self.hold let hm = step(abs(a - at), px) * step(pad, b) * step(b, across - pad) col = mix(col, vec3(0.20, 0.20, 0.21), hm) } let ca = Finish.cover(d, px) var out = Finish.over(alu_lip(q, c, h, r, px, px, 0.8), vec4(col * ca, ca)) if self.lamp > 0.5 { // the lens is lit from above in the window, not along the track let la = bar + 3.0 + across * 0.5 let lb = across * 0.5 let lc = vec2(mix(la, lb, vert), mix(lb, self.rect_size.y - la, vert)) let lamp = alu_lamp(p, lc, across * 0.5 - px, px, vec3(0.972, 0.235, 0.239), vec3(0.353, 0.102, 0.110), step(0.5, self.over), 0.0) out = Finish.over(out, lamp) } return out * self.opacity } // --------------------------------------------------------------------------- // SCROLL BARS. A 9 point slot with a brushed 5 point thumb in it. mod.widgets.ScrollBar.bar_size = 9.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 * 0.5) let sc = sh let sd = alu_rbox(q, sc, sh, sh.y) let col = alu_recess(q, sc, sh, sh.y, px, vec3(0.725, 0.729, 0.741), 2.0, vec3(0.282, 0.286, 0.294), 0.4) let sa = Finish.cover(sd, px) var out = vec4(col * sa, sa) // the thumb let t0 = along * self.norm_scroll let tl = along * self.norm_handle let th = vec2(tl * 0.5 - 1.0, 2.5) let tc = vec2(t0 + tl * 0.5, across * 0.5 - px * 0.5) let td = alu_rbox(q, tc, th, 2.5) let tt = clamp((b - (tc.y - th.y)) / 5.0, 0.0, 1.0) var face = mix(vec3(0.925, 0.933, 0.941), vec3(0.788, 0.796, 0.808), tt) face = Finish.lift(face, 0.2 * self.hover - 0.05 * self.drag) face = mix(face, vec3(0.988, 0.988, 0.996), Finish.band(td, px, 2.0 * px, px) * (1.0 - tt) * 0.8) face = mix(face, vec3(0.247, 0.251, 0.267), Finish.band(td, 0.0, px, px) * 0.7) let con = Finish.drop(q, tc, th, 2.5, vec2(0.0, 0.6), 0.35) * 0.4 out = Finish.over(out, vec4(0.078 * con, 0.071 * con, 0.082 * con, con)) let ta = Finish.cover(td, px) out = Finish.over(out, vec4(face * ta, ta)) return out * self.opacity } // --------------------------------------------------------------------------- // VIEWS AND PANELS. An anodised plate: faint ambient shade and a contact // line outside it, a hairline edge, brushing and grain in the face, a lit // top rim. The sunken panel is a shallow pocket in the housing. let alu_plate = fn(p: vec2, size: vec2, o: vec2, px: float, r: float, sunken: float) -> vec4 { let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o let hi = vec2(Finish.snap(o.x + size.x, px), Finish.snap(o.y + size.y, px)) - o let h = (hi - lo) * 0.5 let c = lo + h let d = alu_rbox(p, c, h, r) var out = vec4(0.0, 0.0, 0.0, 0.0) let amb = Finish.drop(p, c, h, r, vec2(0.0, 1.5), 2.6) * 0.10 * (1.0 - sunken) out = Finish.over(out, vec4(0.0, 0.0, 0.0, amb)) let con = Finish.drop(p, c, h, r, vec2(0.0, 0.5), 0.5) * 0.35 * (1.0 - sunken) out = Finish.over(out, vec4(0.078 * con, 0.071 * con, 0.082 * con, con)) let lip = Finish.ring_out(d, 0.0, px, px) * smoothstep(-0.5, 0.5, p.y - c.y) * 0.8 * sunken out = Finish.over(out, vec4(lip, lip, lip, lip)) let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0) var face = mix(vec3(0.851, 0.851, 0.859), vec3(0.796, 0.796, 0.804), t) face = mix(face, mix(vec3(0.784, 0.784, 0.792), vec3(0.757, 0.757, 0.765), t), sunken) let s = Finish.brushed(p + o, vec2(1.0, 0.0), px * 1.4) - 0.5 face = face + g3(s * 0.02 + Finish.grain((p + o) / px, 0.0245)) // the sunken pocket: the housing's shadow under its top edge let sh = Finish.inset(p, c, h, r, vec2(0.0, 1.8), 1.6) face = mix(face, vec3(0.118, 0.122, 0.133), clamp(sh, 0.0, 1.0) * 0.45 * sunken) let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y) face = mix(face, vec3(1.0, 1.0, 1.0), Finish.band(d, px, 2.0 * px, px) * mix(0.2, 0.7, up) * (1.0 - sunken)) face = mix(face, vec3(0.247, 0.251, 0.267), Finish.band(d, 0.0, px, px) * mix(0.35, mix(0.3, 0.5, up), sunken)) 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 = grow_vertex(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 alu_plate(p, self.rect_size, self.rect_pos, px, 6.0, self.sunken) } // The card is the raised plate too; its palette and shadow numbers from // Rust are not read. mod.widgets.Card.radius = 6.0 mod.widgets.Card.draw_bg.vertex = fn() { let g = grow_vertex(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 = alu_plate(p, self.rect_size, self.rect_pos, px, 6.0, 0.0) let d = alu_rbox(p, self.rect_size * 0.5, self.rect_size * 0.5, 6.0) let l = Finish.cover(d, px) * (0.05 * self.hover - 0.05 * 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 = alu_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 split by a double hairline; the chosen row is a // shallow pocket with a small lit lamp at its head; a hovered row lifts. mod.widgets.ListItemOne.height = 22.0 mod.widgets.ListItem.padding = mod.turtle.Inset{left: 14.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 var out = vec4(0.0, 0.0, 0.0, 0.0) // hover and press let lay = (self.hover * 0.10 - self.down * 0.06) * live out = vec4(max(lay, 0.0), max(lay, 0.0), max(lay, 0.0), abs(lay)) // the chosen row if self.selected > 0.5 { // it ends 11 points short of the row's right edge, clear of the // 9 point scroll bar, so its rounded end and lip stay in view let h = (hi - lo) * 0.5 - vec2(7.5, 1.5) let c = lo + (hi - lo) * 0.5 - vec2(5.5, 0.5) let d = alu_rbox(p, c, h, 3.0) let col = alu_recess(p, c, h, 3.0, px, vec3(0.706, 0.710, 0.725), 2.0, vec3(0.282, 0.286, 0.294), 0.45) let a = Finish.cover(d, px) out = Finish.over(Finish.over(out, alu_lip(p, c, h, 3.0, px, px, 0.85)), vec4(col * a, a)) let lamp = alu_lamp(p, vec2(lo.x + 7.0, c.y), 2.0, px, vec3(0.506, 0.812, 0.992), vec3(0.173, 0.306, 0.412), live, 0.0) out = Finish.over(out, lamp) } // the double hairline under the row let yb = hi.y let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.10 let lt = step(yb - px, p.y) * step(p.y, yb) * 0.35 out = Finish.over(out, vec4(0.0, 0.0, 0.0, dk)) out = Finish.over(out, vec4(lt, lt, lt, lt)) return out } mod.widgets.ListItem.draw_text.color = #3c3d40 // The open list of a drop-down: an anodised plate; the row under the // pointer takes a faint blue wash, the chosen row its check. mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return alu_plate(self.pos * self.rect_size, self.rect_size, self.rect_pos, px, 4.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 c = self.rect_size * 0.5 let d = alu_rbox(p, c, c - vec2(1.0, 0.5), 3.0) let live = 1.0 - self.disabled let wash = Finish.cover(d, px) * 0.18 * self.hover * live var out = vec4(0.357 * wash, 0.655 * wash, 0.851 * wash, wash) let m0 = vec2(5.0, c.y) let m1 = vec2(7.5, c.y + 2.5) let m2 = vec2(11.5, c.y - 3.0) let mk = Finish.cover(min(alu_seg(p, m0, m1), alu_seg(p, m1, m2)) - 0.7, px) * self.active return Finish.over(out, vec4(0.235 * mk, 0.239 * mk, 0.251 * mk, mk)) } // The dock's tabs: the bar is the housing with a double hairline at its // foot; the tab in use is a brushed face rounded at the top, the others // engraved words that lift a little under the pointer. mod.widgets.Tab.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 3.0} mod.widgets.Tab.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let lo = vec2(Finish.snap(o.x, 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, 4.0) let h = (hi - lo) * 0.5 let c = lo + h let d = alu_rbox(p, c, h, 4.0) let t = clamp((p.y - lo.y) / max(self.rect_size.y - 2.0, 1.0), 0.0, 1.0) var face = alu_face(t * 0.6, p + o, px) face = mix(face, vec3(0.988, 0.988, 0.996), Finish.band(d, px, 2.0 * px, px) * 0.85 * step(p.y, lo.y + 3.0)) face = mix(face, vec3(0.247, 0.251, 0.267), Finish.band(d, 0.0, px, px) * 0.6) let a = Finish.cover(d, px) * self.active let lift = Finish.cover(d, px) * 0.10 * self.hover * (1.0 - self.active) return Finish.over(vec4(lift, lift, lift, lift), vec4(face * a, a)) } 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 var out = vec4(0.0, 0.0, 0.0, 0.04) let dk = step(yb - 2.0 * px, p.y) * step(p.y, yb - px) * 0.25 let lt = step(yb - px, p.y) * step(p.y, yb) * 0.5 out = Finish.over(out, vec4(0.0, 0.0, 0.0, dk)) return Finish.over(out, vec4(lt, lt, lt, lt)) } // --------------------------------------------------------------------------- // CHIPS AND BADGES. A chip is the 20 point button face (sunk into a lit // blue well while chosen); a badge is a small plate of dark glass. 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 = grow_vertex(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 p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let ch = min(20.0, self.rect_size.y) let dy = (self.rect_size.y - ch) * 0.5 let sz = vec2(self.rect_size.x, ch) let pp = (p - vec2(0.0, dy) + vec2(m, m)) / (sz + vec2(2.0 * m, 2.0 * m)) // chosen: the loud fill carries light ink let chosen = step(0.6, dot(self.ink.rgb, vec3(0.299, 0.587, 0.114))) if chosen < 0.5 { return alu_button(pp, sz, o + vec2(0.0, dy), px, self.hover, self.down, self.keyed, 0.0, 0.0, 0.0, 4.0, 1.0, m) } let q = p - vec2(0.0, dy) let oo = o + vec2(0.0, dy) let lo = vec2(Finish.snap(oo.x, px), Finish.snap(oo.y, px)) - oo let hi = vec2(Finish.snap(oo.x + sz.x, px), Finish.snap(oo.y + sz.y, px)) - oo - vec2(0.0, px) let h = (hi - lo) * 0.5 let c = lo + h let d = alu_rbox(q, c, h, 4.0) let fill = Finish.lift(vec3(0.357, 0.655, 0.851), 0.08 * self.hover - 0.08 * self.down) let col = alu_recess(q, c, h, 4.0, px, fill, 2.2, vec3(0.224, 0.298, 0.357), 0.55) let a = Finish.cover(d, px) var out = alu_lip(q, c, h, 4.0, px, px, 0.9) out = Finish.over(out, vec4(col * a, a)) return Finish.over(out, alu_focus(d, px, self.keyed)) } mod.widgets.BadgePalette.neutral = #1b1c20 mod.widgets.BadgePalette.on_neutral = #e9eaec mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_bold{font_size: 6.0} mod.widgets.Badge.draw_text.text_style = mod.theme.font_bold{font_size: 6.0} mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let o = self.rect_pos let bh = min(16.0, self.rect_size.y) let y0 = Finish.snap(o.y + (self.rect_size.y - bh) * 0.5, px) - o.y let lo = vec2(Finish.snap(o.x, px) - o.x, y0) let hi = vec2(Finish.snap(o.x + self.rect_size.x, px) - o.x, Finish.snap(o.y + y0 + bh, px) - o.y) let h = (hi - lo) * 0.5 let c = lo + h // a dot (as wide as tall and small) is round; a count is a plate let r = mix(3.0, h.y, step(self.rect_size.x, self.rect_size.y + 0.5) * step(self.rect_size.y, 12.5)) let d = alu_rbox(p, c, h, r) var col = vec3(0.106, 0.110, 0.125) // a faint reflection in the upper half of the glass let t = clamp((p.y - lo.y) / max(2.0 * h.y, 1.0), 0.0, 1.0) col = col + g3(0.025 * (1.0 - smoothstep(0.35, 0.5, t))) // the lit top rim, then the dark edge let up = 1.0 - smoothstep(-0.5, 0.5, p.y - c.y) col = mix(col, col + g3(0.06), Finish.band(d, px, 2.0 * px, px) * up) col = mix(col, vec3(0.047, 0.047, 0.055), Finish.band(d, 0.0, px, px) * 0.9) let a = Finish.cover(d, px) * self.color.a var out = alu_lip(p, c, h, r, px, px, 0.7) return Finish.over(out, vec4(col * a, a)) } // Rules between groups: one dark device row over one light one, never a // single grey line. 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.11 let lt = step(y, p.y) * (1.0 - step(y + px, p.y)) * 0.30 return Finish.over(vec4(0.0, 0.0, 0.0, dk), vec4(lt, lt, 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.11 let lt = step(x, p.x) * (1.0 - step(x + px, p.x)) * 0.30 return Finish.over(vec4(0.0, 0.0, 0.0, dk), vec4(lt, lt, lt, lt)) } // --------------------------------------------------------------------------- // THE HOUSING under everything: the window's own ground. 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) return alu_ground(self.pos * self.rect_size, self.rect_size, px) } // ---------------------------------------------------------------- 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, its quad grown as the slider's is so the thumbs keep // their shape. 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 = 16.0 mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() { let g = grow_vertex(self.rect_pos, self.rect_size, 6.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let m = 6.0 let size = self.rect_size let p = self.pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let live = 1.0 - self.disabled let tr = 8.0 let cy = size.y - tr - 1.0 let th = 6.0 let top = Finish.snap(o.y + cy - th * 0.5, px) - o.y let x0 = Finish.snap(o.x + 1.0, px) - o.x let w = Finish.snap(o.x + size.x - 1.0, px) - o.x - x0 let k0 = x0 + tr + self.start_pos * (w - 2.0 * tr) let k1 = x0 + tr + self.end_pos * (w - 2.0 * tr) // The track lit up to the far thumb, and left of the near thumb unlit. var out = mix(alu_track(p, vec2(x0, top), w, th, px, x0, self.disabled), alu_track(p, vec2(x0, top), w, th, px, k1, self.disabled), step(k0, p.x)) let span = w - 2.0 * tr let nt = max(floor(span / 8.0 + 0.5), 1.0) let pitch = span / nt let fi = floor((p.x - x0 - tr) / pitch + 0.5) let tx = Finish.snap(o.x + x0 + tr + fi * pitch, px) - o.x let major = 1.0 - step(0.3, abs(fract(fi / 5.0 + 0.5) - 0.5)) let tl = mix(2.5, 4.0, major) let ty = Finish.snap(o.y + top - 3.0, px) - o.y let ty0 = Finish.snap(o.y + top - 3.0 - tl, px) - o.y let in_x = step(-0.5, fi) * step(fi, nt + 0.5) let tick = step(tx, p.x) * (1.0 - step(tx + px, p.x)) * step(ty0, p.y) * (1.0 - step(ty, p.y)) * in_x let ta = tick * 0.85 * mix(1.0, 0.4, self.disabled) out = Finish.over(vec4(0.369 * ta, 0.369 * ta, 0.376 * ta, ta), out) let f0 = alu_fit(vec2(k0, top + th * 0.5), o, tr, px) let f1 = alu_fit(vec2(k1, top + th * 0.5), o, tr, px) out = Finish.over(out, alu_disc(p, f0.xy, f0.z, px, 0.8, max(self.hot_start, self.focus) * live, self.drag * self.hot_start * live, self.disabled)) out = Finish.over(out, alu_disc(p, f1.xy, f1.z, px, 0.8, max(self.hot_end, self.focus) * live, self.drag * self.hot_end * live, self.disabled)) return Finish.over(out, alu_focus(length(p - f1.xy) - f1.z, 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: 43.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 43.0 right: 3.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleRocker.draw_bg.size = uniform(18.0) mod.widgets.ToggleSlide.draw_bg.size = uniform(18.0) mod.widgets.ToggleRocker.min_height = 24.0 mod.widgets.ToggleSlide.min_height = 24.0 mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(1.8889) mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(1.8889) mod.widgets.ToggleRocker.draw_bg.knob_inset = uniform(0.0) mod.widgets.ToggleSlide.draw_bg.knob_inset = uniform(0.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 alu_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 alu_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 alu_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 { let f = alu_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 alu_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 = alu_led_lamp_full(ink) let hue = alu_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 = alu_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 polished chrome ring, as on brushed metal. // 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 = alu_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.792, 0.792, 0.800), max(room - 0.5, 2.0), vec3(0.80, 0.81, 0.83), 2.0) return res * self.opacity } true