use mod.prelude.widgets_internal.* // Pixel art: a pale green dot-matrix glass, every control framed in // square pixels. // Everything is drawn on a grid of big pixels, two device pixels square, // counted in window space so that every control shares one grid: a face // finds the big pixel a point lies in and tests its shape at that pixel's // centre, so every edge is a hard stair and no edge is ever soft. Controls // are caps: a frame of ink with its corners stepped off, a light row along // the top and left of the face, a shade row along the right and the foot, // a dark thickness under the face and a one-pixel shadow to the lower // right. A chosen cap is pushed in, dark, and lit by a small lamp. Wells // are dark and hold lit words. Panels are dithered. mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 10.0 right: 10.0 top: 3.0 bottom: 4.5} mod.widgets.ButtonFlat.min_height = 26.0 mod.widgets.ButtonFlat.spacing = 6.0 mod.widgets.Button.draw_bg.border_radius = 0.0 mod.widgets.Button.draw_bg.border_size = 0.67 // A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and // handed to each field below. Buttons stand at the same 26. let field_height = 26.0 let field_pad_v = 3.0 let field_radius = 0.0 let field_border = 0.67 // 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 = #b0b0ae // Plain views stop clipping, so a shadow pixel or a focus ring may fall // into the gap beside a control. Scroll views keep theirs. 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 and `b` one big pixel (two device pixels); `o` the // quad's origin in the window. // The palette: ink, the dark of wells and thickness, a second dark, the // deepest shade, the mid green of shade rows, the mute of a disabled face, // the glass, a cap's face, its face under the pointer and held, the light // row of a bevel, and the lit green of lamps and words on dark. let pa_ink = fn() -> vec3 { return vec3(0.051, 0.129, 0.090) } let pa_dark = fn() -> vec3 { return vec3(0.184, 0.282, 0.212) } let pa_dark2 = fn() -> vec3 { return vec3(0.243, 0.341, 0.267) } let pa_deep = fn() -> vec3 { return vec3(0.118, 0.200, 0.149) } let pa_mid = fn() -> vec3 { return vec3(0.369, 0.463, 0.341) } let pa_mute = fn() -> vec3 { return vec3(0.482, 0.569, 0.439) } let pa_ground = fn() -> vec3 { return vec3(0.478, 0.553, 0.400) } let pa_face = fn() -> vec3 { return vec3(0.510, 0.592, 0.431) } let pa_hi = fn() -> vec3 { return vec3(0.733, 0.796, 0.620) } let pa_down = fn() -> vec3 { return vec3(0.576, 0.659, 0.494) } let pa_light = fn() -> vec3 { return vec3(0.769, 0.827, 0.639) } let pa_lit = fn() -> vec3 { return vec3(0.839, 0.878, 0.698) } let pa_red = fn() -> vec3 { return vec3(0.420, 0.118, 0.094) } let pa_redd = fn() -> vec3 { return vec3(0.353, 0.110, 0.086) } let pa_g3 = fn(v: float) -> vec3 { return vec3(v, v, v) } // Premultiplied source-over, and a colour laid at a coverage. let pa_over = fn(dst: vec4, src: vec4) -> vec4 { return src + dst * (1.0 - src.a) } // THE SHELL. The light grey plastic of the handheld's shell: a faint // speckle and fainter blotches, a little lighter toward the top and // the left where the light falls. `w` is the point in window space. let pa_plastic = fn(w: vec2, px: float) -> vec3 { let base = vec3(0.690, 0.690, 0.686) let fine = Finish.grain(w / px, 0.02) let cq = floor(w / 6.0) let blot = fract(sin(dot(cq, vec2(12.9898, 78.233))) * 43758.5453) - 0.5 let band = 0.97 + 0.05 * (1.0 - clamp(w.y / 1000.0, 0.0, 1.0)) + 0.02 * (1.0 - clamp(w.x / 1400.0, 0.0, 1.0)) return base * band + vec3(fine, fine, fine) + vec3(0.014, 0.014, 0.014) * blot } // The glass of a screen at a point in window space: a dark olive, the dot // matrix (one device pixel row and column of every big pixel a shade // darker), a grain one device pixel fine; `u` the point on the screen from // 0 to 1, for the backlight: brighter toward the middle, dimmer in the // corners (the darkest the ink still reads on, the suite's own bar), and // a soft glow along the edges where the light enters. let pa_glass_at = fn(w: vec2, u: vec2, px: float) -> vec3 { let q = floor(w / px + vec2(0.01, 0.01)) let gx = step(0.5, fract(q.x * 0.5)) let gy = step(0.5, fract(q.y * 0.5)) let grid = max(gx, gy) var col = vec3(0.478, 0.553, 0.400) let v = u * 2.0 - vec2(1.0, 1.0) let r2 = clamp(dot(v, v) * 0.5, 0.0, 1.0) col = col * (1.12 - 0.16 * r2) let e = min(min(u.x, 1.0 - u.x), min(u.y, 1.0 - u.y)) col = col + vec3(0.07, 0.07, 0.03) * (1.0 - smoothstep(0.0, 0.08, e)) col = col * (1.0 - 0.05 * grid) col = col + pa_g3(Finish.grain(w / px, 0.018)) return col } // A screen set into the plastic. From the outside in: `shell` points of // the shell's plastic inside the box, grooved beside the slot; the slot, // a thin dark gap where the // glass panel sits in the plastic; the glass panel's printed border, // `border` points of flat mid grey, its outer edge catching a line of // light with a shadow under it only where it faces the light; and // the LCD window under the glass, // a black line at its edge and the panel's shadow falling in a few points // from every side, more from the top, the green brighter toward the middle // and glowing at the sides where the backlight enters; and a faint sheen // across the whole panel, border and window alike. let pa_lcd = fn(p: vec2, o: vec2, size: vec2, px: float, border: float, shell: float) -> vec4 { let w = p + o let lo = vec2(Finish.snap(o.x + shell, px), Finish.snap(o.y + shell, px)) - o let hi = vec2(Finish.snap(o.x + size.x - shell, px), Finish.snap(o.y + size.y - shell, px)) - o let hb = (hi - lo) * 0.5 let c = lo + hb let fb = Finish.snap(border, px) let h = hb - vec2(fb, fb) let ro = min(5.0, min(hb.x, hb.y)) let rg = max(ro - fb, 1.5) let qo = abs(p - c) - hb + vec2(ro, ro) let df = min(max(qo.x, qo.y), 0.0) + length(max(qo, vec2(0.0, 0.0))) - ro let qg = abs(p - c) - h + vec2(rg, rg) let dg = min(max(qg.x, qg.y), 0.0) + length(max(qg, vec2(0.0, 0.0))) - rg // which way the opening's edge faces here, for the light on the // edges let e = max(abs(p - c) - hb + vec2(ro, ro), vec2(0.0, 0.0)) let nrm = sign(p - c) * e / max(length(e), 0.0001) let lit = 0.0 - (0.6 * nrm.x + 0.8 * nrm.y) // the plastic of the shell inside the box, continuous with the ground, // a shade darker in a groove beside the slot, and no line on it let slot = 1.5 let inbox = step(0.0, p.x) * step(p.x, size.x) * step(0.0, p.y) * step(p.y, size.y) let pa = inbox * (1.0 - Finish.cover(df - slot, px)) var col = pa_plastic(w, px) col = col * mix(0.86, 1.0, smoothstep(slot, slot + 3.5, df)) var out = vec4(col * pa, pa) // the slot let sa = Finish.cover(df - slot, px) * (1.0 - Finish.cover(df, px)) out = pa_over(out, vec4(vec3(0.21, 0.21, 0.22) * sa, sa)) // the glass panel's border: the light comes from the top left and // from nowhere else, so only where the border's outer edge faces it, // along the top and the left, the edge rounds over in a soft bright // band two points wide and the border lies in shadow under that band, // gone six points in; the // foot and the right are the flat border; and the black line at the LCD let ba = Finish.cover(df, px) * (1.0 - Finish.cover(dg, px)) let from = max(lit, 0.0) let rim = 1.0 - smoothstep(0.0, 2.0, 0.0 - df) let shd = smoothstep(0.5, 2.5, 0.0 - df) * (1.0 - smoothstep(2.5, 6.5, 0.0 - df)) var bcol = vec3(0.360, 0.368, 0.384) + pa_g3(Finish.grain(w / px, 0.012)) bcol = bcol * (1.0 - shd * 0.50 * from) bcol = bcol + vec3(1.0, 1.0, 1.0) * rim * rim * 0.40 * from bcol = mix(bcol, vec3(0.06, 0.06, 0.06), Finish.band(0.0 - dg, 0.0, px, px)) out = pa_over(out, vec4(bcol * ba, ba)) // the LCD window: the panel's shadow falling in, more from the top, // and the green brighter toward the middle let ga = Finish.cover(dg, px) let gl = c - h let u = (p - gl) / max(2.0 * h, vec2(1.0, 1.0)) var gcol = pa_glass_at(w, u, px) let st = 1.0 - clamp((p.y - gl.y) / 7.0, 0.0, 1.0) let sa2 = 1.0 - clamp(-dg / 4.0, 0.0, 1.0) let sh = clamp(0.32 * st * st + 0.16 * sa2 * sa2, 0.0, 0.45) gcol = gcol * (1.0 - sh) out = pa_over(out, vec4(gcol * ga, ga)) // the glass over border and window alike: a faint sheen from the top // left, so the two read as one panel let sheen = (1.0 - clamp((u.x + u.y) * 0.5, 0.0, 1.0)) * 0.05 * Finish.cover(df, px) out = out + vec4(sheen, sheen, sheen, 0.0) return out } let pa_lay = fn(dst: vec4, col: vec3, a: float) -> vec4 { return pa_over(dst, vec4(col * a, a)) } let pa_grow = fn(rp: vec2, rs: vec2, m: float) -> vec4 { return vec4(rp.x - m, rp.y - m, rs.x + 2.0 * m, rs.y + 2.0 * m) } // The big pixel p lies in, counted in the window, and its centre in the // quad's own points. let pa_idx = fn(p: vec2, o: vec2, b: float) -> vec2 { return floor((p + o) / b + vec2(0.001, 0.001)) } let pa_cell = fn(p: vec2, o: vec2, b: float) -> vec2 { return (pa_idx(p, o, b) + vec2(0.5, 0.5)) * b - o } // A local coordinate moved to the nearest line of the grid. let pa_snap = fn(v: float, o: float, b: float) -> float { return floor((o + v) / b + 0.5) * b - o } // A box snapped to the grid: .xy the low corner, .zw the high one. let pa_sbox = fn(o: vec2, lo: vec2, hi: vec2, b: float) -> vec4 { return vec4(pa_snap(lo.x, o.x, b), pa_snap(lo.y, o.y, b), pa_snap(hi.x, o.x, b), pa_snap(hi.y, o.y, b)) } // The distance from a pixel centre to a box with its corners stepped off // by `notch` big pixels. let pa_dbox = fn(pc: vec2, sb: vec4, notch: float, b: float) -> float { let c = (sb.xy + sb.zw) * 0.5 let h = (sb.zw - sb.xy) * 0.5 return Finish.sd_chamfer(pc, c, h, notch * b * 1.2 + 0.001) } // Dithers on the big pixels: a checker, one pixel in four, and the ordered // 2 x 2 threshold (0.125 .. 0.875). let pa_checker = fn(p: vec2, o: vec2, b: float) -> float { let q = pa_idx(p, o, b) return step(0.5, fract((q.x + q.y) * 0.5)) } let pa_quarter = fn(p: vec2, o: vec2, b: float) -> float { let q = pa_idx(p, o, b) return step(0.5, fract(q.x * 0.5)) * step(0.5, fract(q.y * 0.5)) } let pa_bayer = fn(p: vec2, o: vec2, b: float) -> float { let q = pa_idx(p, o, b) let x = step(0.5, fract(q.x * 0.5)) let y = step(0.5, fract(q.y * 0.5)) return (mix(mix(0.0, 2.0, x), mix(3.0, 1.0, x), y) + 0.5) * 0.25 } // A ring of pixels outside a shape: the k-th ring out (0 touches it). let pa_ring_out = fn(d: float, k: float, b: float) -> float { return step(k * b, d) * step(d, (k + 1.0) * b) } // A RAISED CAP. `sb` its box on the grid; `notch` the corner step; `th` // the rows of thickness under its face; `face` its face, `hl` the light row // along its top and left, `sh` the shade row along its right and above its // foot, `base` the thickness, `rim` the frame; `shadow` the alpha of the // shadow one pixel to the lower right. Premultiplied. let pa_cap = fn(pc: vec2, b: float, sb: vec4, notch: float, th: float, face: vec3, hl: vec3, sh: vec3, base: vec3, rim: vec3, shadow: float) -> vec4 { let d = pa_dbox(pc, sb, notch, b) let inside = step(d, 0.0) let ds = pa_dbox(pc - vec2(b, b), sb, notch, b) let sa = step(ds, 0.0) * (1.0 - inside) * shadow let out = vec4(pa_ink() * sa, sa) let rim_a = inside * step(-b, d) let foot = sb.w - (th + 1.0) * b let band = inside * (1.0 - rim_a) * step(foot, pc.y) let top = step(pc.y, sb.y + 2.0 * b) let left = step(pc.x, sb.x + 2.0 * b) let right = step(sb.z - 2.0 * b, pc.x) let bot = step(foot - b, pc.y) let lit = max(top, left) let shd = max(right, bot) * (1.0 - lit) var col = face col = mix(col, hl, lit) col = mix(col, sh, shd) col = mix(col, base, band) col = mix(col, rim, rim_a) return pa_over(out, vec4(col * inside, inside)) } // A PUSHED-IN CAP, the chosen one of a group: sunk a pixel, dark, a deep // row under its top edge and down its left, and a small lamp lit under its // word (`lamp` 0 draws it unlit). let pa_sunk = fn(pc: vec2, b: float, sb: vec4, notch: float, face: vec3, rim: vec3, lamp: float, lampw: float) -> vec4 { let d = pa_dbox(pc, sb, notch, b) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) let top = step(pc.y, sb.y + 2.0 * b) let left = step(pc.x, sb.x + 2.0 * b) var col = face col = mix(col, pa_deep(), max(top, left)) // the lamp: a row of lit pixels over the bottom rim, the label's width // at most let cx = (sb.x + sb.z) * 0.5 let ly = step(sb.w - 3.0 * b, pc.y) * step(pc.y, sb.w - 2.0 * b) let la = step(abs(pc.x - cx), lampw * b) * ly * inside col = mix(col, mix(pa_dark2(), pa_lit(), lamp), la) col = mix(col, rim, rim_a) return vec4(col * inside, inside) } // The dotted focus ring: every other pixel of the second ring out. let pa_focus = fn(d: float, p: vec2, o: vec2, b: float) -> float { return pa_ring_out(d, 1.0, b) * pa_checker(p, o, b) } // A chevron pointing down, in whole pixels: 7 across and 4 deep, its top // row at `at` (a pixel centre); coverage of the pixel at pc. Rows 0 and 1 // are two arms two pixels thick, row 2 the three middle pixels, row 3 the // tip. let pa_chevron = fn(pc: vec2, at: vec2, b: float) -> float { let q = floor((pc - at) / b + vec2(0.5, 0.5)) let row = q.y let ax = abs(q.x) let arms = step(-0.5, row) * step(row, 1.5) * step(2.0 - row - 0.5, ax) * step(ax, 3.0 - row + 0.5) let r2 = step(abs(row - 2.0), 0.5) * step(ax, 1.5) let r3 = step(abs(row - 3.0), 0.5) * step(ax, 0.5) return max(arms, max(r2, r3)) } // --------------------------------------------------------------------------- // BUTTONS. One face for the family; the template picks the kind: // 0 the raised cap (frame 1 adds an ink keyline inside its light row, frame // 2 dithers its face); 1 the primary, a dark cap in an ink, lit and ink // frame with a lamp under its word; 2 the outline, a frame with no cap; // 3 the flat, bare until the pointer comes; 4 the warning cap, its // thickness and its word a dark red. // The quad is grown 4 points for the shadow and the focus ring. let pa_button_face = fn(p: vec2, o: vec2, size: vec2, b: float, kind: float, frame: float, hover: float, down: float, focus: float, disabled: float) -> vec4 { let dis = step(0.5, disabled) let live = 1.0 - dis let hov = step(0.5, hover) * live let held = step(0.5, down) * live let foc = step(0.5, focus) * live let sb = pa_sbox(o, vec2(0.0, 0.0), size, b) let pc = pa_cell(p, o, b) let flat = step(1.5, kind) * step(kind, 3.5) let primary = step(0.5, kind) * step(kind, 1.5) let danger = step(3.5, kind) var out = vec4(0.0, 0.0, 0.0, 0.0) // the cap drops a pixel while held let sbc = vec4(sb.x, sb.y + held * b * (1.0 - flat), sb.z, sb.w) let d = pa_dbox(pc, sbc, 1.0, b) if flat < 0.5 { let th = 1.0 - held var face = mix(mix(pa_face(), pa_hi(), hov), pa_down(), held) var hl = mix(pa_light(), pa_mute(), held) var sh = mix(pa_mute(), pa_down(), held) var base = mix(pa_deep(), pa_redd(), danger) face = mix(face, mix(mix(pa_dark(), pa_dark2(), hov), pa_deep(), held), primary) hl = mix(hl, mix(pa_lit(), pa_mid(), held), primary) sh = mix(sh, mix(pa_lit(), pa_dark(), held), primary) face = mix(face, pa_mute(), dis) hl = mix(hl, vec3(0.541, 0.627, 0.494), dis) sh = mix(sh, pa_mute(), dis) base = mix(base, pa_mid(), dis) let rim = mix(pa_ink(), pa_mid(), dis) out = pa_cap(pc, b, sbc, 1.0, th, face, hl, sh, base, rim, 0.30 * (1.0 - held) * live) // the face box: the cap above its thickness let foot = sbc.w - (th + 1.0) * b let df = pa_dbox(pc, vec4(sbc.x, sbc.y, sbc.z, foot), 1.0, b) // a keyline two pixels in: the primary's inner ink, the second // action's frame let second = step(0.5, frame) * step(frame, 1.5) let kl = step(-3.0 * b, df) * step(df, -2.0 * b) * max(primary, second) * step(pc.y, foot) out = pa_lay(out, mix(pa_ink(), pa_mid(), dis), kl) // the third action: its face dithered with the light row's colour let dith = step(1.5, frame) * step(df, -2.0 * b) * pa_checker(p, o, b) * step(pc.y, foot) out = pa_lay(out, mix(mix(pa_hi(), pa_light(), hov), pa_face(), held), dith * live) // the primary's lamp, in the thickness band under its word let cx = (sbc.x + sbc.z) * 0.5 let ly = step(sbc.w - 2.0 * b, pc.y) * step(pc.y, sbc.w - b) let la = step(abs(pc.x - cx), 3.0 * b) * ly * primary out = pa_lay(out, mix(mix(pa_lit(), vec3(0.910, 0.933, 0.839), hov), pa_mid(), dis), la) } else { let bare = step(2.5, kind) // the frame: always for the outline, under the pointer or held for // the flat let show = max(1.0 - bare, max(hov, held)) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) * show let inner = inside * step(d, -b) let da = inner * pa_checker(p, o, b) * hov * (1.0 - held) out = pa_lay(out, pa_light(), da) out = pa_lay(out, pa_down(), inner * held) let rcol = mix(mix(pa_ink(), pa_mid(), bare * (1.0 - held)), pa_mid(), dis) out = pa_lay(out, rcol, rim_a) // the outline's light row under its top frame, a hint of a cap let hlr = inner * step(pc.y, sbc.y + 2.0 * b) * (1.0 - bare) * (1.0 - held) out = pa_lay(out, pa_light(), hlr * 0.0) } let kf = pa_focus(pa_dbox(pc, sb, 1.0, b), p, o, b) * foc out = pa_lay(out, pa_ink(), kf) return out } // The wait spinner the stock face drew, which this one must draw too: eight // two-pixel blocks round the middle of the face, the lit one walking on the // button's own animation clock. let pa_wait = fn(p: vec2, o: vec2, c: vec2, b: float, anim: float, ink: vec3, ghost: vec3) -> vec4 { let pc = pa_cell(p, o, b) let v = pc - c var th = atan2(v.x, -v.y) if th < 0.0 { th = th + 2.0 * PI } let k = floor(th / (PI * 0.25) + 0.5) let ang = k * PI * 0.25 let dir = vec2(sin(ang), -cos(ang)) let q = v - dir * 4.0 * b let a = step(max(abs(q.x), abs(q.y)), b * 1.01) let head = floor(fract(anim) * 8.0) let age = fract((head - k) / 8.0 + 1.0) let lit = step(age, 0.2) let col = mix(ghost, ink, lit) return vec4(col * a, a) } let pa_button = fn(pos: vec2, size: vec2, o: vec2, px: float, kind: float, frame: float, hover: float, down: float, focus: float, disabled: float, loading: float, anim: float, m: float) -> vec4 { let b = 2.0 * px let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) var out = pa_button_face(p, o, size, b, kind, frame, hover, down, focus, disabled) if loading > 0.001 { let c = vec2(pa_snap(size.x * 0.5, o.x, b), pa_snap(size.y * 0.5 - b, o.y, b)) let dark = step(0.5, kind) * step(kind, 1.5) let ink = mix(pa_ink(), pa_lit(), dark) let ghost = mix(pa_mute(), pa_dark2(), dark) let w = pa_wait(p, o, c, b, anim, ink, ghost) out = pa_over(out, w * loading) } return out } mod.widgets.ButtonFlat.draw_bg.kind = uniform(3.0) mod.widgets.ButtonFlat.draw_bg.frame = uniform(0.0) mod.widgets.ButtonFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ButtonFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return self.host_over(pa_button(self.pos, self.rect_size, self.rect_pos, px, self.kind, self.frame, self.hover, self.down, self.focus, self.disabled, self.loading, self.anim_time, 4.0), 4.0) } mod.widgets.ButtonFlat.clip_x = false mod.widgets.ButtonFlat.clip_y = false // Inks: ink on the glass and on a cap. mod.widgets.ButtonFlat.draw_text.color = #0d2117 mod.widgets.ButtonFlat.draw_text.color_hover = #0d2117 mod.widgets.ButtonFlat.draw_text.color_down = #0d2117 mod.widgets.ButtonFlat.draw_text.color_focus = #0d2117 mod.widgets.ButtonFlat.draw_text.color_disabled = #435c42 mod.widgets.ButtonFlat.draw_text.text_style = mod.theme.font_label{} mod.widgets.ButtonFlat.draw_icon.color = #0d2117 mod.widgets.ButtonFlat.icon_walk.width = 14.0 // The caps: the standard key and everything cut from it. They are handed a // draw object of their own, since most of them share the flat button's. let pa_cap_bg = mod.widgets.ButtonFlat.draw_bg{ kind: 0.0 frame: 0.0 } let pa_capped = fn(w) { w.draw_bg = pa_cap_bg } pa_capped(mod.widgets.Button) pa_capped(mod.widgets.ButtonGradientX) pa_capped(mod.widgets.ButtonGradientY) pa_capped(mod.widgets.ButtonIcon) pa_capped(mod.widgets.ButtonGradientXIcon) pa_capped(mod.widgets.ButtonGradientYIcon) pa_capped(mod.widgets.ButtonXs) pa_capped(mod.widgets.ButtonSm) pa_capped(mod.widgets.ButtonLg) pa_capped(mod.widgets.ButtonXl) pa_capped(mod.widgets.ButtonCompound) // The primary: the dark cap, lit words, the one dark key of a row. mod.widgets.ButtonPrimary.draw_bg.kind = 1.0 mod.widgets.ButtonPrimary.draw_text.color = #d6e0b2 mod.widgets.ButtonPrimary.draw_text.color_hover = #e8eed6 mod.widgets.ButtonPrimary.draw_text.color_down = #c0cf9f mod.widgets.ButtonPrimary.draw_text.color_focus = #d6e0b2 mod.widgets.ButtonPrimary.draw_text.color_disabled = #3e5744 mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_label{} mod.widgets.ButtonPrimary.draw_icon.color = #d6e0b2 // The second action: the cap with an ink keyline inside its light row. mod.widgets.ButtonSecondary.draw_bg.kind = 0.0 mod.widgets.ButtonSecondary.draw_bg.frame = 1.0 mod.widgets.ButtonSecondary.draw_text.color = #0d2117 mod.widgets.ButtonSecondary.draw_text.color_hover = #0d2117 mod.widgets.ButtonSecondary.draw_text.color_down = #0d2117 mod.widgets.ButtonSecondary.draw_text.color_focus = #0d2117 mod.widgets.ButtonSecondary.draw_text.color_disabled = #435c42 mod.widgets.ButtonSecondary.draw_icon.color = #0d2117 // The tonal action: the cap with a dithered face. mod.widgets.ButtonTertiary.draw_bg.kind = 0.0 mod.widgets.ButtonTertiary.draw_bg.frame = 2.0 mod.widgets.ButtonTertiary.draw_text.color = #0d2117 mod.widgets.ButtonTertiary.draw_text.color_hover = #0d2117 mod.widgets.ButtonTertiary.draw_text.color_down = #0d2117 mod.widgets.ButtonTertiary.draw_text.color_focus = #0d2117 mod.widgets.ButtonTertiary.draw_text.color_disabled = #435c42 mod.widgets.ButtonTertiary.draw_icon.color = #0d2117 // The destructive action: the cap with a dark red thickness and word, the // one other colour of the sheet and only where it means a warning. mod.widgets.ButtonDanger.draw_bg.kind = 4.0 mod.widgets.ButtonDanger.draw_bg.frame = 0.0 mod.widgets.ButtonDanger.draw_text.color = #6b1e18 mod.widgets.ButtonDanger.draw_text.color_hover = #6b1e18 mod.widgets.ButtonDanger.draw_text.color_down = #6b1e18 mod.widgets.ButtonDanger.draw_text.color_focus = #6b1e18 mod.widgets.ButtonDanger.draw_text.color_disabled = #435c42 mod.widgets.ButtonDanger.draw_icon.color = #6b1e18 // The outline: a frame of ink round the glass, no cap. mod.widgets.ButtonOutline.draw_bg.kind = 2.0 mod.widgets.ButtonOutline.draw_bg.frame = 0.0 mod.widgets.ButtonOutline.draw_text.color = #0d2117 mod.widgets.ButtonOutline.draw_text.color_hover = #0d2117 mod.widgets.ButtonOutline.draw_text.color_down = #0d2117 mod.widgets.ButtonOutline.draw_text.color_focus = #0d2117 mod.widgets.ButtonOutline.draw_text.color_disabled = #435c42 mod.widgets.ButtonOutline.draw_icon.color = #0d2117 mod.widgets.ButtonDashed.draw_bg.kind = 2.0 // The flatter faces: bare until the pointer comes. mod.widgets.ButtonFlatter.draw_bg.kind = 3.0 mod.widgets.ButtonFlatter.draw_bg.frame = 0.0 // The size rungs keep the family's padding; a small key is 20 tall, a // large one 32. mod.widgets.ButtonSm.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 1.5 bottom: 3.0} mod.widgets.ButtonSm.min_height = 21.33 mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 1.0 bottom: 2.5} mod.widgets.ButtonXs.min_height = 18.67 mod.widgets.ButtonLg.padding = mod.turtle.Inset{left: 13.0 right: 13.0 top: 3.0 bottom: 5.0} mod.widgets.ButtonLg.min_height = 32.0 mod.widgets.ButtonXl.padding = mod.turtle.Inset{left: 16.0 right: 16.0 top: 4.0 bottom: 6.0} mod.widgets.ButtonXl.min_height = 37.33 mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 6.0 right: 6.0 top: 3.0 bottom: 4.5} // HELD STATES. The stock tracks write the pointer's hover and the press as // snapped values, and a snapped value never arrives when a state is held // from the start (a still page, a restored screen). The same states with // plain values and a snapped entry look and move the same under a live // pointer, and hold. let pa_held_states = fn(w) { w.animator.hover.on.from = {all: Snap} w.animator.hover.on.apply = {draw_bg: {down: 0.0, hover: 1.0} draw_text: {down: 0.0, hover: 1.0}} w.animator.hover.down.from = {all: Snap} w.animator.hover.down.apply = {draw_bg: {down: 1.0, hover: 1.0} draw_text: {down: 1.0, hover: 1.0}} } pa_held_states(mod.widgets.ButtonFlat) pa_held_states(mod.widgets.CheckBoxFlat) pa_held_states(mod.widgets.ToggleFlat) pa_held_states(mod.widgets.RadioButtonFlat) pa_held_states(mod.widgets.RadioButtonTabFlat) pa_held_states(mod.widgets.TextInputFlat) pa_held_states(mod.widgets.DropDownFlat) // --------------------------------------------------------------------------- // CHECK BOXES. A box ten pixels square: an ink frame with its corners // stepped off, a light well inside with a shade row under its top and down // its left, and a lit lip under its foot where the cut catches the light. // On, the well is dark and holds a lit pixel check; mixed, a lit bar. mod.widgets.CheckBoxFlat.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 5.0 bottom: 5.0} mod.widgets.CheckBoxFlat.label_walk.margin = mod.turtle.Inset{left: 20.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.CheckBoxFlat.draw_bg.size = uniform(13.34) mod.widgets.CheckBoxFlat.draw_bg.border_size = uniform(0.67) mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(0.0) mod.widgets.CheckBoxFlat.draw_text.color_error = #0d2117 // The mark box of a check box, radio or toggle, on the grid. let pa_markbox = fn(size: vec2, o: vec2, w: float, hgt: float, at_end: float, b: float) -> vec4 { let x = (size.x - w) * at_end let y0 = pa_snap(size.y * 0.5 - hgt * 0.5, o.y, b) let x0 = pa_snap(x, o.x, b) return vec4(x0, y0, x0 + floor(w / b + 0.5) * b, y0 + floor(hgt / b + 0.5) * b) } // A segment from a to b: the distance to it. let pa_seg = fn(p: vec2, a: vec2, c: vec2) -> float { let pa = p - a let ba = c - a let t = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0) return length(pa - ba * t) } // A sunk well of pixels (check boxes, radio wells): its fill, a shade row // along the top and left inside the frame, a lit lip outside its foot. mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let dis = step(0.5, self.disabled) let live = 1.0 - dis let hov = step(0.5, self.hover) * live let held = step(0.5, self.down) * live let sb = pa_markbox(self.rect_size, o, self.size, self.size, self.mark_at_end, b) let pc = pa_cell(p, o, b) let d = pa_dbox(pc, sb, 1.0, b) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) let on = step(0.5, self.active) * (1.0 - step(0.5, self.mixed)) let mixd = step(0.5, self.mixed) let dark = max(on, mixd) var out = vec4(0.0, 0.0, 0.0, 0.0) // the lit lip under the foot and down the right, outside the frame let lip = pa_ring_out(d, 0.0, b) * max(step(sb.w, pc.y), step(sb.z, pc.x)) * live out = pa_lay(out, pa_light(), lip) // the well var col = mix(mix(pa_face(), pa_hi(), hov), pa_down(), held) col = mix(col, mix(mix(pa_dark(), pa_dark2(), hov), pa_deep(), held), dark) let tl = step(min(pc.x - sb.x, pc.y - sb.y), 2.0 * b) col = mix(col, mix(mix(pa_mute(), pa_mid(), held), pa_deep(), dark), tl) // disabled: a dithered mute let dcol = mix(pa_mute(), vec3(0.541, 0.627, 0.494), pa_checker(p, o, b)) col = mix(col, dcol, dis) col = mix(col, mix(pa_ink(), pa_mid(), dis), rim_a) out = pa_lay(out, col, inside) // the check: a stepped stroke two pixels thick, ten pixels to the box let u = (pc - sb.xy) / max(sb.zw - sb.xy, vec2(b, b)) * 10.0 // the check's pixels, one row mask per line of the ten: rows 2 to 6 let ci = floor(u.x) let cj = floor(u.y) var mask = 0.0 mask = mix(mask, 128.0, step(abs(cj - 2.0), 0.5)) mask = mix(mask, 192.0, step(abs(cj - 3.0), 0.5)) mask = mix(mask, 100.0, step(abs(cj - 4.0), 0.5)) mask = mix(mask, 60.0, step(abs(cj - 5.0), 0.5)) mask = mix(mask, 24.0, step(abs(cj - 6.0), 0.5)) let bit = step(0.5, fract(floor(mask / pow(2.0, ci) + 0.001) * 0.5)) let ck = bit * on * (1.0 - rim_a) * step(0.0, ci) * step(ci, 9.0) let bar = step(2.0, u.x) * step(u.x, 8.0) * step(4.0, u.y) * step(u.y, 6.0) * mixd let mk = max(ck, bar) out = pa_lay(out, mix(mix(pa_lit(), pa_light(), held), pa_mid(), dis), mk) // an error doubles the frame let er = pa_ring_out(d, 0.0, b) * step(0.5, self.error) out = pa_lay(out, pa_ink(), er) let kf = pa_focus(d, p, o, b) * step(0.5, self.focus) * live out = pa_lay(out, pa_ink(), kf) return out } mod.widgets.CheckBoxFlat.clip_x = false mod.widgets.CheckBoxFlat.clip_y = false // TOGGLES. A dark slot in an ink frame, two cells long; the knob is a // light cap standing in one cell, and the other cell holds a small lamp, // lit when the switch is on. mod.widgets.ToggleFlat.label_walk.margin = mod.turtle.Inset{left: 39.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleFlat.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 4.0 bottom: 4.0} mod.widgets.ToggleFlat.draw_bg.size = uniform(16.0) mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.0) mod.widgets.ToggleFlat.draw_bg.knob_inset = uniform(1.33) mod.widgets.ToggleFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ToggleFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let dis = step(0.5, self.disabled) let live = 1.0 - dis let hov = step(0.5, self.hover) * live let held = step(0.5, self.down) * live let tw = self.size * self.pill_aspect let sb = pa_markbox(self.rect_size, o, tw, self.size, self.mark_at_end, b) let pc = pa_cell(p, o, b) let d = pa_dbox(pc, sb, 1.0, b) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) let t = step(0.5, mix(self.active, self.drag_pos, self.drag)) var out = vec4(0.0, 0.0, 0.0, 0.0) // the lit lip under the foot let lip = pa_ring_out(d, 0.0, b) * step(sb.w, pc.y) * live out = pa_lay(out, pa_light(), lip) // the slot: dark, a deep row under its top edge; dithered under the // pointer var col = mix(pa_dark(), pa_deep(), step(pc.y, sb.y + 2.0 * b)) col = mix(col, pa_dark2(), hov * pa_checker(p, o, b)) col = mix(col, mix(pa_mute(), pa_mid(), pa_checker(p, o, b)), dis) col = mix(col, mix(pa_ink(), pa_mid(), dis), rim_a) out = pa_lay(out, col, inside) // the knob: a cap in one half, a pixel in from the frame let hw = (sb.z - sb.x) * 0.5 let kx = sb.x + t * hw let kb = vec4(kx + b, sb.y + b, kx + hw - b * 0.0 + b * 0.0, sb.w - b) let kb2 = vec4(kb.x, kb.y, kx + hw, kb.w) let kface = mix(mix(pa_light(), pa_lit(), hov), pa_down(), held) let khl = mix(pa_lit(), pa_mute(), held) let ksh = mix(pa_mute(), pa_down(), held) let kf2 = mix(kface, vec3(0.604, 0.678, 0.541), dis) let knob = pa_cap(pc, b, kb2, 1.0, 1.0 - held, kf2, mix(khl, kf2, dis), mix(ksh, pa_mute(), dis), mix(pa_mid(), pa_mute(), dis), mix(pa_ink(), pa_mid(), dis), 0.0) out = pa_over(out, knob) // the lamp in the free half: two by two pixels, lit when on let lx = mix(sb.z - hw * 0.5, sb.x + hw * 0.5, t) let lc = vec2(pa_snap(lx, o.x, b), (sb.y + sb.w) * 0.5) let lq = abs(pc - lc) let la = step(max(lq.x, lq.y), b) * (1.0 - step(0.5, knob.a)) let lcol = mix(pa_mid(), pa_lit(), t * live) out = pa_lay(out, lcol, la) let el = pa_ring_out(d, 0.0, b) * step(0.5, self.error) out = pa_lay(out, pa_ink(), el) let kf = pa_focus(d, p, o, b) * step(0.5, self.focus) * live out = pa_lay(out, pa_ink(), kf) return out } mod.widgets.ToggleFlat.clip_x = false mod.widgets.ToggleFlat.clip_y = false // RADIOS. A pixel circle ten across: an ink ring, a light well with a shade // arc under its top and left, a lit lip under its foot. Chosen, a dark disc // in the well with a lit two by two lamp in its middle. mod.widgets.RadioButtonFlat.padding = mod.turtle.Inset{left: 0.0 right: 8.0 top: 5.0 bottom: 5.0} mod.widgets.RadioButtonFlat.label_walk.margin = mod.turtle.Inset{left: 20.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.RadioButtonFlat.draw_bg.size = uniform(13.34) mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let dis = step(0.5, self.disabled) let live = 1.0 - dis let hov = step(0.5, self.hover) * live let held = step(0.5, self.down) * live let sb = pa_markbox(self.rect_size, o, self.size, self.size, 0.0, b) let c = (sb.xy + sb.zw) * 0.5 let rad = (sb.z - sb.x) * 0.5 - 0.15 * b let pc = pa_cell(p, o, b) let v = pc - c let r = length(v) let d = r - rad let inside = step(d, 0.0) let rim_a = inside * step(-b, d) let on = step(0.5, self.active) var out = vec4(0.0, 0.0, 0.0, 0.0) let lip = step(rad, r) * step(r, rad + b) * step(0.35 * rad, v.y) * live out = pa_lay(out, pa_light(), lip) var col = mix(mix(pa_face(), pa_hi(), hov), pa_down(), held) // the shade arc: the ring inside the frame, on its upper left half let arc = step(-2.0 * b, d) * step(v.x + v.y, 0.0) col = mix(col, mix(pa_mute(), pa_mid(), held), arc) let dcol = mix(pa_mute(), vec3(0.541, 0.627, 0.494), pa_checker(p, o, b)) col = mix(col, dcol, dis) col = mix(col, mix(pa_ink(), pa_mid(), dis), rim_a) out = pa_lay(out, col, inside) // the chosen disc and its lamp let disc = step(r, rad - 1.9 * b) * on out = pa_lay(out, mix(mix(pa_dark(), pa_deep(), held), pa_mid(), dis), disc) let lq = abs(v) let lamp = step(max(lq.x, lq.y), b) * on out = pa_lay(out, mix(pa_lit(), pa_light(), dis), lamp) let kf = step(rad + b, r) * step(r, rad + 2.0 * b) * pa_checker(p, o, b) * step(0.5, self.focus) * live out = pa_lay(out, pa_ink(), kf) return out } mod.widgets.RadioButtonFlat.clip_x = false mod.widgets.RadioButtonFlat.clip_y = false // A caption cap, the cell of a tab row or a radio tab: raised at rest, the // light row dithered under the pointer; chosen or held, pushed in and dark // with its lamp lit. let pa_caption = fn(p: vec2, o: vec2, sb: vec4, b: float, chosen: float, hover: float, held: float, focus: float, disabled: float, lampw: float) -> vec4 { let dis = step(0.5, disabled) let live = 1.0 - dis let hov = step(0.5, hover) * live let hd = step(0.5, held) * live let sel = step(0.5, chosen) let pc = pa_cell(p, o, b) var out = vec4(0.0, 0.0, 0.0, 0.0) if sel + hd > 0.5 { let sbs = vec4(sb.x, sb.y + b, sb.z, sb.w) let face = mix(mix(pa_dark(), pa_dark2(), hov), pa_mute(), dis) out = pa_sunk(pc, b, sbs, 1.0, face, mix(pa_ink(), pa_mid(), dis), sel * live, lampw) } else { let face = mix(mix(pa_face(), pa_hi(), hov), pa_mute(), dis) let hl = mix(pa_light(), vec3(0.541, 0.627, 0.494), dis) let sh = mix(pa_mute(), pa_mute(), dis) out = pa_cap(pc, b, sb, 1.0, 1.0, face, hl, sh, mix(pa_deep(), pa_mid(), dis), mix(pa_ink(), pa_mid(), dis), 0.30 * live) } let kf = pa_focus(pa_dbox(pc, sb, 1.0, b), p, o, b) * step(0.5, focus) * live out = pa_lay(out, pa_ink(), kf) return out } // RADIO TABS: caption caps. mod.widgets.RadioButtonTabFlat.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 5.0 bottom: 6.5} mod.widgets.RadioButtonTabFlat.label_walk.margin = mod.turtle.Inset{left: 10.0 right: 10.0 top: 0.0 bottom: 0.0} mod.widgets.RadioButtonTabFlat.draw_text.color = #0d2117 mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #0d2117 mod.widgets.RadioButtonTabFlat.draw_text.color_down = #d6e0b2 mod.widgets.RadioButtonTabFlat.draw_text.color_focus = #0d2117 mod.widgets.RadioButtonTabFlat.draw_text.color_active = #d6e0b2 mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #435c42 mod.widgets.RadioButtonTabFlat.draw_text.text_style = mod.theme.font_label{} mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let sb = pa_sbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size, b) return pa_caption(p, self.rect_pos, sb, b, self.active, self.hover, self.down, self.focus, self.disabled, 3.0) } mod.widgets.RadioButtonTabFlat.clip_x = false mod.widgets.RadioButtonTabFlat.clip_y = false mod.widgets.RadioButtonTab.clip_x = false mod.widgets.RadioButtonTab.clip_y = false // --------------------------------------------------------------------------- // SLIDERS. A rail five pixels deep: an ink frame, dark inside with a deep // row under its top, the value's run lit with a light row along its top, // and a lit lip under the frame's foot. The thumb is a raised cap eight // pixels wide with a grip line down its middle; it drops a pixel while // dragged. let pa_rail = fn(p: vec2, o: vec2, b: float, sb: vec4, lo: float, hi: float, dis: float) -> vec4 { let pc = pa_cell(p, o, b) let d = pa_dbox(pc, sb, 1.0, b) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) var out = vec4(0.0, 0.0, 0.0, 0.0) let lip = pa_ring_out(d, 0.0, b) * step(sb.w, pc.y) * (1.0 - dis) out = pa_lay(out, pa_light(), lip) let top = step(pc.y, sb.y + 2.0 * b) let lit = step(lo, pc.x) * step(pc.x, hi) var col = mix(pa_dark(), pa_deep(), top) col = mix(col, mix(pa_lit(), vec3(0.910, 0.933, 0.839), top), lit) col = mix(col, mix(mix(pa_mute(), vec3(0.541, 0.627, 0.494), pa_checker(p, o, b)), pa_light(), lit), dis) col = mix(col, mix(pa_ink(), pa_mid(), dis), rim_a) return pa_lay(out, col, inside) } let pa_thumb = fn(p: vec2, o: vec2, b: float, hx: float, yc: float, w: float, hgt: float, hover: float, drag: float, focus: float, dis: float) -> vec4 { let live = 1.0 - dis let hov = step(0.5, hover) * live let dr = step(0.5, drag) * live let pc = pa_cell(p, o, b) let x0 = pa_snap(hx - w * b * 0.5, o.x, b) let y0 = pa_snap(yc - hgt * b * 0.5, o.y, b) let sb = vec4(x0, y0 + dr * b, x0 + w * b, y0 + hgt * b) let face = mix(mix(mix(pa_light(), pa_lit(), hov), pa_down(), dr), pa_mute(), dis) let hl = mix(mix(pa_lit(), pa_mute(), dr), pa_mute(), dis) let sh = mix(mix(pa_mute(), pa_down(), dr), pa_mute(), dis) var out = pa_cap(pc, b, sb, 1.0, 1.0 - dr, face, hl, sh, mix(pa_deep(), pa_mid(), dis), mix(pa_ink(), pa_mid(), dis), 0.32 * (1.0 - dr) * live) // the grip: one ink column down the middle of the face let gy = step(sb.y + 3.0 * b, pc.y) * step(pc.y, sb.w - 3.0 * b - (1.0 - dr) * b) let ga = step(abs(pc.x - (x0 + w * b * 0.5) + 0.5 * b), 0.51 * b) * gy out = pa_lay(out, pa_mid(), ga) let kf = pa_focus(pa_dbox(pc, vec4(x0, y0, x0 + w * b, y0 + hgt * b), 1.0, b), p, o, b) * step(0.5, focus) * live out = pa_lay(out, pa_ink(), kf) return out } let pa_slider = fn(p: vec2, o: vec2, size: vec2, px: float, top: float, slide: float, origin: float, arc_origin: float, hover: float, focus: float, drag: float, disabled: float, x0: float, x1: float, thumb: float) -> vec4 { let b = 2.0 * px let dis = step(0.5, disabled) let yc = (top + size.y) * 0.5 let tw = 8.0 let lo_t = min(origin, slide) * arc_origin let hi_t = mix(slide, max(origin, slide), arc_origin) let span0 = x0 + tw * b * 0.5 * thumb let span1 = x1 - tw * b * 0.5 * thumb let hx = span0 + slide * (span1 - span0) let rh = mix(3.0, 5.0, thumb) let ry = pa_snap(yc - rh * b * 0.5, o.y, b) let sb = vec4(pa_snap(x0, o.x, b), ry, pa_snap(x1, o.x, b), ry + rh * b) let lo = mix(sb.x, span0 + lo_t * (span1 - span0), step(0.001, lo_t)) let hi = span0 + hi_t * (span1 - span0) var out = pa_rail(p, o, b, sb, lo, hi, dis) if thumb > 0.5 { out = pa_over(out, pa_thumb(p, o, b, hx, yc, tw, 11.0, hover, drag, focus, dis)) } else { // the minimal slider: under the pointer or in focus, an ink tick // standing at the value let pc = pa_cell(p, o, b) let tk = step(abs(pc.x - pa_snap(hx, o.x, b) - 0.5 * b), 0.51 * b) * step(sb.y - b, pc.y) * step(pc.y, sb.w + b) out = pa_lay(out, pa_ink(), tk * max(step(0.5, hover), max(step(0.5, focus), step(0.5, drag))) * (1.0 - dis)) } return out } mod.widgets.SliderFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return pa_slider(p, self.rect_pos, self.rect_size, px, self.offset_y, self.slide_pos, self.origin_pos, self.arc_origin, self.hover, self.focus, self.drag, self.disabled, 0.0, self.rect_size.x, 1.0) } mod.widgets.SliderFlat.clip_x = false mod.widgets.SliderFlat.clip_y = false // The minimal slider: the rail alone, three pixels deep. mod.widgets.SliderMinimal.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderMinimal.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return pa_slider(p, self.rect_pos, self.rect_size, px, self.offset_y, self.slide_pos, self.origin_pos, self.arc_origin, self.hover, self.focus, self.drag, self.disabled, 0.0, self.rect_size.x, 0.0) } mod.widgets.SliderMinimal.clip_x = false mod.widgets.SliderMinimal.clip_y = false // The value read out beside a slider: tabular digits in the ink. let pa_value_style = mod.theme.font_regular{font_size: 8.0 font_family: mod.theme.font_regular.font_family{ibm_plex_text := FontMember{res: crate_resource("makepad_widgets:resources/jetbrains_mono_variable.ttf") weight: 700.0 asc: 0.0 desc: 0.0}}} // The value beside a slider is printed on the glass, bare: no box, ink. let pa_bare_value = fn(t) { t.draw_text.text_style = pa_value_style t.draw_text.color = #0d2117 t.draw_text.color_hover = #0d2117 t.draw_text.color_focus = #0d2117 t.draw_text.color_down = #0d2117 t.draw_text.color_disabled = #4f6a4c t.draw_text.color_empty = #1e3326 t.draw_text.color_empty_hover = #1e3326 t.draw_text.color_empty_focus = #1e3326 t.draw_bg.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } } pa_bare_value(mod.widgets.SliderMinimal.text_input) pa_bare_value(mod.widgets.SliderFlat.text_input) pa_bare_value(mod.widgets.SliderRoundFlat.text_input) // The round slider: the same rail and thumb in the room after its label, // its number at the right. mod.widgets.SliderRoundFlat.height = 22.0 mod.widgets.SliderRoundFlat.text_input.min_height = 0.0 mod.widgets.SliderRoundFlat.text_input.padding = mod.turtle.Inset{left: 0.0 right: 2.0 top: 0.0 bottom: 0.0} mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return pa_slider(p, self.rect_pos, self.rect_size, px, 0.0, self.slide_pos, self.origin_pos, self.arc_origin, self.hover, self.focus, self.drag, self.disabled, self.label_size, self.rect_size.x - 36.0, 1.0) } mod.widgets.SliderRoundFlat.clip_x = false mod.widgets.SliderRoundFlat.clip_y = false // FADERS. An ink slot two pixels wide, dotted ladders on both sides, a // long dot every fifth step; the cap is a raised cap twelve pixels across // and seven along, a grip line of ink across its middle, the line the // value is read off. mod.widgets.SliderFader.cap_size = 9.33 mod.widgets.SliderFader.track_inset = 6.0 mod.widgets.SliderFaderY.width = 24.0 mod.widgets.SliderFader.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 3.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SliderFader.draw_bg.pixel = fn() { let m = 3.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let size = self.rect_size let vert = step(0.5, self.is_vertical) let dis = step(0.5, self.disabled) let live = 1.0 - dis let pc = pa_cell(p, o, b) let along = mix(size.x, size.y, 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 middle line, on the grid let midx = pa_snap(size.x * 0.5, o.x, b) let midy = pa_snap(size.y * 0.5, o.y, b) // coordinates: `a` along from the low end, `c` across from the middle let a = mix(pc.x, size.y - pc.y, vert) let c = mix(pc.y - midy, pc.x - midx, vert) let icol = mix(pa_ink(), pa_mid(), dis) var out = vec4(0.0, 0.0, 0.0, 0.0) // the ladders: a dot every tenth of the travel, three pixels out from // the slot on both sides; the fifth ones two pixels long let st = travel / 10.0 let kk = floor((a - foot) / st + 0.5) let ya = foot + kk * st let on_step = step(abs(a - ya), 0.5 * b) * step(-0.5, kk) * step(kk, 10.5) let major = step(fract(kk / 5.0 + 0.01), 0.1) let ac = abs(c) let dot_at = step(3.0 * b, ac) * step(ac, mix(4.0, 5.0, major) * b) out = pa_lay(out, icol, on_step * dot_at) // the slot: two pixels wide, dark with an ink frame a pixel wide let s0 = self.inset_px let s1 = along - self.inset_px let in_slot = step(s0, a) * step(a, s1) * step(ac, 1.51 * b) let slot_rim = in_slot * step(0.99 * b, ac) out = pa_lay(out, mix(pa_dark(), pa_mute(), dis), in_slot) out = pa_lay(out, icol, slot_rim * 0.0) out = pa_lay(out, icol, in_slot * (1.0 - step(abs(c + 0.5 * b), 0.51 * b)) * 0.0) // the cap, in window space let at = foot + self.slide_pos * travel let cw = 12.0 * b let chh = 7.0 * b let ccx = mix(at, size.x * 0.5, vert) let ccy = mix(size.y * 0.5, size.y - at, vert) let wx = mix(chh, cw, vert) let wy = mix(cw, chh, vert) let x0 = pa_snap(ccx - wx * 0.5, o.x, b) let y0 = pa_snap(ccy - wy * 0.5, o.y, b) let dr = step(0.5, self.drag) * live let hov = step(0.5, self.hover) * live let sb = vec4(x0, y0 + dr * b, x0 + wx, y0 + wy) let face = mix(mix(mix(pa_light(), pa_lit(), hov), pa_down(), dr), pa_mute(), dis) let hl = mix(mix(pa_lit(), pa_mute(), dr), pa_mute(), dis) let sh = mix(pa_mute(), pa_mute(), dis) let cap = pa_cap(pc, b, sb, 1.0, 1.0 - dr, face, hl, sh, mix(pa_deep(), pa_mid(), dis), icol, 0.32 * (1.0 - dr) * live) out = pa_over(out, cap) // the grip line across the cap's face, through its middle let gm = pa_snap((sb.y + sb.w - (1.0 - dr) * b) * 0.5, o.y, b) let gline_v = step(abs(pc.y - gm + 0.5 * b), 0.51 * b) * step(sb.x + 2.0 * b, pc.x) * step(pc.x, sb.z - 2.0 * b) let gmx = pa_snap((sb.x + sb.z) * 0.5, o.x, b) let gline_h = step(abs(pc.x - gmx + 0.5 * b), 0.51 * b) * step(sb.y + 2.0 * b, pc.y) * step(pc.y, sb.w - 3.0 * b) let gl = mix(gline_h, gline_v, vert) out = pa_lay(out, icol, gl) let kf = pa_focus(pa_dbox(pc, vec4(x0, y0, x0 + wx, y0 + wy), 1.0, b), p, o, b) * step(0.5, self.focus) * live out = pa_lay(out, pa_ink(), kf) return out } // --------------------------------------------------------------------------- // KNOBS. A pixel disc: a shadow a pixel to the lower right, an ink ring, a // dark bezel carrying the value as a lit arc, an ink ring, and a light face // shaded from lit at its upper left to mute at its lower right by an // ordered dither; an ink pointer one pixel wide from near the middle out // to the bezel. let pa_knob = fn(p: vec2, o: vec2, cx: float, cy: float, rad: float, px: float, slide: float, origin: float, arc_origin: float, gap: float, hover: float, focus: float, drag: float, disabled: float) -> vec4 { let b = 2.0 * px let dis = step(0.5, disabled) let live = 1.0 - dis let hov = step(0.5, hover) * live let dr = step(0.5, drag) * live let c = vec2(pa_snap(cx, o.x, b), pa_snap(cy, o.y, b)) let rr = floor(rad / b) * b let pc = pa_cell(p, o, b) let v = pc - c let r = length(v) let bw = mix(2.0, 3.0, step(34.0, rr * 2.0)) * b var out = vec4(0.0, 0.0, 0.0, 0.0) // the shadow let rs = length(v - vec2(b, b)) let sa = step(rs, rr) * step(rr, r) * 0.32 * live out = pa_lay(out, pa_ink(), sa) let inside = step(r, rr) // the bezel and the value arc on it let start = gap * 0.5 * PI / 180.0 let sweep = 2.0 * PI - gap * PI / 180.0 var ta = atan2(-v.x, v.y) if ta < 0.0 { ta = ta + 2.0 * PI } let t = (ta - start) / sweep let lo = min(origin, slide) * arc_origin let hi = mix(slide, max(origin, slide), arc_origin) let on_arc = step(lo, t) * step(t, hi) * step(0.0, t) * step(t, 1.0) let in_bez = step(rr - b - bw, r) * step(r, rr - b) var bcol = mix(pa_dark(), pa_deep(), step(0.0, v.y + v.x)) bcol = mix(bcol, mix(pa_lit(), pa_light(), dr), on_arc) bcol = mix(bcol, mix(pa_mute(), pa_mid(), step(0.5, on_arc)), dis) // the face: an ordered dither between four greens let fr = rr - 2.0 * b - bw let s = clamp(0.4 - 0.6 * dot(v / max(fr, 1.0), vec2(0.7071, 0.7071)), 0.0, 1.0) + 0.18 * hov - 0.2 * dr let lvl = clamp(floor(s * 3.0 + pa_bayer(p, o, b)), 0.0, 3.0) var fcol = mix(pa_mute(), pa_face(), step(0.5, lvl)) fcol = mix(fcol, pa_light(), step(1.5, lvl)) fcol = mix(fcol, pa_lit(), step(2.5, lvl)) fcol = mix(fcol, mix(pa_mute(), vec3(0.541, 0.627, 0.494), pa_checker(p, o, b)), dis) let in_face = step(r, fr) var col = mix(pa_ink(), bcol, in_bez) col = mix(col, fcol, in_face) col = mix(col, pa_mid(), (1.0 - in_bez) * (1.0 - in_face) * dis) // the pointer let av = start + sweep * slide let dir = vec2(-sin(av), cos(av)) let pd = pa_seg(v, dir * fr * 0.18, dir * (fr - 0.5 * b)) let pa = step(pd, 0.62 * b) * in_face col = mix(col, mix(pa_ink(), pa_mid(), dis), pa) out = pa_lay(out, col, inside) // focus: a dotted ring a pixel out let kf = step(rr + b, r) * step(r, rr + 2.0 * b) * pa_checker(p, o, b) * step(0.5, focus) * live out = pa_lay(out, pa_ink(), kf) return out } mod.widgets.RotaryKnob.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RotaryKnob.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let dia = min(self.rect_size.x, self.rect_size.y) let rad = dia * 0.5 - 1.5 return pa_knob(p, self.rect_pos, self.rect_size.x * 0.5, self.rect_size.y - dia * 0.5, rad, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.focus, self.drag, self.disabled) } mod.widgets.RotaryKnob.clip_x = false mod.widgets.RotaryKnob.clip_y = false mod.widgets.RotaryFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RotaryFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let dia = min(self.rect_size.x, self.rect_size.y - 24.0) let rad = dia * 0.5 - 2.0 return pa_knob(p, self.rect_pos, self.rect_size.x * 0.5, 24.0 + dia * 0.5, rad, px, self.slide_pos, self.origin_pos, self.arc_origin, self.gap, self.hover, self.focus, self.drag, self.disabled) } mod.widgets.RotaryFlat.clip_x = false mod.widgets.RotaryFlat.clip_y = false mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 4.0 right: 4.0 top: 1.0 bottom: 1.0} mod.widgets.RotaryFlat.text_input.min_height = 0.0 mod.widgets.RotaryFlat.text_input.draw_text.text_style = pa_value_style // --------------------------------------------------------------------------- // FIELDS. A dark well let into the glass: an ink frame with its corners // stepped off, a deep row under its top and down its left, a lit lip under // its foot and down its right where the cut catches the light, lit words. // Under the pointer the well lifts a shade; in focus a lit keyline runs // inside the frame. Read only, the well is a dithered mute. let pa_well = fn(p: vec2, o: vec2, sb: vec4, b: float, hover: float, focus: float, disabled: float) -> vec4 { let dis = step(0.5, disabled) let live = 1.0 - dis let hov = step(0.5, hover) * live let foc = step(0.5, focus) * live let pc = pa_cell(p, o, b) let d = pa_dbox(pc, sb, 1.0, b) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) var out = vec4(0.0, 0.0, 0.0, 0.0) let lip = pa_ring_out(d, 0.0, b) * max(step(sb.w, pc.y), step(sb.z, pc.x)) out = pa_lay(out, pa_light(), lip) var col = mix(pa_dark(), pa_dark2(), hov) let tl = step(min(pc.x - sb.x, pc.y - sb.y), 2.0 * b) col = mix(col, pa_deep(), tl) let r1 = step(-2.0 * b, d) * step(d, -b) col = mix(col, pa_lit(), r1 * foc) let dcol = mix(pa_mute(), vec3(0.435, 0.529, 0.408), pa_checker(p, o, b)) col = mix(col, mix(dcol, pa_mid(), tl), dis) col = mix(col, mix(pa_ink(), pa_mid(), dis), rim_a) return pa_lay(out, col, inside) } mod.widgets.TextInputFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 2.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.TextInputFlat.draw_bg.pixel = fn() { let m = 2.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let sb = pa_sbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size, b) let vis = step(0.001, self.border_size) return pa_well(p, self.rect_pos, sb, b, self.hover, self.focus, self.disabled) * vis } mod.widgets.TextInputFlat.clip_x = false mod.widgets.TextInputFlat.clip_y = false mod.widgets.TextInputFlat.padding = mod.turtle.Inset{left: 9.0 right: 9.0 top: 3.0 bottom: 3.0} mod.widgets.TextInputFlat.draw_text.color = #d6e0b2 mod.widgets.TextInputFlat.draw_text.color_hover = #d6e0b2 mod.widgets.TextInputFlat.draw_text.color_focus = #e8eed6 mod.widgets.TextInputFlat.draw_text.color_down = #d6e0b2 mod.widgets.TextInputFlat.draw_text.color_disabled = #243a2b mod.widgets.TextInputFlat.draw_text.color_empty = #a4b98c mod.widgets.TextInputFlat.draw_text.color_empty_hover = #b0c296 mod.widgets.TextInputFlat.draw_text.color_empty_focus = #a4b98c // The caret is a lit block a big pixel wide. mod.widgets.TextInputFlat.draw_cursor.pixel = fn() { let a = (1.0 - self.blink) * self.focus * (1.0 - self.disabled) return vec4(vec3(0.910, 0.933, 0.839) * a, a) } mod.widgets.TextInputFlat.draw_selection.border_radius = 0.0 mod.widgets.TextInputFlat.draw_selection.pixel = fn() { let a = 0.36 * self.focus * (1.0 - self.disabled) return vec4(vec3(0.839, 0.878, 0.698) * a, a) } // The number field: the same dark well, its stepper a raised light cap at // the right, split across its middle by an ink rule, a pixel arrow in each // half; a half lifts under the pointer and drops while pressed. mod.widgets.NumberField.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 2.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.NumberField.draw_bg.pixel = fn() { let m = 2.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let sb = pa_sbox(self.rect_pos, vec2(0.0, 0.0), self.rect_size, b) return pa_well(p, self.rect_pos, sb, b, self.hover, self.focus, self.disabled) } mod.widgets.NumberField.clip_x = false mod.widgets.NumberField.clip_y = false mod.widgets.NumberField.pad_left = 9.0 mod.widgets.NumberField.input.draw_text.color = #d6e0b2 mod.widgets.NumberField.input.draw_text.color_hover = #d6e0b2 mod.widgets.NumberField.input.draw_text.color_focus = #e8eed6 mod.widgets.NumberField.input.draw_text.color_down = #d6e0b2 mod.widgets.NumberField.input.draw_text.color_disabled = #243a2b mod.widgets.NumberField.input.draw_text.color_empty = #a4b98c mod.widgets.NumberField.input.draw_text.color_empty_hover = #b0c296 mod.widgets.NumberField.input.draw_text.color_empty_focus = #a4b98c mod.widgets.NumberSpin.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let size = self.rect_size let dis = step(0.5, self.disabled) let live = 1.0 - dis let pc = pa_cell(p, o, b) // the cap stands two pixels in from the well's frame let sb = pa_sbox(o, vec2(b * 0.0, 2.0 * b), vec2(size.x - 2.0 * b, size.y - 2.0 * b), b) let mid = pa_snap((sb.y + sb.w) * 0.5, o.y, b) let upper = step(pc.y, mid) let hot = mix(max(0.0, -self.hot), max(0.0, self.hot), upper) * live let dn = mix(max(0.0, -self.down), max(0.0, self.down), upper) * live let face = mix(mix(mix(pa_light(), pa_lit(), step(0.5, hot)), pa_down(), step(0.5, dn)), pa_mute(), dis) var out = pa_cap(pc, b, sb, 1.0, 0.0, face, mix(pa_lit(), pa_mute(), dis), pa_mute(), pa_deep(), mix(pa_ink(), pa_mid(), dis), 0.0) let icol = mix(pa_ink(), pa_mid(), dis) // the rule across the middle let rule = step(abs(pc.y - mid), 0.51 * b) * step(sb.x, pc.x) * step(pc.x, sb.z) out = pa_lay(out, icol, rule) // the arrows: stepped triangles, five pixels across let cx = pa_snap((sb.x + sb.z) * 0.5, o.x, b) let qx = floor((pc.x - cx) / b) let yu = pa_snap((sb.y + mid) * 0.5, o.y, b) let yd = pa_snap((mid + sb.w) * 0.5, o.y, b) let ru = floor((pc.y - yu) / b) + 2.0 let rd = 1.0 - floor((pc.y - yd) / b) let au = step(0.0, ru) * step(ru, 2.5) * step(abs(qx + 0.5), ru + 0.6) let ad = step(0.0, rd) * step(rd, 2.5) * step(abs(qx + 0.5), rd + 0.6) out = pa_lay(out, icol, max(au, ad)) return out } // The printed arrow glyphs are hidden; the face draws the arrows. mod.widgets.NumberSpin.draw_text.color = #0d211700 mod.widgets.NumberSpin.draw_text.color_disabled = #0d211700 // DROP-DOWNS. A raised cap with a cell twelve pixels wide split off at its // right by an ink rule, a pixel chevron in it; open or held, the cap is // pushed in. let pa_choice = fn(p: vec2, o: vec2, size: vec2, b: float, hover: float, open: float, focus: float, disabled: float) -> vec4 { let dis = step(0.5, disabled) let live = 1.0 - dis let pc = pa_cell(p, o, b) let sb = pa_sbox(o, vec2(0.0, 0.0), size, b) var out = pa_button_face(p, o, size, b, 0.0, 0.0, hover, open, focus, disabled) let held = step(0.5, open) * live let rx = pa_snap(size.x - 16.0, o.x, b) let icol = mix(pa_ink(), pa_mid(), dis) let rule = step(abs(pc.x - rx - 0.5 * b), 0.51 * b) * step(sb.y + (1.0 + held) * b, pc.y) * step(pc.y, sb.w - 2.0 * b) out = pa_lay(out, icol, rule) let cxc = pa_snap((rx + sb.z) * 0.5, o.x, b) let cyc = pa_snap(size.y * 0.5 - 2.0 * b + held * b, o.y, b) let ch = pa_chevron(pc, vec2(cxc + 0.5 * b, cyc + 0.5 * b), b) out = pa_lay(out, icol, ch) return out } mod.widgets.DropDownFlat.padding = mod.turtle.Inset{left: 10.0 right: 26.0 top: 3.0 bottom: 4.5} mod.widgets.DropDownFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.DropDownFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return pa_choice(p, self.rect_pos, self.rect_size, 2.0 * px, self.hover, max(self.active, self.down * self.hover), self.focus, self.disabled) } mod.widgets.DropDownFlat.clip_x = false mod.widgets.DropDownFlat.clip_y = false mod.widgets.DropDownFlat.draw_text.color = #0d2117 mod.widgets.DropDownFlat.draw_text.color_hover = #0d2117 mod.widgets.DropDownFlat.draw_text.color_focus = #0d2117 mod.widgets.DropDownFlat.draw_text.color_down = #0d2117 mod.widgets.DropDownFlat.draw_text.color_disabled = #435c42 mod.widgets.DropDownFlat.draw_text.text_style = mod.theme.font_label{} // The select wears a button built before this sheet: it is given the // drop-down's cap and cell here, by its own path. mod.widgets.Select.face.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Select.face.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return pa_choice(p, self.rect_pos, self.rect_size, 2.0 * px, self.hover, self.down, self.focus, self.disabled) } mod.widgets.MultiSelect.face.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.MultiSelect.face.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return pa_choice(p, self.rect_pos, self.rect_size, 2.0 * px, self.hover, self.down, self.focus, self.disabled) } mod.widgets.Select.face.padding = mod.turtle.Inset{left: 10.0 right: 26.0 top: 3.0 bottom: 4.5} mod.widgets.MultiSelect.face.padding = mod.turtle.Inset{left: 10.0 right: 26.0 top: 3.0 bottom: 4.5} mod.widgets.Select.face.min_height = 26.0 mod.widgets.MultiSelect.face.min_height = 26.0 mod.widgets.Select.face.draw_text.color = #0d2117 mod.widgets.Select.face.draw_text.color_hover = #0d2117 mod.widgets.Select.face.draw_text.color_down = #0d2117 mod.widgets.Select.face.draw_text.color_focus = #0d2117 mod.widgets.MultiSelect.face.draw_text.color = #0d2117 mod.widgets.MultiSelect.face.draw_text.color_hover = #0d2117 mod.widgets.MultiSelect.face.draw_text.color_down = #0d2117 mod.widgets.MultiSelect.face.draw_text.color_focus = #0d2117 // The combo box: the dark well with lit words, and at its right a small // raised cap holding the chevron, pushed in while its list is open. mod.widgets.ComboBoxFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 2.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() { let m = 2.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let size = self.rect_size let dis = step(0.5, self.disabled) let live = 1.0 - dis let sb = pa_sbox(o, vec2(0.0, 0.0), size, b) var out = pa_well(p, o, sb, b, self.hover, self.focus, self.disabled) let pc = pa_cell(p, o, b) let held = step(0.5, self.down * self.hover) * live let kb = vec4(pa_snap(size.x - 18.0, o.x, b), sb.y + 2.0 * b + held * b, sb.z - 2.0 * b, sb.w - 2.0 * b) let face = mix(mix(mix(pa_light(), pa_lit(), step(0.5, self.hover) * live), pa_down(), held), pa_mute(), dis) let cap = pa_cap(pc, b, kb, 1.0, 1.0 - held, face, mix(pa_lit(), pa_mute(), dis), pa_mute(), mix(pa_deep(), pa_mid(), dis), mix(pa_ink(), pa_mid(), dis), 0.0) out = pa_over(out, cap) let cxc = pa_snap((kb.x + kb.z) * 0.5, o.x, b) let cyc = pa_snap((kb.y + kb.w) * 0.5 - 2.5 * b, o.y, b) let ch = pa_chevron(pc, vec2(cxc + 0.5 * b, cyc + 0.5 * b), b) out = pa_lay(out, mix(pa_ink(), pa_mid(), dis), ch) return out } mod.widgets.ComboBoxFlat.clip_x = false mod.widgets.ComboBoxFlat.clip_y = false mod.widgets.ComboBoxFlat.padding = mod.turtle.Inset{left: 9.0 right: 28.0 top: 3.0 bottom: 3.0} mod.widgets.ComboBoxFlat.input.draw_text.color = #d6e0b2 mod.widgets.ComboBoxFlat.input.draw_text.color_hover = #d6e0b2 mod.widgets.ComboBoxFlat.input.draw_text.color_focus = #e8eed6 mod.widgets.ComboBoxFlat.input.draw_text.color_down = #d6e0b2 mod.widgets.ComboBoxFlat.input.draw_text.color_disabled = #243a2b mod.widgets.ComboBoxFlat.input.draw_text.color_empty = #a4b98c mod.widgets.ComboBoxFlat.input.draw_text.color_empty_hover = #b0c296 mod.widgets.ComboBoxFlat.input.draw_text.color_empty_focus = #a4b98c mod.widgets.ComboBoxFlat.draw_popup_bg.color = #b3c599 mod.widgets.ComboBoxFlat.draw_popup_bg.border_color = #0d2117 mod.widgets.ComboBoxFlat.draw_popup_bg.border_radius = 0.0 mod.widgets.ComboBoxFlat.draw_item.color_hover = #c0cf9f mod.widgets.ComboBoxFlat.draw_item.color_active = #2f4836 mod.widgets.ComboBoxFlat.draw_item_text.color_active = #d6e0b2 mod.widgets.ComboBoxFlat.item_height = 20.0 // the input inside the box takes no row of its own mod.widgets.ComboBoxFlat.input.min_height = 0.0 // --------------------------------------------------------------------------- // TABS. A row of caption caps; the tab in use is pushed in, dark, its lamp // lit under its lit word. mod.widgets.Tabs.height = 26.0 mod.widgets.Tabs.draw_bg.color = #00000000 mod.widgets.Tabs.draw_bg.pixel = fn() { return vec4(0.0, 0.0, 0.0, 0.0) } let pa_tab = fn(pos: vec2, size: vec2, o: vec2, px: float, chosen: float, hover: float) -> vec4 { let m = 3.0 let b = 2.0 * px let p = pos * (size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let sb = pa_sbox(o, vec2(0.0, 0.0), size - vec2(b, 0.0), b) return pa_caption(p, o, sb, b, chosen, hover, 0.0, 0.0, 0.0, 3.0) } mod.widgets.Tabs.draw_tab.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 3.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_tab.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return pa_tab(self.pos, self.rect_size, self.rect_pos, px, 0.0, 0.0) } mod.widgets.Tabs.draw_tab_hover.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 3.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_tab_hover.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return pa_tab(self.pos, self.rect_size, self.rect_pos, px, 0.0, 1.0) } mod.widgets.Tabs.draw_tab_active.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 3.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.Tabs.draw_tab_active.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) return pa_tab(self.pos, self.rect_size, self.rect_pos, px, 1.0, 0.0) } mod.widgets.Tabs.draw_sep.color = #00000000 mod.widgets.Tabs.draw_mark.color = #0d2117 // The close mark is a stepped x in lit pixels (it stands on the dark tab in // use), the add mark a plus in ink pixels. mod.widgets.Tabs.draw_mark.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let is_plus = step(0.5, self.plus) let cx0 = mix(self.rect_size.x - 6.0, self.rect_size.x * 0.5, is_plus) let c = vec2(pa_snap(cx0, o.x, b), pa_snap(self.rect_size.y * 0.5, o.y, b)) let pc = pa_cell(p, o, b) let q = floor((pc - c) / b) let r = mix(2.0, 4.0, is_plus) let inbox = step(abs(q.x + 0.5), r + 0.5) * step(abs(q.y + 0.5), r + 0.5) let cross = max(step(abs(q.x - q.y), 0.5), step(abs(q.x + q.y + 1.0), 0.5)) * inbox let plus = max(step(abs(q.x + 0.5), 1.0), step(abs(q.y + 0.5), 1.0)) * inbox let a = mix(cross, plus, is_plus) * self.color.a let col = mix(vec3(0.839, 0.878, 0.698), vec3(0.051, 0.129, 0.090), is_plus) return vec4(col * a, a) } mod.widgets.Tabs.draw_text.color = #0d2117 mod.widgets.Tabs.draw_text_active.color = #d6e0b2 mod.widgets.Tabs.draw_text.ink_centered = true mod.widgets.Tabs.draw_text_active.ink_centered = true let pa_tab_type = mod.theme.font_label{font_size: 8.0 line_spacing: 1.0} mod.widgets.Tabs.draw_text.text_style = pa_tab_type mod.widgets.Tabs.draw_text_active.text_style = pa_tab_type // The segmented group: one raised strip; the chosen answer is pushed in, // dark, framed in ink on both sides, its lamp lit under its word. mod.widgets.SegmentedControlFlat.segment_height = 26.0 mod.widgets.SegmentedControlFlat.segment_padding = 10.0 mod.widgets.SegmentedControlFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let sb = pa_sbox(o, vec2(0.0, 0.0), self.rect_size, b) let pc = pa_cell(p, o, b) return pa_cap(pc, b, sb, 1.0, 1.0, pa_face(), pa_light(), pa_mute(), pa_deep(), pa_ink(), 0.30) } mod.widgets.SegmentedControlFlat.draw_pill.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 2.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() { let m = 2.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) let o = self.rect_pos let sb = pa_sbox(o, vec2(0.0, 0.0), self.rect_size, b) let sbs = vec4(sb.x, sb.y + b, sb.z, sb.w) let pc = pa_cell(p, o, b) return pa_sunk(pc, b, sbs, 0.0, pa_dark(), pa_ink(), 1.0, 3.0) } mod.widgets.SegmentedControlFlat.draw_text.color = #0d2117 mod.widgets.SegmentedControlFlat.draw_text_selected.color = #d6e0b2 mod.widgets.SegmentedControlFlat.draw_text.text_style = mod.theme.font_label{} mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_label{} mod.widgets.SegmentedControlFlat.palette.on_neutral_container = #d6e0b2 // The dock's tabs: the tab in use is a light cap with an ink bar along its // top; the others lay the checker under the pointer. mod.widgets.Tab.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let sb = pa_sbox(o, vec2(0.0, 0.0), self.rect_size, b) let pc = pa_cell(p, o, b) var out = vec4(0.0, 0.0, 0.0, 0.0) let da = pa_checker(p, o, b) * step(0.5, self.hover) * (1.0 - step(0.5, self.active)) out = pa_lay(out, pa_light(), da) let act = step(0.5, self.active) out = pa_lay(out, pa_face(), act) let bar = step(pc.y, sb.y + 2.0 * b) * act let side = max(step(pc.x, sb.x + b), step(sb.z - b, pc.x)) * act out = pa_lay(out, pa_ink(), max(bar, side)) return out } mod.widgets.TabBar.draw_fill.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let yb = pa_snap(self.rect_size.y, self.rect_pos.y, b) let la = step(yb - b, p.y) * step(p.y, yb) return vec4(pa_ink() * la, la) } // --------------------------------------------------------------------------- // PROGRESS. A rail six pixels deep in an ink frame, dark inside, the done // run lit with a light row along its top. The amount that is not known // cannot move without a clock, so it stands still: a lit run across the // middle third. mod.widgets.ProgressBarFlat.height = 9.33 mod.widgets.ProgressBarFlat.draw_bg.thickness = 8.0 mod.widgets.ProgressBarFlat.draw_bg.border_radius = 0.0 mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let size = self.rect_size let hgt = 6.0 * b let y0 = pa_snap((size.y - hgt) * 0.5, o.y, b) let x0 = size.x * self.start let x1 = size.x * self.end let sb = vec4(pa_snap(x0, o.x, b), y0, pa_snap(x1, o.x, b), y0 + hgt) var lo = sb.x var hi = sb.x + clamp((self.value - self.start) / max(self.end - self.start, 0.0001), 0.0, 1.0) * (sb.z - sb.x) if self.value < 0.0 { lo = sb.x + (sb.z - sb.x) * 0.36 hi = sb.x + (sb.z - sb.x) * 0.64 } return pa_rail(p, o, b, sb, lo, hi, 0.0) * self.opacity } // LEVEL METERS. A dark rail in an ink frame holding segments two pixels // long and one apart, lit to the level and dark beyond; the held peak a // segment of ink-framed lit; the over lamp a small cap at the end, lit // when the level has gone over. mod.widgets.LevelMeter.height = 9.33 mod.widgets.LevelMeterColumn.width = 9.33 mod.widgets.LevelMeter.draw_bg.lamp_size = uniform(8.0) mod.widgets.LevelMeter.draw_bg.lamp_gap = uniform(3.0) mod.widgets.LevelMeter.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let size = self.rect_size let vert = step(0.5, self.vertical) let pc = pa_cell(p, o, b) let along = mix(size.x, size.y, vert) let lamp_room = self.lamp * (self.lamp_size + self.lamp_gap) let bar = max(4.0 * b, along - lamp_room) // the rail's box in window space: horizontal runs from the left, a // column from the foot let sbh = pa_sbox(o, vec2(0.0, 0.0), vec2(bar, size.y), b) let sbv = pa_sbox(o, vec2(0.0, size.y - bar), size, b) let sb = mix(sbh, sbv, vert) let d = pa_dbox(pc, sb, 1.0, b) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) // position along from the low end, in pixels from the rail's start let s = mix(pc.x - sb.x, sb.w - pc.y, vert) let len = mix(sb.z - sb.x, sb.w - sb.y, vert) let k = floor((s - b) / (3.0 * b)) let within = s - b - k * 3.0 * b let n = floor((len - 2.0 * b + b) / (3.0 * b)) let seg = step(within, 2.0 * b) * step(0.0, k) * step(k, n - 1.0) * step(b, s) * step(s, len - b) let lit = step((k + 0.5) / max(n, 1.0), self.level + 0.0001) * step(0.0001, self.level) let hk = floor(self.hold * n - 0.0001) let peak = step(abs(k - hk), 0.5) * step(0.002, self.hold) var col = pa_dark() col = mix(col, mix(pa_dark2(), pa_lit(), max(lit, peak)), seg) col = mix(col, pa_ink(), rim_a) var out = vec4(col * inside, inside) if self.lamp > 0.5 { let ls = floor(self.lamp_size / b + 0.5) * b let lh = pa_sbox(o, vec2(bar + self.lamp_gap, (size.y - ls) * 0.5), vec2(bar + self.lamp_gap + ls, (size.y + ls) * 0.5), b) let lv = pa_sbox(o, vec2((size.x - ls) * 0.5, size.y - bar - self.lamp_gap - ls), vec2((size.x + ls) * 0.5, size.y - bar - self.lamp_gap), b) let lb = mix(lh, lv, vert) let on = step(0.5, self.over) let lamp = pa_cap(pc, b, lb, 1.0, 0.0, mix(pa_dark2(), pa_lit(), on), mix(pa_dark2(), vec3(0.910, 0.933, 0.839), on), mix(pa_deep(), pa_light(), on), pa_deep(), pa_ink(), 0.0) out = pa_over(out, lamp) } return out * self.opacity } // SCROLL BARS. Ten points: a dark trough in an ink frame, the thumb a // raised light cap a pixel inside it, lifted under the pointer and pushed // in while dragged. mod.widgets.ScrollBar.bar_size = 10.67 mod.widgets.ScrollBar.bar_side_margin = 0.0 mod.widgets.ScrollBar.draw_bg.size = 10.67 mod.widgets.ScrollBar.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let size = self.rect_size let vert = step(0.5, self.is_vertical) let pc = pa_cell(p, o, b) let sb = pa_sbox(o, vec2(0.0, 0.0), size, b) let d = pa_dbox(pc, sb, 1.0, b) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) var col = mix(pa_dark(), pa_dark2(), pa_quarter(p, o, b)) col = mix(col, pa_ink(), rim_a) var out = vec4(col * inside, inside) let len = mix(size.x, size.y, vert) let t0 = len * self.norm_scroll let t1 = len * (self.norm_scroll + self.norm_handle) let th = mix(pa_sbox(o, vec2(t0, 0.0), vec2(t1, size.y), b), pa_sbox(o, vec2(0.0, t0), vec2(size.x, t1), b), vert) let tb = vec4(th.x, th.y, th.z, th.w) let hov = step(0.5, max(self.hover, self.drag)) let dr = step(0.5, self.drag) let face = mix(mix(pa_light(), pa_lit(), hov), pa_down(), dr) let cap = pa_cap(pc, b, tb, 1.0, 0.0, face, mix(pa_lit(), pa_mute(), dr), pa_mute(), pa_deep(), pa_ink(), 0.0) out = pa_over(out, cap) return out * self.opacity } // --------------------------------------------------------------------------- // PANELS. A raised panel is a large cap: an ink frame with its corners // stepped off twice, a light row along its top and left, a shade row along // its right and above its foot, a dark thickness, a shadow a pixel to the // lower right, and a face dithered in a checker of two mid greens. The // inset is let in: an ink frame, two deep rows under its top and down its // left, a light row inside its right and foot, and a lighter face with one // pixel in four darkened. let pa_panel = fn(p: vec2, o: vec2, size: vec2, b: float, sunk: float, card: float) -> vec4 { let pc = pa_cell(p, o, b) let sb = pa_sbox(o, vec2(0.0, 0.0), size, b) var out = vec4(0.0, 0.0, 0.0, 0.0) if sunk > 0.5 { let d = pa_dbox(pc, sb, 2.0, b) let inside = step(d, 0.0) let rim_a = inside * step(-b, d) let lip = pa_ring_out(d, 0.0, b) * max(step(sb.w, pc.y), step(sb.z, pc.x)) out = pa_lay(out, pa_light(), lip) var col = mix(vec3(0.490, 0.576, 0.431), vec3(0.455, 0.537, 0.416), pa_quarter(p, o, b)) let dt = min(pc.x - sb.x, pc.y - sb.y) let db = min(sb.z - pc.x, sb.w - pc.y) col = mix(col, pa_mid(), step(dt, 4.0 * b) * step(dt, db)) col = mix(col, pa_dark2(), step(dt, 3.0 * b) * step(dt, db)) col = mix(col, pa_deep(), step(dt, 2.0 * b) * step(dt, db)) col = mix(col, vec3(0.604, 0.690, 0.529), step(db, 2.0 * b) * step(db, dt)) col = mix(col, pa_ink(), rim_a) out = pa_lay(out, col, inside) } else { let face = mix(vec3(0.561, 0.647, 0.478), vec3(0.620, 0.706, 0.533), card) let face2 = mix(vec3(0.518, 0.604, 0.443), vec3(0.588, 0.675, 0.506), card) let f = mix(face, face2, pa_checker(p, o, b)) out = pa_cap(pc, b, sb, 2.0, 1.0, f, pa_light(), pa_mute(), pa_deep(), pa_ink(), 0.32) // the checker stays inside the bevel: the bevel rows are solid } return out } mod.widgets.PanelView.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 14.0 bottom: 14.0} mod.widgets.InsetPanelView.padding = mod.turtle.Inset{left: 14.0 right: 14.0 top: 14.0 bottom: 14.0} mod.widgets.Card.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 12.0 bottom: 12.0} mod.widgets.PanelView.clip_x = false mod.widgets.PanelView.clip_y = false mod.widgets.PanelView.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 3.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 = 3.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) return pa_lcd(p, self.rect_pos, self.rect_size, px, 6.0, 2.5) } // The card: a screen of its own; the pointer lays the // light row's checker over it, a press darkens it a step. mod.widgets.Card.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 3.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 b = 2.0 * px let m = 3.0 let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) var out = pa_lcd(p, self.rect_pos, self.rect_size, px, 4.0, 2.5) let pc = pa_cell(p, self.rect_pos, b) let sb = pa_sbox(self.rect_pos, vec2(10.0, 10.0), self.rect_size - vec2(10.0, 10.0), b) let d = pa_dbox(pc, sb, 2.0, b) let inner = step(d, -2.0 * b) * step(pc.y, sb.w - 3.0 * b) let la = inner * pa_quarter(p, self.rect_pos, b) * max(step(0.5, self.hover), step(0.5, self.down)) return pa_lay(out, mix(pa_light(), pa_mute(), step(0.5, self.down)), la) } // The plain rounded view keeps the colours its caller gives it; its edge // is stepped on the grid. mod.widgets.RoundedView.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos 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 sb = pa_sbox(o, lo, hi, b) let pc = pa_cell(p, o, b) let notch = clamp(floor(self.border_radius / 3.0 + 0.5), 0.0, 2.0) let d = pa_dbox(pc, sb, notch, b) 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 inside = step(d, 0.0) var out = vec4(fill.rgb * fill.a * inside, fill.a * inside) if self.border_size > 0.0 { let la = inside * step(-b, d) * self.border_color.a out = pa_over(out, vec4(self.border_color.rgb * la, la)) } return out } // --------------------------------------------------------------------------- // LISTS. Rows of 22 points. The row's words have no chosen ink, so a chosen // row cannot turn dark: it is a lit bar framed in ink with the pointer of a // handheld's menu at its head. The pointer's row lays the light checker. mod.widgets.ListItemOne.height = 22.0 mod.widgets.ListItem.padding = mod.turtle.Inset{left: 18.0 right: 8.0 top: 0.0 bottom: 0.0} mod.widgets.ListItem.draw_text.color = #0d2117 mod.widgets.ListItem.draw_text_2.color = #1e3326 mod.widgets.ListItem.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let live = 1.0 - step(0.5, self.disabled) let sel = step(0.5, self.selected) let pc = pa_cell(p, o, b) let sb = pa_sbox(o, vec2(1.0, 1.0), self.rect_size - vec2(12.0, 1.0), b) let d = pa_dbox(pc, sb, 1.0, b) let inside = step(d, 0.0) var out = vec4(0.0, 0.0, 0.0, 0.0) let ba = inside * pa_checker(p, o, b) * max(step(0.5, self.hover), step(0.5, self.down)) * live * (1.0 - sel) out = pa_lay(out, pa_light(), ba) // the chosen row var col = mix(pa_lit(), pa_light(), step(sb.w - 2.0 * b, pc.y)) col = mix(col, pa_ink(), step(-b, d)) out = pa_lay(out, col, inside * sel) // the menu pointer: a stepped triangle four pixels deep at the head let cy = pa_snap(self.rect_size.y * 0.5, o.y, b) let q = floor((pc - vec2(sb.x + 2.0 * b, cy)) / b) let tri = step(0.0, q.x) * step(q.x, 3.5) * step(abs(q.y + 0.5), 4.0 - q.x) * sel out = pa_lay(out, pa_ink(), tri) return out } // The open list of a drop-down: a light sheet in an ink frame with its // corners stepped off; the pointer's row lays the checker, the chosen row // is dark with lit words. mod.widgets.PopupMenuFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let sb = pa_sbox(o, vec2(0.0, 0.0), self.rect_size, b) let pc = pa_cell(p, o, b) return pa_cap(pc, b, sb, 1.0, 0.0, vec3(0.702, 0.773, 0.600), pa_lit(), pa_face(), pa_deep(), pa_ink(), 0.0) } mod.widgets.PopupMenuItem.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let live = 1.0 - step(0.5, self.disabled) let inrow = step(1.0, p.x) * step(p.x, self.rect_size.x - 1.0) var out = vec4(0.0, 0.0, 0.0, 0.0) let ha = pa_checker(p, o, b) * step(0.5, self.hover) * live * (1.0 - step(0.5, self.active)) * inrow out = pa_lay(out, pa_light(), ha) let ia = step(0.5, self.active) * inrow out = pa_lay(out, pa_dark(), ia) return out } mod.widgets.PopupMenuItem.draw_text.color_active = #d6e0b2 // THE SPINNER. Eight blocks two pixels square round a circle: one in ink, // the next ones stepping down through the dark and the mid to the mute. A // sheet may not read the pass clock and the spinner carries no other, so // it stands still. mod.widgets.SpinnerFlat.size = 18.67 mod.widgets.SpinnerFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let c = vec2(pa_snap(self.rect_size.x * 0.5, o.x, b), pa_snap(self.rect_size.y * 0.5, o.y, b)) let pc = pa_cell(p, o, b) let v = pc - c var th = atan2(v.x, -v.y) if th < 0.0 { th = th + 2.0 * PI } let k = floor(th / (PI * 0.25) + 0.5) let ang = k * PI * 0.25 let rr = floor(min(self.rect_size.x, self.rect_size.y) * 0.5 / b - 1.0) * b let bc = c + vec2(sin(ang), -cos(ang)) * (rr - b) let q = abs(pc - vec2(pa_snap(bc.x, o.x, b), pa_snap(bc.y, o.y, b))) let a = step(max(q.x, q.y), b) * self.alpha var col = pa_mid() col = mix(col, pa_dark2(), step(abs(k - 6.0), 0.5)) col = mix(col, pa_dark(), step(abs(k - 7.0), 0.5)) col = mix(col, pa_ink(), step(abs(k - 8.0), 0.5) + step(k, 0.5)) return vec4(col * a, a) } // Chips: a small raised cap 20 points tall inside the room its size rung // takes; a chosen chip (light letters) is the dark pushed-in cap. mod.widgets.ChipFlat.radius = 0.0 mod.widgets.ChipFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let inset = max((self.rect_size.y - 21.33) * 0.5, 0.0) let sb = pa_sbox(o, vec2(0.0, inset), vec2(self.rect_size.x - b, self.rect_size.y - inset), b) let pc = pa_cell(p, o, b) let chosen = step(0.6, dot(self.ink.rgb, vec3(0.299, 0.587, 0.114))) let hov = step(0.5, self.hover) let held = step(0.5, self.down) var out = vec4(0.0, 0.0, 0.0, 0.0) if chosen > 0.5 { out = pa_sunk(pc, b, vec4(sb.x, sb.y + b, sb.z, sb.w), 1.0, mix(pa_dark(), pa_dark2(), hov), pa_ink(), 1.0, 2.0) } else { let face = mix(mix(pa_face(), pa_hi(), hov), pa_down(), held) out = pa_cap(pc, b, vec4(sb.x, sb.y + held * b, sb.z, sb.w), 1.0, 0.0, face, mix(pa_light(), pa_mute(), held), pa_mute(), pa_deep(), pa_ink(), 0.3 * (1.0 - held)) } let d = pa_dbox(pc, sb, 1.0, b) let kf = step(-3.0 * b, d) * step(d, -2.0 * b) * self.keyed out = pa_lay(out, mix(pa_ink(), pa_lit(), chosen), kf) return out } // Badges: a small light cell in an ink frame with ink digits. mod.widgets.BadgePalette.neutral = #c0cf9f mod.widgets.BadgePalette.on_neutral = #0d2117 mod.widgets.BadgePalette.neutral_container = #93a87e mod.widgets.BadgePalette.on_neutral_container = #0d2117 mod.widgets.BadgeFlat.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let o = self.rect_pos let bh = min(17.33, self.rect_size.y) let sb = pa_sbox(o, vec2(0.0, (self.rect_size.y - bh) * 0.5), vec2(self.rect_size.x, (self.rect_size.y + bh) * 0.5), b) let pc = pa_cell(p, o, b) let d = pa_dbox(pc, sb, 1.0, b) let inside = step(d, 0.0) var col = self.color.rgb col = mix(col, pa_ink(), step(-b, d)) let a = inside * self.color.a return vec4(col * a, a) } // Rules between groups: one big pixel of ink. mod.widgets.Hr.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let y = pa_snap(self.rect_size.y * 0.5, self.rect_pos.y, b) let a = step(y - b, p.y) * (1.0 - step(y, p.y)) return vec4(pa_ink() * a, a) } mod.widgets.Vr.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let b = 2.0 * px let p = self.pos * self.rect_size let x = pa_snap(self.rect_size.x * 0.5, self.rect_pos.x, b) let a = step(x - b, p.x) * (1.0 - step(x, p.x)) return vec4(pa_ink() * a, a) } // --------------------------------------------------------------------------- // THE SCREEN VIEW keeps the stock face, the readout frame the pixel art // sheet has: an ink line at the top and the left, the shadow under the // top edge, the lighter face, the lit lip under the foot. // THE TOP. The caption bar is the shell's plastic with what the device // prints on it, in three plates parted by seams cut into the plastic: at // the left the battery plate with a glossy red lamp and BAT printed dark, // then the plate with the power legend pressed into it and a groove along // the middle with a gap for the title the label draws, and at the right // the keys' plate with the window's controls as the device's keys. The // lettering is drawn, each letter a few strokes and rings, so it is as // smooth as print at any size. // Distance to the segment from a to b. let pa_seg = fn(p: vec2, a: vec2, b: vec2) -> float { let pa = p - a let ba = b - a let h = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0) return length(pa - ba * h) } // A bold sans letter drawn in a box one cap height tall with its top left // at the origin: O N F B A T (0 to 5), a left-pointing triangle (6) and a // small ring (7). `h` is the cap height in points and `t` half the stroke // in points; returns the signed distance in points. let pa_letter = fn(ch: float, p: vec2, h: float, t: float) -> float { let q = p / h var d = abs(Material.sd_box(q, vec2(0.40, 0.50), vec2(0.30, 0.40), 0.30)) if ch > 0.5 { d = min(pa_seg(q, vec2(0.11, 0.11), vec2(0.11, 0.89)), pa_seg(q, vec2(0.69, 0.11), vec2(0.69, 0.89))) d = min(d, pa_seg(q, vec2(0.11, 0.11), vec2(0.69, 0.89))) } if ch > 1.5 { d = min(pa_seg(q, vec2(0.11, 0.11), vec2(0.11, 0.89)), pa_seg(q, vec2(0.11, 0.11), vec2(0.65, 0.11))) d = min(d, pa_seg(q, vec2(0.11, 0.50), vec2(0.58, 0.50))) } if ch > 2.5 { d = min(pa_seg(q, vec2(0.11, 0.11), vec2(0.11, 0.89)), pa_seg(q, vec2(0.11, 0.11), vec2(0.40, 0.11))) d = min(d, min(pa_seg(q, vec2(0.11, 0.50), vec2(0.40, 0.50)), pa_seg(q, vec2(0.11, 0.89), vec2(0.44, 0.89)))) d = min(d, max(abs(length(q - vec2(0.40, 0.305)) - 0.195), 0.40 - q.x)) d = min(d, max(abs(length(q - vec2(0.44, 0.695)) - 0.195), 0.44 - q.x)) } if ch > 3.5 { d = min(pa_seg(q, vec2(0.40, 0.11), vec2(0.08, 0.89)), pa_seg(q, vec2(0.40, 0.11), vec2(0.72, 0.89))) d = min(d, pa_seg(q, vec2(0.21, 0.62), vec2(0.59, 0.62))) } if ch > 4.5 { d = min(pa_seg(q, vec2(0.08, 0.11), vec2(0.72, 0.11)), pa_seg(q, vec2(0.40, 0.11), vec2(0.40, 0.89))) } if ch > 5.5 { d = min(pa_seg(q, vec2(0.08, 0.50), vec2(0.62, 0.14)), pa_seg(q, vec2(0.62, 0.14), vec2(0.62, 0.86))) d = min(d, pa_seg(q, vec2(0.62, 0.86), vec2(0.08, 0.50))) } if ch > 6.5 { d = abs(length(q - vec2(0.30, 0.50)) - 0.17) } return d * h - t } // The word BAT with its top left at `at`, the letters on a pitch of 6.3. let pa_bat = fn(p: vec2, at: vec2, h: float, t: float) -> float { var d = pa_letter(3.0, p - at, h, t) d = min(d, pa_letter(4.0, p - at - vec2(6.3, 0.0), h, t)) d = min(d, pa_letter(5.0, p - at - vec2(12.6, 0.0), h, t)) return d } // The power legend, the triangle, OFF, the ring and ON, with its top at // `top`; the triangle and the ring in a thinner stroke. let pa_legend = fn(p: vec2, top: float, h: float, t: float) -> float { var d = pa_letter(6.0, p - vec2(63.0, top), h, t * 0.6) d = min(d, pa_letter(0.0, p - vec2(77.0, top), h, t)) d = min(d, pa_letter(2.0, p - vec2(88.0, top), h, t)) d = min(d, pa_letter(2.0, p - vec2(99.0, top), h, t)) d = min(d, pa_letter(7.0, p - vec2(112.5, top), h, t * 0.6)) d = min(d, pa_letter(0.0, p - vec2(124.0, top), h, t)) d = min(d, pa_letter(1.0, p - vec2(135.0, top), h, t)) return d } // A seam cut into the plastic, lit from the top left: `d` runs across it // from the wall in shadow at zero, a dark band a point and a half wide, // then the lit wall from two points in to four and a half, then flat. // Returns the factor on the plastic. let pa_seam = fn(d: float, px: float) -> float { let dark = smoothstep(0.0 - 0.6 * px, 0.6 * px, d) * (1.0 - smoothstep(1.5 - 0.6 * px, 1.5 + 0.6 * px, d)) let lit = smoothstep(2.0 - 0.6 * px, 2.0 + 0.6 * px, d) * (1.0 - smoothstep(3.8, 5.0, d)) return 1.0 - 0.53 * dark + 0.21 * lit } // The battery lamp: a glossy red dome in a dark ring, its red deepening // toward the rim and glowing at the lower right, a pink spark at the upper // left, a thin shadow under it and a faint red cast on the plastic. let pa_lamp = fn(p: vec2, c: vec2, r: float, px: float) -> vec4 { let d = length(p - c) - r let a = Finish.cover(d, px) let u = (p - c) / r let z = sqrt(max(0.0, 1.0 - dot(u, u))) var col = vec3(0.80, 0.10, 0.07) col = mix(col, vec3(0.96, 0.16, 0.08), smoothstep(-0.7, 0.9, (u.x + u.y) * 0.7071)) col = col * (0.70 + 0.35 * z) col = col + vec3(0.55, 0.42, 0.44) * (1.0 - smoothstep(0.0, 0.55, length(u - vec2(-0.36, -0.40)))) col = mix(col, vec3(0.20, 0.03, 0.03), Finish.band(d, 0.0, 0.7, px)) let sh = (1.0 - smoothstep(0.0, 2.0, length(p - c - vec2(0.5, 0.9)) - r)) * (1.0 - a) * 0.38 let halo = (1.0 - smoothstep(0.0, 3.0, d)) * (1.0 - a) * 0.10 var out = vec4(0.0, 0.0, 0.0, sh) out = pa_over(out, vec4(vec3(0.9, 0.2, 0.2) * halo, halo)) out = pa_over(out, vec4(col * a, a)) return out } mod.widgets.Window.caption_bar.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * self.rect_size let w = p + self.rect_pos let gy = self.rect_size.y * 0.5 let cx = self.rect_size.x * 0.5 var col = pa_plastic(w, px) // the seams: under the top plate as far as the keys' plate, down the // keys' plate's edge, and down the battery plate's edge let kx = self.rect_size.x - 125.0 let sy = 22.0 col = col * mix(1.0, pa_seam(p.y - sy, px), step(p.x, kx)) col = col * pa_seam(p.x - kx, px) col = col * mix(1.0, pa_seam(p.x - 44.0, px), step(p.y, sy)) // the groove along the middle, from the legend to the keys, parting // for the title: a dark line and a lit line under it let along = step(150.0, p.x) * step(p.x, self.rect_size.x - 150.0) * step(110.0, abs(p.x - cx)) col = mix(col, col * 0.70, along * step(abs(p.y - gy), 0.75 * px + 0.001)) col = mix(col, col * 1.10, along * step(abs(p.y - gy - 1.5 * px), 0.75 * px + 0.001)) // the power legend pressed into the plastic, centred on its plate: the // letter a solid darker grey, the wall it meets at the top left a dark // line inside its edge there, and the lit wall at the bottom right a // bright line showing just outside its edge let sh = vec2(0.8, 0.8) let g0 = Finish.cover(pa_legend(p, 6.75, 8.5, 1.25), px) let gd = Finish.cover(pa_legend(p - sh, 6.75, 8.5, 1.25), px) let gu = Finish.cover(pa_legend(p + sh, 6.75, 8.5, 1.25), px) col = mix(col, col * 0.84, g0) col = mix(col, col * 0.66, g0 * (1.0 - gd)) col = mix(col, col * 1.28, (1.0 - g0) * gu) // the battery plate: BAT printed dark and bold, and the lamp let bat = Finish.cover(pa_bat(p, vec2(19.5, 7.5), 7.0, 0.8), px) col = mix(col, vec3(0.23, 0.24, 0.26), bat) col = pa_over(vec4(col, 1.0), pa_lamp(p, vec2(10.5, 11.5), 5.5, px)).rgb return vec4(col, 1.0) } // The countersunk well a key sits in, as a darkening of the plastic // outside the key: `d` the distance outside the key's edge, `facing` 0 // at the top left where the well's rim faces away from the light and 1 // at the bottom right where the key's own side casts into it. One soft // gradient from the key's edge outward, no ring: nearly black and short // at the bottom right, mild and wide at the top left. let pa_well = fn(d: float, facing: float, px: float) -> float { let w = mix(2.8, 1.8, facing) let fall = 1.0 - clamp(d / w, 0.0, 1.0) return mix(0.55, 0.85, facing) * fall * smoothstep(0.0 - 0.5 * px, 0.5 * px, d) } // A round magenta puck key: a flat disc with a rounded corner, lit from // the top left, so the corner catches the light there and falls into // shadow at the bottom right, the top a shade lighter toward the light, // a dark side, sitting in its countersunk well; `mark` is the pale mark // printed on the top. let pa_round_key = fn(p: vec2, c: vec2, r: float, px: float, lift: float, mark: float) -> vec4 { let d = length(p - c) - r let a = Finish.cover(d, px) let u = p - c let s = max(length(u), 0.0001) let dir = u / s let toward = clamp((0.0 - (dir.x + dir.y)) * 0.7071, -1.0, 1.0) let facing = 0.5 - 0.5 * toward let f = 2.4 let k = clamp((s - (r - f)) / f, 0.0, 1.0) let th = k * 1.5708 let sn = sin(th) let cs = cos(th) let lam = clamp(sn * toward * 0.71 + cs * 0.70, 0.0, 1.2) var col = vec3(0.46, 0.11, 0.31) * (0.35 + 0.95 * lam) * lift col = col * (1.0 + 0.10 * (0.0 - (u.x + u.y)) / r) col = col + vec3(0.08, 0.05, 0.07) * (1.0 - smoothstep(0.0, 1.1, length(u / r - vec2(-0.30, -0.35)))) // the corner goes pink where it faces the light, and the side is a // dark rim only where the key turns from the light col = mix(col, vec3(0.76, 0.44, 0.60), k * sn * max(toward, 0.0) * 0.6) col = mix(col, vec3(0.22, 0.04, 0.15), Finish.band(d, 0.0, 0.9, px) * facing) col = mix(col, vec3(0.95, 0.72, 0.88), mark) let well = pa_well(d, facing, px) * (1.0 - a) var out = vec4(0.0, 0.0, 0.0, well) out = pa_over(out, vec4(col * a, a)) return out } // The window's keys: the device's, each pressed into the plastic in a // countersunk well. Minimise is a glossy dark pill like the small keys // under a handheld's screen, a half-cylinder lit from above with a bright // rim along its top; maximise and close are magenta puck keys, their // marks pale and thin: a square outline and a cross. let DesktopButtonType = mod.widgets.DesktopButtonType mod.widgets.DesktopButton.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 lift = 1.0 + 0.08 * self.hover - 0.14 * self.down var out = vec4(0.0, 0.0, 0.0, 0.0) match self.button_type { DesktopButtonType.WindowsMin => { let hh = 5.0 let d = Material.sd_box(p, c, vec2(15.0, hh), hh) let a = Finish.cover(d, px) let v = clamp((p.y - c.y) / hh, -1.0, 1.0) let z = sqrt(max(0.0, 1.0 - v * v)) let lam = clamp((0.0 - v) * 0.55 + z * 0.80, 0.0, 1.2) var col = vec3(0.30, 0.30, 0.32) * (0.45 + 0.75 * lam) * lift let gx = (v + 0.58) / 0.20 col = col + vec3(0.42, 0.42, 0.42) * exp(0.0 - gx * gx) col = mix(col, vec3(0.14, 0.14, 0.15), Finish.band(d, 0.0, 0.5, px) * step(c.y - hh * 0.55, p.y)) col = mix(col, vec3(0.90, 0.90, 0.90), Finish.band(d, 0.0, 0.7, px) * step(p.y, c.y - hh * 0.55)) let q = (p - c) / vec2(15.0, hh) let ql = max(length(q), 0.0001) let facing = clamp(0.5 + 0.5 * (q.x + q.y) / ql * 0.7071, 0.0, 1.0) let well = pa_well(d, facing, px) * (1.0 - a) out = vec4(0.0, 0.0, 0.0, well) out = pa_over(out, vec4(col * a, a)) return out } DesktopButtonType.WindowsClose => { let m1 = pa_seg(p, c - vec2(3.0, 3.0), c + vec2(3.0, 3.0)) let m2 = pa_seg(p, c + vec2(-3.0, 3.0), c + vec2(3.0, -3.0)) let mark = Finish.cover(min(m1, m2) - 0.55, px) return pa_round_key(p, c, 10.0, px, lift, mark) } _ => { let mark = Finish.cover(abs(Material.sd_box(p, c, vec2(3.4, 3.4), 0.5)) - 0.5, px) return pa_round_key(p, c, 10.0, px, lift, mark) } } return out } // The keys stand thirty points apart on their plate, the close key // twenty-one from the edge; the pill keeps its wider cell. mod.widgets.Window.caption_bar.windows_buttons.max.width = 30.0 mod.widgets.Window.caption_bar.windows_buttons.close.width = 30.0 mod.widgets.Window.caption_bar.windows_buttons.margin = mod.turtle.Inset{left: 0.0 right: 6.0 top: 0.0 bottom: 0.0} // THE GROUND. The window is the handheld's shell, the light grey plastic; // every pane is a glass panel set into it, with a little plastic of its // own round it, so the shell runs on between the screens. The bar between // two panes is the plastic too. mod.widgets.Window.show_bg = true mod.widgets.Window.draw_bg.pixel = fn() { let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let w = self.pos * self.rect_size + self.rect_pos return vec4(pa_plastic(w, px), 1.0) } mod.widgets.Splitter.draw_bg.color_bg = #b0b0ae mod.widgets.Splitter.draw_bg.color = #b0b0ae mod.widgets.Splitter.draw_bg.color_hover = #9c9c9a mod.widgets.Splitter.draw_bg.color_drag = #888886 mod.widgets.Splitter.draw_bg.color_focus = #9c9c9a mod.widgets.Splitter.draw_bg.border_radius = 0.0 mod.widgets.Splitter.draw_bg.splitter_pad = 0.0 // ---------------------------------------------------------------- band // THE BAND is the sheet's own slider with two thumbs: the same track, the // same lit value and the same thumb as the Slider, the value lit between // the two thumbs. The widget's grab geometry is set to the thumbs' own, so // what is drawn is what is dragged. mod.widgets.RangeSliderFlat.clip_x = false mod.widgets.RangeSliderFlat.clip_y = false mod.widgets.RangeSliderFlat.track_inset = 0.0 mod.widgets.RangeSliderFlat.handle_size = 10.6667 mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() { let g = pa_grow(self.rect_pos, self.rect_size, 4.0) self.vertex_pos = self.clip_and_transform_vertex(g.xy, g.zw) } mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() { let m = 4.0 let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5) let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m) var out = pa_slider(p, self.rect_pos, self.rect_size, px, self.label_px, self.end_pos, self.start_pos, 1.0, self.hot_end, self.focus, self.drag * self.hot_end, self.disabled, 0.0, self.rect_size.x, 1.0) // The near thumb, as the slider's own thumb is placed. let b = 2.0 * px let tw = 8.0 let yc = (self.label_px + self.rect_size.y) * 0.5 let span0 = tw * b * 0.5 let span1 = self.rect_size.x - tw * b * 0.5 let hx = span0 + self.start_pos * (span1 - span0) return pa_over(out, pa_thumb(p, self.rect_pos, b, hx, yc, tw, 11.0, self.hot_start, self.drag * self.hot_start, self.focus, step(0.5, self.disabled))) } // ---------------------------------------------------------------- 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: 39.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleSlide.label_walk.margin = mod.turtle.Inset{left: 39.0 right: 0.0 top: 0.0 bottom: 0.0} mod.widgets.ToggleRocker.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 4.0 bottom: 4.0} mod.widgets.ToggleSlide.padding = mod.turtle.Inset{left: 0.0 right: 0.0 top: 4.0 bottom: 4.0} mod.widgets.ToggleRocker.draw_bg.size = uniform(16.0) mod.widgets.ToggleSlide.draw_bg.size = uniform(16.0) mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(2.0) mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(2.0) mod.widgets.ToggleRocker.draw_bg.knob_inset = uniform(1.33) mod.widgets.ToggleSlide.draw_bg.knob_inset = uniform(1.33) mod.widgets.ToggleRocker.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex mod.widgets.ToggleSlide.draw_bg.vertex = mod.widgets.ToggleFlat.draw_bg.vertex mod.widgets.ToggleRocker.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel mod.widgets.ToggleSlide.draw_bg.pixel = mod.widgets.ToggleFlat.draw_bg.pixel mod.widgets.ToggleRocker.clip_x = false mod.widgets.ToggleSlide.clip_x = false mod.widgets.ToggleRocker.clip_y = false mod.widgets.ToggleSlide.clip_y = false // 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 } true