2325 lines
122 KiB
Text
2325 lines
122 KiB
Text
use mod.prelude.widgets_internal.*
|
|
|
|
// Line frames of a head-up display over a dark ground.
|
|
// A control is drawn, not built: a one-device-pixel frame in the cyan ink,
|
|
// brightest on its top edge, cut at 45 degrees on two opposite corners,
|
|
// around a face that is a dark tint of the ground lit from inside. One run
|
|
// of each top line burns at full with a short bloom (a pale core, a
|
|
// saturated body, a soft halo); the rest of the frame never glows. On is a
|
|
// solid accent block; focus is four corner brackets standing off the box.
|
|
mod.widgets.Button.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.Button.draw_bg.border_radius = 0.0
|
|
mod.widgets.Button.draw_bg.border_size = 0.34
|
|
// A ROW OF FIELDS IS ONE HEIGHT: the box metrics are stated once here and
|
|
// handed to each field below, and the buttons take the same 26 point row.
|
|
let field_height = 26.0
|
|
let field_pad_v = 4.0
|
|
let field_radius = 0.0
|
|
let field_border = 0.34
|
|
|
|
// The room a Fit field takes: where a field is Fit, the padding IS its
|
|
// height.
|
|
let field_room = fn(w) {
|
|
w.min_height = field_height
|
|
w.padding.top = field_pad_v
|
|
w.padding.bottom = field_pad_v
|
|
}
|
|
// The box a field draws. Given the draw object and not the widget, because
|
|
// not every field draws its own box.
|
|
let field_face = fn(d) {
|
|
d.border_radius = field_radius
|
|
d.border_size = field_border
|
|
}
|
|
// A field that states a frame and lays its own parts out inside it takes
|
|
// the height alone: a padding would displace its line.
|
|
let field_frame = fn(w) {
|
|
w.min_height = field_height
|
|
}
|
|
|
|
field_room(mod.widgets.TextInput)
|
|
field_face(mod.widgets.TextInput.draw_bg)
|
|
field_room(mod.widgets.TextInputFlat)
|
|
field_face(mod.widgets.TextInputFlat.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 = #03090f
|
|
|
|
// Light and focus marks leave the layout box: plain views stop clipping so
|
|
// a bloom or a bracket can fall into the gap beside a control. Scroll views
|
|
// still clip.
|
|
mod.widgets.View.clip_x = false
|
|
mod.widgets.View.clip_y = false
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SHARED PARTS. Distances in points, negative inside, y down; `px` is one
|
|
// device pixel in points. The inks, darkest first:
|
|
// ground #03090f, well #050d13, dim line #1d3a42, foot line #2e6570, side
|
|
// line #3f7f8a, top line #6fa9b3, accent #36c8dc, bright #6af0f5, run
|
|
// #bfe9ef, text #b8eef5, hot #e6fdff. Halo body: the accent at 0.45.
|
|
|
|
// The halo of a lit line, x points out from it: a gaussian near part over
|
|
// an exponential tail, cut to nothing at `reach`. 0.77 of `amp` one device
|
|
// pixel out, 0.38 at 2 points, 0.15 at 3 (reach 3.5).
|
|
let h_glow = fn(x: float, amp: float, reach: float) -> float {
|
|
let t = max(x, 0.0)
|
|
let n = 0.6 * exp(0.0 - t * t / (0.35 * reach * reach)) + 0.4 * exp(0.0 - 3.0 * t / reach)
|
|
return amp * n * (1.0 - smoothstep(0.7 * reach, reach, t))
|
|
}
|
|
// Halo colour: the saturated accent close to the line, its deep body
|
|
// further out.
|
|
let h_halo_ink = fn(x: float) -> vec3 {
|
|
return mix(vec3(0.10, 0.42, 0.47), vec3(0.212, 0.784, 0.863), exp(0.0 - max(x, 0.0) / 0.8))
|
|
}
|
|
// Light added over a premultiplied colour.
|
|
let h_add = fn(under: vec4, ink: vec3, g: float) -> vec4 {
|
|
return vec4(under.rgb + ink * g, min(under.a + g * 0.6, 1.0))
|
|
}
|
|
|
|
// Which way the edge under a frame pixel faces: -1 up, +1 down, 0 sideways,
|
|
// the cut corners between. `k` are the cuts (tl, tr, br, bl).
|
|
let h_facing = fn(p: vec2, lo: vec2, hi: vec2, k: vec4, px: float) -> float {
|
|
var ny = 0.0
|
|
ny = mix(ny, -1.0, step(p.y, lo.y + 1.5 * px))
|
|
ny = mix(ny, 1.0, step(hi.y - 1.5 * px, p.y))
|
|
let tl = step(p.x - lo.x + p.y - lo.y, k.x + 1.5 * px) * step(0.01, k.x)
|
|
let tr = step(hi.x - p.x + p.y - lo.y, k.y + 1.5 * px) * step(0.01, k.y)
|
|
let br = step(hi.x - p.x + hi.y - p.y, k.z + 1.5 * px) * step(0.01, k.z)
|
|
let bl = step(p.x - lo.x + hi.y - p.y, k.w + 1.5 * px) * step(0.01, k.w)
|
|
ny = mix(ny, -0.7, max(tl, tr))
|
|
ny = mix(ny, 0.7, max(br, bl))
|
|
return ny
|
|
}
|
|
// Light from above, told by line brightness only: the top ink on an edge
|
|
// facing up, the side ink sideways, the foot ink facing down.
|
|
let h_ink3 = fn(ny: float, top: vec3, side: vec3, foot: vec3) -> vec3 {
|
|
return mix(mix(side, top, max(0.0 - ny, 0.0)), foot, max(ny, 0.0))
|
|
}
|
|
// The rest ladder of a frame.
|
|
let h_top = fn() -> vec3 { return vec3(0.435, 0.663, 0.702) }
|
|
let h_side = fn() -> vec3 { return vec3(0.247, 0.498, 0.541) }
|
|
let h_foot = fn() -> vec3 { return vec3(0.180, 0.396, 0.439) }
|
|
let h_dim = fn() -> vec3 { return vec3(0.114, 0.227, 0.259) }
|
|
let h_acc = fn() -> vec3 { return vec3(0.212, 0.784, 0.863) }
|
|
let h_bright = fn() -> vec3 { return vec3(0.416, 0.941, 0.961) }
|
|
let h_well = fn() -> vec3 { return vec3(0.020, 0.051, 0.075) }
|
|
|
|
// Corner brackets on a square frame: 1 dpx arms `arm` long at the four
|
|
// corners of the box lo..hi. Returns coverage.
|
|
let h_brackets = fn(p: vec2, lo: vec2, hi: vec2, arm: float, px: float) -> float {
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 0.0)
|
|
let ex = min(p.x - lo.x, hi.x - p.x)
|
|
let ey = min(p.y - lo.y, hi.y - p.y)
|
|
return Finish.band(d, 0.0, px, px) * step(ex, arm) * step(ey, arm)
|
|
}
|
|
// The focus mark: brackets 2 points outside the box, 5 point arms, the
|
|
// accent, with the short bloom the one focused thing may have. Laid over
|
|
// `under`.
|
|
let h_focus = fn(under: vec4, p: vec2, lo: vec2, hi: vec2, px: float, focus: float) -> vec4 {
|
|
let off = 3.0 * px
|
|
let a = lo - vec2(off, off)
|
|
let b = hi + vec2(off, off)
|
|
let br = h_brackets(p, a, b, 5.0, px) * focus
|
|
let c = (a + b) * 0.5
|
|
let d = Material.sd_box(p, c, (b - a) * 0.5, 0.0)
|
|
let ex = min(p.x - a.x, b.x - p.x)
|
|
let ey = min(p.y - a.y, b.y - p.y)
|
|
let near = (1.0 - smoothstep(4.0, 7.0, ex)) * (1.0 - smoothstep(4.0, 7.0, ey))
|
|
let x = abs(d + 0.5 * px) - 0.5 * px
|
|
let g = h_glow(x, 0.30 * focus, 3.0) * near * (1.0 - br)
|
|
let o = h_add(under, h_halo_ink(x), g)
|
|
return mix(o, vec4(h_acc(), 1.0), br)
|
|
}
|
|
|
|
// Distance to a segment.
|
|
let h_seg = fn(p: vec2, a: vec2, b: vec2) -> float {
|
|
let pa = p - a
|
|
let ba = b - a
|
|
let t = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0)
|
|
return length(pa - ba * t)
|
|
}
|
|
|
|
// A hexagon with flat top and bottom, `r` from the centre to a flat side.
|
|
let h_hex = fn(q: vec2, r: float) -> float {
|
|
var p = abs(q)
|
|
let t = 2.0 * min(-0.866025404 * p.x + 0.5 * p.y, 0.0)
|
|
p = p - vec2(t * -0.866025404, t * 0.5)
|
|
let cx = clamp(p.x, -0.577350269 * r, 0.577350269 * r)
|
|
let e = vec2(p.x - cx, p.y - r)
|
|
let l = length(e)
|
|
return mix(l, 0.0 - l, step(e.y, 0.0))
|
|
}
|
|
|
|
// The wait mark a replaced face draws itself: twelve short ticks on a ring
|
|
// where the label was, a bright head turning once a second on the button's
|
|
// own animation clock and a fading tail.
|
|
let h_wait = fn(under: vec4, p: vec2, lo: vec2, hi: vec2, px: float, loading: float, t: float) -> vec4 {
|
|
let c = (lo + hi) * 0.5
|
|
let r = clamp((hi.y - lo.y) * 0.3, 3.0, 8.0)
|
|
let q = p - c
|
|
let a = fract(atan2(q.y, q.x) / 6.2831853 + 0.25)
|
|
let slot = floor(a * 12.0 + 0.5)
|
|
let ang = slot / 12.0 * 6.2831853 - 1.5707963
|
|
let dir = vec2(cos(ang), sin(ang))
|
|
let s = h_seg(q, dir * (r - 2.5), dir * r)
|
|
let head = fract(slot / 12.0 - t)
|
|
let lit = mix(0.25, 1.0, pow(1.0 - head, 3.0))
|
|
let cov = Finish.cover(s - 0.5 * px, px) * loading
|
|
return mix(under, vec4(mix(h_dim(), h_bright(), lit), 1.0), cov)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// BUTTONS. One face for the family; `style` picks the dressing:
|
|
// 0 standard, 1 primary (double line, the one bloom), 2 secondary (armed:
|
|
// the face at a fifth of the accent, the line in the accent), 3 danger (the
|
|
// red role), 4 outline (line and no face), 5 flat (a foot line only until
|
|
// the pointer comes), 6 flatter (nothing until the pointer comes),
|
|
// 7 tertiary (a dim square frame with bright corner brackets), 8 dashed
|
|
// (the outline in 3 point dashes).
|
|
// The quad is grown by 6 points for the bloom and the focus brackets.
|
|
let h_button = fn(p: vec2, o: vec2, s: vec2, px: float, cuts: vec4, hover: float, down: float, focus: float, disabled: float, style: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + s.x, px), Finish.snap(o.y + s.y, px)) - o
|
|
let hh = hi.y - lo.y
|
|
let ww = hi.x - lo.x
|
|
let tile = step(6.5, style) * step(style, 7.5)
|
|
let dashed = step(7.5, style)
|
|
let k = Finish.snap(clamp(0.38 * hh, 3.0, 12.0), px)
|
|
let kq = cuts * k * (1.0 - tile)
|
|
let c = (lo + hi) * 0.5
|
|
let d = Finish.sd_notch(p, c, (hi - lo) * 0.5, kq)
|
|
let yn = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
|
|
let live = 1.0 - disabled
|
|
let hv = hover * live
|
|
let dn = down * live
|
|
let hero = step(0.5, style) * step(style, 1.5)
|
|
let armed = step(1.5, style) * step(style, 2.5)
|
|
let danger = step(2.5, style) * step(style, 3.5)
|
|
let outline = max(step(3.5, style) * step(style, 4.5), dashed)
|
|
let flat = step(4.5, style) * step(style, 6.5)
|
|
let flatter = step(5.5, style) * step(style, 6.5)
|
|
|
|
// The face: flat within a few percent at rest, lit from below on the
|
|
// primary and the armed face, turned over and darker while held.
|
|
var ft = vec3(0.051, 0.114, 0.145)
|
|
var fb = vec3(0.039, 0.090, 0.118)
|
|
ft = mix(ft, vec3(0.055, 0.125, 0.161), hero)
|
|
fb = mix(fb, vec3(0.078, 0.180, 0.224), hero)
|
|
ft = mix(ft, vec3(0.035, 0.122, 0.153), armed)
|
|
fb = mix(fb, vec3(0.063, 0.220, 0.275), armed)
|
|
ft = mix(ft, vec3(0.239, 0.047, 0.071), danger)
|
|
fb = mix(fb, vec3(0.271, 0.078, 0.102), danger)
|
|
var face = mix(ft, fb, yn) + vec3(0.022, 0.030, 0.036) * hv * (1.0 - dn)
|
|
// Held, the face lights from inside as an active one does: dark at the
|
|
// head, the accent's deep ink at the foot.
|
|
let lit_face = mix(vec3(0.0, 0.094, 0.133), vec3(0.0, 0.227, 0.302), yn * yn)
|
|
face = mix(face, mix(lit_face, mix(fb, ft, yn) * 1.4, danger), dn)
|
|
face = mix(face, vec3(0.027, 0.071, 0.102), disabled)
|
|
let rest_a = 0.9 * (1.0 - outline) * (1.0 - flat) * mix(1.0, 0.45, tile)
|
|
var fa = max(rest_a, (outline + flat) * max(0.45 * hv, 0.8 * dn))
|
|
fa = mix(fa, 0.6 * rest_a, disabled)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(face * fa * inside, fa * inside)
|
|
|
|
// The contact line: one dark device row inside the foot, gone while held.
|
|
let foot = Finish.band(d, px, 2.0 * px, px) * step(hi.y - 2.5 * px, p.y)
|
|
out = mix(out, vec4(0.020, 0.047, 0.063, 1.0), foot * step(0.5, fa) * (1.0 - dn))
|
|
|
|
// The frame, top bright, sides three quarters, foot half.
|
|
let ny = h_facing(p, lo, hi, kq, px)
|
|
var top = h_top()
|
|
var side = h_side()
|
|
var bot = h_foot()
|
|
top = mix(top, vec3(0.557, 0.639, 0.631), hero)
|
|
side = mix(side, vec3(0.624, 0.812, 0.831), hero)
|
|
bot = mix(bot, vec3(0.518, 0.596, 0.600), hero)
|
|
top = mix(top, vec3(0.300, 0.860, 0.930), armed)
|
|
side = mix(side, h_acc() * 0.72, armed)
|
|
bot = mix(bot, h_acc() * 0.55, armed)
|
|
top = mix(top, vec3(0.510, 0.149, 0.200), danger)
|
|
side = mix(side, vec3(0.560, 0.150, 0.230), danger)
|
|
bot = mix(bot, vec3(0.612, 0.145, 0.263), danger)
|
|
var lc = h_ink3(ny, top, side, bot)
|
|
lc = Finish.lift(lc, 0.24 * max(hv, dn))
|
|
// The tile's own frame is the dim line; its brackets carry the ink.
|
|
lc = mix(lc, mix(h_dim(), h_foot(), hv), tile)
|
|
lc = mix(lc, h_dim(), disabled)
|
|
let edge = Finish.band(d, 0.0, px, px)
|
|
// Flat: a foot line alone at rest; flatter: nothing. Both frame up
|
|
// under the pointer and when held.
|
|
let footline = step(hi.y - 1.5 * px, p.y) * (1.0 - flatter)
|
|
var shown = mix(1.0, max(max(footline * 0.8, 0.6 * hv), dn), flat)
|
|
// Dashes counted in window pixels along the edge, so they run on
|
|
// unbroken round the corners.
|
|
let run_len = p.x + p.y + o.x + o.y
|
|
let dash = step(fract(run_len / 4.5), 0.62)
|
|
shown = shown * mix(1.0, dash, dashed)
|
|
out = mix(out, vec4(lc, 1.0), edge * shown)
|
|
|
|
// The primary's second line, 0.11 of the height in, at half the light.
|
|
let ins = max(Finish.snap(0.11 * hh, px), 2.0 * px)
|
|
let inner = Finish.band(d + ins, 0.0, px, px) * hero
|
|
let ic = Finish.lift(mix(vec3(0.353, 0.541, 0.565), vec3(0.420, 0.561, 0.580), yn), 0.12 * max(hv, dn))
|
|
out = mix(out, vec4(mix(ic, h_dim(), disabled), 1.0), inner)
|
|
|
|
// The tile's corner brackets.
|
|
let arm = min(0.3 * min(ww, hh), 8.0)
|
|
let brk = h_brackets(p, lo, hi, arm, px) * tile
|
|
let bc = Finish.lift(h_ink3(ny, h_top(), h_side(), h_foot()), 0.12 + 0.2 * max(hv, dn))
|
|
out = mix(out, vec4(mix(bc, h_dim(), disabled), 1.0), brk)
|
|
|
|
// The bright run on the top line: from the cut corner along 30 % of the
|
|
// width (40 % on the primary), longer under the pointer, fading into
|
|
// the dim line at its far end. It carries the only bloom of a control:
|
|
// amp 0.40 over 4.5 points, the primary's 0.60 over 5.
|
|
let runs = (1.0 - flat) * (1.0 - tile) * (1.0 - dashed) * live
|
|
let x0 = lo.x + kq.x
|
|
let avail = ww - kq.x - kq.y - 4.0
|
|
let rl = min(max((mix(0.30, 0.40, hero) + mix(0.30, 0.20, hero) * hv) * ww, 22.0 + 10.0 * hv), avail)
|
|
let rx = smoothstep(x0 - px, x0 + 2.0, p.x) * (1.0 - smoothstep(x0 + rl * 0.65, x0 + rl, p.x)) * runs
|
|
let topline = edge * step(p.y, lo.y + 1.5 * px)
|
|
var rc = vec3(0.749, 0.914, 0.937)
|
|
rc = mix(rc, vec3(0.902, 0.992, 1.0), hero)
|
|
rc = mix(rc, vec3(0.659, 0.969, 0.980), armed)
|
|
rc = mix(rc, vec3(0.961, 0.412, 0.424), danger)
|
|
out = mix(out, vec4(rc, 1.0), topline * rx)
|
|
let amp = (mix(0.40, 0.60, hero) + 0.14 * hv) * (1.0 - dn * 0.6)
|
|
var hink = h_halo_ink(d)
|
|
hink = mix(hink, vec3(0.45, 0.10, 0.12), danger)
|
|
let above = 1.0 - smoothstep(lo.y - px, lo.y + px, p.y)
|
|
let gout = h_glow(d, amp, mix(4.5, 5.0, hero)) * rx * above * (1.0 - inside)
|
|
out = h_add(out, hink, gout)
|
|
// The same light falls a little way into the face below the run.
|
|
let gin = h_glow(0.0 - d - px, amp * 0.35, 2.5) * rx * inside * (1.0 - edge) * step(p.y, lo.y + 4.0)
|
|
out = h_add(out, hink, gin)
|
|
// The primary's whole outer line throws a faint bloom.
|
|
let gb = h_glow(d, (0.10 + 0.06 * hv) * hero * live * (1.0 - dn), 2.5) * (1.0 - inside)
|
|
out = h_add(out, h_halo_ink(d), gb)
|
|
|
|
return h_focus(out, p, lo, hi, px, focus * live)
|
|
}
|
|
|
|
mod.widgets.ButtonFlat.clip_x = false
|
|
mod.widgets.ButtonFlat.clip_y = false
|
|
mod.widgets.ButtonFlat.padding = mod.turtle.Inset{left: 12.0 right: 12.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.ButtonFlat.min_height = 26.0
|
|
mod.widgets.ButtonFlat.icon_walk = Walk{width: 13.0, height: Fit}
|
|
mod.widgets.ButtonFlat.draw_bg.glow_room = uniform(6.0)
|
|
mod.widgets.ButtonFlat.draw_bg.vertex = fn() {
|
|
let m = self.glow_room
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.ButtonFlat.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(1.0, 0.0, 1.0, 0.0), self.hover, self.down, self.focus, self.disabled, 5.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
mod.widgets.Button.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(1.0, 0.0, 1.0, 0.0), self.hover, self.down, self.focus, self.disabled, 0.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
// The size rungs and the compound are standard buttons. They share the
|
|
// flat button's draw object, so they are handed one of their own.
|
|
mod.widgets.ButtonXs.draw_bg = mod.widgets.Button.draw_bg{}
|
|
mod.widgets.ButtonSm.draw_bg = mod.widgets.Button.draw_bg{}
|
|
mod.widgets.ButtonLg.draw_bg = mod.widgets.Button.draw_bg{}
|
|
mod.widgets.ButtonXl.draw_bg = mod.widgets.Button.draw_bg{}
|
|
mod.widgets.ButtonCompound.draw_bg = mod.widgets.Button.draw_bg{}
|
|
mod.widgets.ButtonXs.min_height = 18.0
|
|
mod.widgets.ButtonSm.min_height = 22.0
|
|
mod.widgets.ButtonLg.min_height = 32.0
|
|
mod.widgets.ButtonXl.min_height = 38.0
|
|
mod.widgets.ButtonSm.padding = mod.turtle.Inset{left: 9.0 right: 9.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ButtonXs.padding = mod.turtle.Inset{left: 7.0 right: 7.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ButtonLg.padding = mod.turtle.Inset{left: 16.0 right: 16.0 top: 0.0 bottom: 0.0}
|
|
mod.widgets.ButtonIcon.padding = mod.turtle.Inset{left: 8.0 right: 8.0 top: 4.0 bottom: 4.0}
|
|
|
|
mod.widgets.ButtonPrimary.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(1.0, 0.0, 1.0, 0.0), self.hover, self.down, self.focus, self.disabled, 1.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
mod.widgets.ButtonSecondary.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(1.0, 0.0, 1.0, 0.0), self.hover, self.down, self.focus, self.disabled, 2.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
mod.widgets.ButtonTertiary.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(0.0, 0.0, 0.0, 0.0), self.hover, self.down, self.focus, self.disabled, 7.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
mod.widgets.ButtonDanger.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(1.0, 0.0, 1.0, 0.0), self.hover, self.down, self.focus, self.disabled, 3.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
mod.widgets.ButtonOutline.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(1.0, 0.0, 0.0, 0.0), self.hover, self.down, self.focus, self.disabled, 4.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
mod.widgets.ButtonDashed.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(1.0, 0.0, 0.0, 0.0), self.hover, self.down, self.focus, self.disabled, 8.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
mod.widgets.ButtonFlatter.draw_bg.pixel = fn() {
|
|
let m = self.glow_room
|
|
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 f = h_button(p, self.rect_pos, self.rect_size, px, vec4(1.0, 0.0, 1.0, 0.0), self.hover, self.down, self.focus, self.disabled, 6.0)
|
|
return self.host_over(h_wait(f, p, vec2(0.0, 0.0), self.rect_size, px, self.loading, self.anim_time), m)
|
|
}
|
|
|
|
// Button inks: the hot white-cyan on the primary, the accent on the armed
|
|
// face, the red role's pale rose on danger, the dim line when off.
|
|
mod.widgets.ButtonPrimary.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
|
|
mod.widgets.ButtonPrimary.draw_text.color = #e6fdff
|
|
mod.widgets.ButtonPrimary.draw_text.color_hover = #ffffff
|
|
mod.widgets.ButtonPrimary.draw_text.color_down = #e6fdff
|
|
mod.widgets.ButtonPrimary.draw_text.color_focus = #e6fdff
|
|
mod.widgets.ButtonPrimary.draw_text.color_disabled = #33545b
|
|
mod.widgets.ButtonPrimary.draw_icon.color = #e6fdff
|
|
mod.widgets.ButtonSecondary.draw_text.color = #5ddcec
|
|
mod.widgets.ButtonSecondary.draw_text.color_hover = #8cf0f8
|
|
mod.widgets.ButtonSecondary.draw_text.color_down = #8cf0f8
|
|
mod.widgets.ButtonSecondary.draw_text.color_focus = #5ddcec
|
|
mod.widgets.ButtonSecondary.draw_text.color_disabled = #33545b
|
|
mod.widgets.ButtonSecondary.draw_icon.color = #5ddcec
|
|
mod.widgets.ButtonTertiary.draw_text.color = #a6d9e0
|
|
mod.widgets.ButtonTertiary.draw_text.color_hover = #d4f7fb
|
|
mod.widgets.ButtonTertiary.draw_text.color_down = #d4f7fb
|
|
mod.widgets.ButtonTertiary.draw_text.color_focus = #b8eef5
|
|
mod.widgets.ButtonTertiary.draw_text.color_disabled = #33545b
|
|
mod.widgets.ButtonTertiary.draw_icon.color = #a6d9e0
|
|
mod.widgets.ButtonDanger.draw_text.color = #e1b8c0
|
|
mod.widgets.ButtonDanger.draw_text.color_hover = #f5d8dd
|
|
mod.widgets.ButtonDanger.draw_text.color_down = #f5d8dd
|
|
mod.widgets.ButtonDanger.draw_text.color_focus = #e1b8c0
|
|
mod.widgets.ButtonDanger.draw_text.color_disabled = #4a353a
|
|
mod.widgets.ButtonDanger.draw_icon.color = #e1b8c0
|
|
mod.widgets.ButtonOutline.draw_text.color = #a6d9e0
|
|
mod.widgets.ButtonOutline.draw_text.color_hover = #d4f7fb
|
|
mod.widgets.ButtonOutline.draw_text.color_down = #d4f7fb
|
|
mod.widgets.ButtonOutline.draw_text.color_focus = #b8eef5
|
|
mod.widgets.ButtonOutline.draw_text.color_disabled = #33545b
|
|
mod.widgets.ButtonPrimary.draw_bg.loading_color = #6af0f5
|
|
// The trailing icon is a draw object of its own, which the icon ink above
|
|
// does not reach, and the roles share the flat button's until each is
|
|
// handed one of its own with the ink of its leading icon.
|
|
mod.widgets.ButtonFlat.draw_icon_end.color = #a6d9e0
|
|
mod.widgets.ButtonPrimary.draw_icon_end = mod.widgets.ButtonFlat.draw_icon_end{color: #e6fdff}
|
|
mod.widgets.ButtonSecondary.draw_icon_end = mod.widgets.ButtonFlat.draw_icon_end{color: #5ddcec}
|
|
mod.widgets.ButtonDanger.draw_icon_end = mod.widgets.ButtonFlat.draw_icon_end{color: #e1b8c0}
|
|
mod.widgets.ButtonTertiary.draw_icon_end = mod.widgets.ButtonFlat.draw_icon_end{color: #a6d9e0}
|
|
|
|
// 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 h_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}}
|
|
}
|
|
h_held_states(mod.widgets.ButtonFlat)
|
|
h_held_states(mod.widgets.CheckBoxFlat)
|
|
h_held_states(mod.widgets.ToggleFlat)
|
|
h_held_states(mod.widgets.RadioButtonFlat)
|
|
h_held_states(mod.widgets.RadioButtonTabFlat)
|
|
h_held_states(mod.widgets.TextInputFlat)
|
|
h_held_states(mod.widgets.DropDownFlat)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CHECK BOXES. A 14 point square: the frame, a device pixel of ground, then
|
|
// the mark as a solid accent block 0.7 of the box. No tick: the block is the
|
|
// mark. Mixed is the block at half light cut by a dark bar; error takes the
|
|
// red role; held arms the well at a fifth of the accent; focus brackets it.
|
|
let h_check = fn(p: vec2, o: vec2, rs: vec2, px: float, sz: float, at_end: float, hover: float, down: float, focus: float, active: float, mixed: float, err: float, disabled: float) -> vec4 {
|
|
let x0 = (rs.x - sz) * at_end
|
|
let y0 = rs.y * 0.5 - sz * 0.5
|
|
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + y0, px)) - o
|
|
let hi = lo + vec2(Finish.snap(sz, px), Finish.snap(sz, px))
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 0.0)
|
|
let live = 1.0 - disabled
|
|
let hv = hover * live
|
|
let on = max(active, mixed)
|
|
let ny = h_facing(p, lo, hi, vec4(0.0, 0.0, 0.0, 0.0), px)
|
|
var lc = h_ink3(ny, h_top(), h_side(), h_foot())
|
|
lc = mix(lc, h_ink3(ny, h_bright(), h_acc(), h_acc() * 0.8), on)
|
|
lc = mix(lc, h_ink3(ny, vec3(0.961, 0.412, 0.424), vec3(0.80, 0.30, 0.33), vec3(0.612, 0.145, 0.263)), err)
|
|
lc = Finish.lift(lc, 0.26 * max(hv, down * live))
|
|
lc = mix(lc, h_dim(), disabled)
|
|
let inside = Finish.cover(d, px)
|
|
let well = mix(mix(h_well(), vec3(0.055, 0.184, 0.216), down * live), h_well() + h_acc() * 0.06, hv * (1.0 - down))
|
|
var out = vec4(mix(well, vec3(0.012, 0.035, 0.059), disabled) * inside, inside)
|
|
out = mix(out, vec4(lc, 1.0), Finish.band(d, 0.0, px, px))
|
|
// The mark, 3 device pixels in from the frame.
|
|
let md = d + 3.0 * px
|
|
let mk = Finish.cover(md, px)
|
|
var ink = mix(h_acc(), h_bright(), hv)
|
|
ink = mix(ink, vec3(1.0, 0.376, 0.408), err)
|
|
let block = mix(ink, ink * 0.55, mixed * (1.0 - active))
|
|
let lit = mk * on * live
|
|
out = mix(out, vec4(block, 1.0), lit)
|
|
// A device pixel of dark bar through the mixed block.
|
|
let bar = Finish.cover(abs(p.y - c.y) - 0.5 * px, px) * step(abs(p.x - c.x), 2.5) * mixed * (1.0 - active) * live
|
|
out = mix(out, vec4(0.020, 0.075, 0.102, 1.0), bar)
|
|
// Off and on, a disabled mark is only a line.
|
|
let ghost = Finish.band(md, 0.0, px, px) * on * disabled
|
|
out = mix(out, vec4(h_dim(), 1.0), ghost)
|
|
return h_focus(out, p, lo, hi, px, focus * live)
|
|
}
|
|
|
|
mod.widgets.CheckBoxFlat.clip_x = false
|
|
mod.widgets.CheckBoxFlat.clip_y = false
|
|
mod.widgets.CheckBoxFlat.label_walk.margin.left = 16.0
|
|
mod.widgets.CheckBoxFlat.draw_bg.size = uniform(14.0)
|
|
mod.widgets.CheckBoxFlat.draw_bg.border_radius = uniform(0.0)
|
|
mod.widgets.CheckBoxFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
|
|
}
|
|
mod.widgets.CheckBoxFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
|
|
return h_check(p, self.rect_pos, self.rect_size, px, self.size, self.mark_at_end, self.hover, self.down, self.focus, self.active, self.mixed, self.error, self.disabled)
|
|
}
|
|
mod.widgets.CheckBoxFlat.draw_text.color_error = #ff9aa0
|
|
|
|
// RADIOS. A hexagon with flat top and bottom, the frame's light from above;
|
|
// on, a smaller solid hexagon in the accent.
|
|
mod.widgets.RadioButtonFlat.clip_x = false
|
|
mod.widgets.RadioButtonFlat.clip_y = false
|
|
mod.widgets.RadioButtonFlat.label_walk.margin.left = 16.0
|
|
mod.widgets.RadioButtonFlat.draw_bg.size = uniform(14.0)
|
|
mod.widgets.RadioButtonFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
|
|
}
|
|
mod.widgets.RadioButtonFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
|
|
let o = self.rect_pos
|
|
// Points up and down, the flat sides on whole device pixels.
|
|
let r = Finish.snap(self.size * 0.46, px)
|
|
let rr = r * 1.1547
|
|
let c = vec2(Finish.snap(o.x + self.size * 0.5 - r, px) + r - o.x, Finish.snap(o.y + self.rect_size.y * 0.5, px) - o.y)
|
|
let d = h_hex(vec2(p.y - c.y, p.x - c.x), r)
|
|
let live = 1.0 - self.disabled
|
|
let hv = self.hover * live
|
|
let dn = self.down * live
|
|
let q = p - c
|
|
let ny = clamp(q.y / rr, -1.0, 1.0)
|
|
var lc = h_ink3(ny, h_top(), h_side(), h_foot())
|
|
lc = mix(lc, h_ink3(ny, h_bright(), h_acc(), h_acc() * 0.8), self.active)
|
|
lc = Finish.lift(lc, 0.26 * max(hv, dn))
|
|
lc = mix(lc, h_dim(), self.disabled)
|
|
let inside = Finish.cover(d, px)
|
|
let well = mix(mix(h_well(), vec3(0.055, 0.184, 0.216), dn), h_well() + h_acc() * 0.06, hv * (1.0 - dn))
|
|
var out = vec4(mix(well, vec3(0.012, 0.035, 0.059), self.disabled) * inside, inside)
|
|
out = mix(out, vec4(lc, 1.0), Finish.band(d, 0.0, px, px))
|
|
let md = h_hex(vec2(q.y, q.x), r * 0.58)
|
|
out = mix(out, vec4(mix(h_acc(), h_bright(), hv), 1.0), Finish.cover(md, px) * self.active * live)
|
|
out = mix(out, vec4(h_dim(), 1.0), Finish.band(md, 0.0, px, px) * self.active * self.disabled)
|
|
let lo = c - vec2(r, rr)
|
|
let hi = c + vec2(r, rr)
|
|
return h_focus(out, p, lo, hi, px, self.focus * live)
|
|
}
|
|
|
|
// TOGGLES. A 34 by 14 track cut at two corners, a well inside a device
|
|
// pixel of ground, and a square thumb block: off, the dim block at the left
|
|
// and three dim ticks in the empty half; on, the accent block at the right
|
|
// with a keep-out line round it and the ticks in the half it left.
|
|
mod.widgets.ToggleFlat.clip_x = false
|
|
mod.widgets.ToggleFlat.clip_y = false
|
|
mod.widgets.ToggleFlat.label_walk.margin.left = 37.0
|
|
mod.widgets.ToggleFlat.draw_bg.size = uniform(14.0)
|
|
mod.widgets.ToggleFlat.draw_bg.pill_aspect = uniform(2.4286)
|
|
// A knob icon is a 7 point mark in the middle of the thumb. The widget
|
|
// places it for the stock knob (its centre half the size plus 0.34 in, the
|
|
// travel less 0.34 and a 1.5 inset, 0.6 of that knob wide), and a sheet's
|
|
// writes to those values do not reach where it reads them; so the icon's
|
|
// own point hook scales the mark about its centre and moves it onto this
|
|
// thumb: 2.16 points right while off, 1 point left while on.
|
|
let h_knob_icon = fn(pos: vec2, rs: vec2, dx: float) -> vec2 {
|
|
let c = rs * 0.5
|
|
let k = 7.0 / max(min(rs.x, rs.y), 1.0)
|
|
return c + (pos - c) * k + vec2(dx, 0.0)
|
|
}
|
|
mod.widgets.ToggleFlat.draw_icon_off.transform_svg_point = fn(pos: vec2) -> vec2 {
|
|
return h_knob_icon(pos, self.rect_size, 2.16)
|
|
}
|
|
mod.widgets.ToggleFlat.draw_icon_on.transform_svg_point = fn(pos: vec2) -> vec2 {
|
|
return h_knob_icon(pos, self.rect_size, -1.0)
|
|
}
|
|
mod.widgets.ToggleFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(5.0, 5.0), self.rect_size + vec2(10.0, 10.0))
|
|
}
|
|
mod.widgets.ToggleFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(10.0, 10.0)) - vec2(5.0, 5.0)
|
|
let o = self.rect_pos
|
|
let sz = vec2(self.size * self.pill_aspect, self.size)
|
|
let x0 = (self.rect_size.x - sz.x) * self.mark_at_end
|
|
let lo = vec2(Finish.snap(o.x + x0, px), Finish.snap(o.y + self.rect_size.y * 0.5 - sz.y * 0.5, px)) - o
|
|
let hi = lo + vec2(Finish.snap(sz.x, px), Finish.snap(sz.y, px))
|
|
let c = (lo + hi) * 0.5
|
|
let kq = vec4(3.0, 0.0, 3.0, 0.0)
|
|
let d = Finish.sd_notch(p, c, (hi - lo) * 0.5, kq)
|
|
let live = 1.0 - self.disabled
|
|
let hv = self.hover * live
|
|
let dn = self.down * live
|
|
let t = mix(self.active, self.drag_pos, self.drag)
|
|
let on = self.active * live
|
|
let ny = h_facing(p, lo, hi, kq, px)
|
|
var lc = h_ink3(ny, h_top(), h_side(), h_foot())
|
|
lc = mix(lc, h_ink3(ny, h_acc() * 0.95, h_acc() * 0.72, h_acc() * 0.6), on)
|
|
lc = Finish.lift(lc, 0.26 * max(hv, dn))
|
|
lc = mix(lc, h_dim(), self.disabled)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(mix(h_well(), vec3(0.012, 0.035, 0.059), self.disabled) * inside, inside)
|
|
out = mix(out, vec4(lc, 1.0), Finish.band(d, 0.0, px, px))
|
|
// The thumb: 15 by 10 at the 14 point size, scaled with the size, its
|
|
// centre 2.5 points past half the size from either end.
|
|
let tw = self.size * 15.0 / 14.0
|
|
let th = Finish.snap(self.size * 10.0 / 14.0, px)
|
|
let tcx = self.size * 0.5 + 2.5 + (sz.x - self.size - 5.0) * t
|
|
let tx = lo.x + tcx - tw * 0.5
|
|
let tlo = vec2(Finish.snap(o.x + tx, px) - o.x, Finish.snap(o.y + c.y - th * 0.5, px) - o.y)
|
|
let thi = tlo + vec2(Finish.snap(tw, px), th)
|
|
let tc = (tlo + thi) * 0.5
|
|
let td = Material.sd_box(p, tc, (thi - tlo) * 0.5, 0.0)
|
|
// Three ticks in the empty half.
|
|
let ex = lo.x + mix(sz.x - self.size * 0.5 - 3.5, self.size * 0.5 + 3.5, t)
|
|
let tq = p.x - ex
|
|
let tick = Finish.cover(abs(abs(tq) - 3.0 * step(1.0, abs(tq))) - 0.5 * px, px) * step(abs(tq), 3.5) * step(abs(p.y - c.y), 2.0)
|
|
out = mix(out, vec4(mix(h_dim(), h_foot(), on), 1.0), tick * inside)
|
|
var thumb = mix(h_dim(), mix(h_acc(), h_bright(), hv), t * live)
|
|
thumb = Finish.lift(thumb, 0.12 * hv * (1.0 - self.active))
|
|
thumb = mix(thumb, h_foot(), dn * (1.0 - self.active))
|
|
// The keep-out line round a lit block, then the block with a hotter top.
|
|
out = mix(out, vec4(0.012, 0.035, 0.059, 1.0), Finish.ring_out(td, 0.0, px, px) * t * live)
|
|
let topl = Finish.band(td, 0.0, px, px) * step(p.y, tlo.y + 1.5 * px)
|
|
var blk = mix(thumb, Finish.lift(thumb, 0.3), topl)
|
|
blk = mix(blk, h_dim() * 0.8, self.disabled)
|
|
out = mix(out, vec4(blk, 1.0), Finish.cover(td, px))
|
|
return h_focus(out, p, lo, hi, px, self.focus * live)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SLIDERS. The track is the family's outline, gap and fill: a 6 point bar
|
|
// of one device pixel, a device pixel of ground, then the value as a solid
|
|
// accent fill with a bright leading pixel. The handle is a bright bar with
|
|
// two bracket stubs; a tick ruler runs under the track.
|
|
let h_slider = fn(p: vec2, o: vec2, rs: vec2, px: float, cy: float, pad: float, pos: float, from: float, hover: float, focus: float, drag: float, disabled: float, ruler: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let touch = max(max(hover, drag), focus) * live
|
|
let yc = Finish.snap(o.y + cy, px) - o.y
|
|
let x0 = Finish.snap(o.x + pad, px) - o.x
|
|
let x1 = Finish.snap(o.x + rs.x - pad, px) - o.x
|
|
let hh = 6.0 * px * 1.5
|
|
let lo = vec2(x0, yc - Finish.snap(3.0, px))
|
|
let hi = vec2(x1, lo.y + Finish.snap(6.0, px))
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 0.0)
|
|
let ny = h_facing(p, lo, hi, vec4(0.0, 0.0, 0.0, 0.0), px)
|
|
var lc = h_ink3(ny, h_side(), h_foot(), h_foot() * 0.85)
|
|
lc = Finish.lift(lc, 0.12 * touch)
|
|
lc = mix(lc, h_dim(), disabled)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(h_well() * inside, inside)
|
|
out = mix(out, vec4(lc, 1.0), Finish.band(d, 0.0, px, px))
|
|
// The fill.
|
|
let xv = mix(x0, x1, pos)
|
|
let xf = mix(x0, x1, from)
|
|
let fa = min(xf, xv)
|
|
let fb = max(xf, xv)
|
|
let fd = max(d + 2.0 * px, max(fa - p.x, p.x - fb))
|
|
let fill = Finish.cover(fd, px) * step(0.5 * px, fb - fa)
|
|
var ink = mix(h_acc() * 0.92, h_acc(), touch)
|
|
ink = mix(ink, mix(h_acc(), h_bright(), 0.35), drag * live)
|
|
ink = mix(ink, h_dim(), disabled)
|
|
let lead = step(fb - px, p.x) * step(xf, xv)
|
|
out = mix(out, vec4(mix(ink, h_bright() * live + h_dim() * disabled, lead), 1.0), fill)
|
|
// The ruler: a tick every tenth, a longer one every half.
|
|
let tq = (p.x - x0) / max(x1 - x0, 1.0) * 10.0
|
|
let ti = floor(tq + 0.5)
|
|
let tx = abs(tq - ti) * (x1 - x0) / 10.0
|
|
let major = step(abs(ti - 5.0), 0.5) + step(ti, 0.5) + step(9.5, ti)
|
|
let tl = mix(3.0, 5.0, min(major, 1.0))
|
|
let ty = p.y - hi.y - 2.0
|
|
let tick = Finish.cover(tx - 0.5 * px, px) * step(0.0, ty) * step(ty, tl) * step(-0.5, ti) * step(ti, 10.5) * ruler
|
|
out = mix(out, vec4(mix(h_dim(), h_foot(), min(major, 1.0)) * mix(1.0, 0.7, disabled), 1.0), tick)
|
|
// The handle: a 3 by 14 bright bar with a keep-out pixel, and two
|
|
// bracket stubs above and below it.
|
|
let hx = Finish.snap(o.x + xv, px) - o.x
|
|
let hd = Material.sd_box(p, vec2(hx, c.y), vec2(1.5, 7.0), 0.0)
|
|
out = mix(out, vec4(0.012, 0.035, 0.059, 1.0), Finish.ring_out(hd, 0.0, px, px) * live)
|
|
let hink = mix(mix(h_bright(), vec3(0.80, 0.99, 1.0), touch), h_foot(), disabled)
|
|
out = mix(out, vec4(hink, 1.0), Finish.cover(hd, px))
|
|
let sy = abs(p.y - c.y) - 9.0
|
|
let stub = Finish.cover(abs(sy) - 0.5 * px, px) * step(abs(p.x - hx), 3.5)
|
|
out = mix(out, vec4(mix(mix(h_acc(), h_bright(), drag), h_dim(), disabled), 1.0), stub)
|
|
// The handle may glow while it is the one thing touched.
|
|
let g = h_glow(hd, (0.28 * focus + 0.4 * drag) * live, 3.0) * (1.0 - Finish.cover(hd, px))
|
|
return h_add(out, h_halo_ink(hd), g)
|
|
}
|
|
|
|
mod.widgets.SliderFlat.clip_x = false
|
|
mod.widgets.SliderFlat.clip_y = false
|
|
mod.widgets.SliderFlat.height = 36.0
|
|
mod.widgets.SliderFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
|
|
}
|
|
mod.widgets.SliderFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
|
|
let lo = min(self.origin_pos, self.slide_pos)
|
|
let from = mix(0.0, lo, self.arc_origin)
|
|
let to = mix(self.slide_pos, max(self.origin_pos, self.slide_pos), self.arc_origin)
|
|
return h_slider(p, self.rect_pos, self.rect_size, px, 24.0, 2.0, to, from, self.hover, self.focus, self.drag, self.disabled, 1.0)
|
|
}
|
|
// The minimal slider: a hairline rail, the value as two device pixels of
|
|
// accent, a small bright square for a handle.
|
|
mod.widgets.SliderMinimal.clip_x = false
|
|
mod.widgets.SliderMinimal.clip_y = false
|
|
mod.widgets.SliderMinimal.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
|
|
}
|
|
mod.widgets.SliderMinimal.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let touch = max(max(self.hover, self.drag), self.focus) * live
|
|
let y = Finish.snap(o.y + self.offset_y + (self.rect_size.y - self.offset_y) * 0.5, px) - o.y
|
|
let inx = step(0.0, p.x) * step(p.x, self.rect_size.x)
|
|
let rail = Finish.cover(abs(p.y - y - 0.5 * px) - 0.5 * px, px) * inx
|
|
var out = vec4(vec3(0.106, 0.337, 0.388) * mix(1.0, 0.5, self.disabled) * rail, rail)
|
|
let lo = min(self.origin_pos, self.slide_pos)
|
|
let hi = max(self.origin_pos, self.slide_pos)
|
|
let a = mix(0.0, lo, self.arc_origin) * self.rect_size.x
|
|
let b = mix(self.slide_pos, hi, self.arc_origin) * self.rect_size.x
|
|
let fill = Finish.cover(abs(p.y - y - 0.5 * px) - px, px) * step(a, p.x) * step(p.x, b)
|
|
out = mix(out, vec4(mix(h_acc(), h_dim(), self.disabled), 1.0), fill)
|
|
let hx = Finish.snap(o.x + self.slide_pos * self.rect_size.x, px) - o.x
|
|
let hd = Material.sd_box(p, vec2(hx, y + 0.5 * px), vec2(3.0, 3.0), 0.0)
|
|
out = mix(out, vec4(0.012, 0.035, 0.059, 1.0), Finish.ring_out(hd, 0.0, px, px) * live)
|
|
out = mix(out, vec4(mix(mix(h_bright(), vec3(0.80, 0.99, 1.0), touch), h_foot(), self.disabled), 1.0), Finish.cover(hd, px))
|
|
let g = h_glow(hd, 0.28 * max(self.drag, self.focus) * live, 3.0) * (1.0 - Finish.cover(hd, px))
|
|
return h_add(out, h_halo_ink(hd), g)
|
|
}
|
|
// The round slider is the segmented meter: a frame, a device pixel of
|
|
// ground, and 5 point cells with 2.5 point gaps lit up to the value; unlit
|
|
// cells are hairline outlines, the last lit cell burns bright.
|
|
mod.widgets.SliderRoundFlat.clip_x = false
|
|
mod.widgets.SliderRoundFlat.clip_y = false
|
|
mod.widgets.SliderRoundFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
|
|
}
|
|
mod.widgets.SliderRoundFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let touch = max(max(self.hover, self.drag), self.focus) * live
|
|
// The frame stops short of the value, which stands to its right.
|
|
let bh = min(self.rect_size.y - 4.0, 12.0)
|
|
let lo = vec2(Finish.snap(o.x + self.label_size, px), Finish.snap(o.y + self.rect_size.y * 0.5 - bh * 0.5, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x - 38.0, px) - o.x, lo.y + Finish.snap(bh, px))
|
|
let c = (lo + hi) * 0.5
|
|
let kq = vec4(3.0, 0.0, 3.0, 0.0)
|
|
let d = Finish.sd_notch(p, c, (hi - lo) * 0.5, kq)
|
|
let ny = h_facing(p, lo, hi, kq, px)
|
|
var lc = h_ink3(ny, h_side(), h_foot(), h_foot() * 0.85)
|
|
lc = Finish.lift(lc, 0.12 * touch)
|
|
lc = mix(lc, h_dim(), self.disabled)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(h_well() * inside, inside)
|
|
out = mix(out, vec4(lc, 1.0), Finish.band(d, 0.0, px, px))
|
|
// Cells.
|
|
let ci0 = lo.x + 3.0 * px
|
|
let span = hi.x - 3.0 * px - ci0
|
|
let pitch = 7.5
|
|
let n = max(floor((span + 2.5) / pitch), 1.0)
|
|
let used = n * pitch - 2.5
|
|
let start = ci0 + (span - used) * 0.5
|
|
let ci = floor((p.x - start) / pitch)
|
|
let cx0 = start + ci * pitch
|
|
let incell = step(0.0, ci) * step(ci, n - 1.0)
|
|
let cd = Material.sd_box(p, vec2(Finish.snap(o.x + cx0, px) - o.x + 2.5, c.y), vec2(2.5, (hi.y - lo.y) * 0.5 - 3.0 * px), 0.0)
|
|
let lit = step((ci + 0.5) / n, self.slide_pos + 0.0001)
|
|
let head = lit * (1.0 - step((ci + 1.5) / n, self.slide_pos + 0.0001))
|
|
var ink = mix(h_acc(), h_bright(), head)
|
|
ink = mix(ink, h_foot(), self.disabled)
|
|
out = mix(out, vec4(ink, 1.0), Finish.cover(cd, px) * lit * incell)
|
|
out = mix(out, vec4(h_dim(), 1.0), Finish.band(cd, 0.0, px, px) * (1.0 - lit) * incell)
|
|
let g = h_glow(cd, 0.25 * head * touch, 2.5) * (1.0 - Finish.cover(cd, px)) * incell * inside
|
|
return h_add(out, h_halo_ink(cd), g)
|
|
}
|
|
|
|
// FADERS. The slot is two rails 3 points apart closed by cross bars, the
|
|
// value fills between them at 0.6 of the accent, a tick ladder stands on
|
|
// one side, and the cap is a 14 by 6 bright block cut by a dark centre line
|
|
// inside a keep-out pixel and an accent frame.
|
|
mod.widgets.SliderFader.cap_size = 6.0
|
|
mod.widgets.SliderFader.track_inset = 6.0
|
|
mod.widgets.SliderFader.clip_x = false
|
|
mod.widgets.SliderFader.clip_y = false
|
|
mod.widgets.SliderFader.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
|
|
}
|
|
mod.widgets.SliderFader.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p0 = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
|
|
let along = mix(self.rect_size.x, self.rect_size.y, self.is_vertical)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, self.is_vertical)
|
|
// Track space: a runs from the value's zero, b across.
|
|
let p = vec2(mix(p0.x, self.rect_size.y - p0.y, self.is_vertical), mix(p0.y, p0.x, self.is_vertical))
|
|
let live = 1.0 - self.disabled
|
|
let touch = max(max(self.hover, self.drag), self.focus) * live
|
|
let travel = max(along - self.inset_px * 2.0 - self.cap_px, 1.0)
|
|
let foot = self.inset_px + self.cap_px * 0.5
|
|
let mid = Finish.snap(across * 0.5, px) + 0.5 * px
|
|
let a0 = foot - 2.0
|
|
let a1 = foot + travel + 2.0
|
|
let inrun = step(a0, p.x) * step(p.x, a1)
|
|
let rails = Finish.cover(abs(abs(p.y - mid) - 2.0) - 0.5 * px, px) * inrun
|
|
let ends = Finish.cover(abs(abs(p.x - (a0 + a1) * 0.5) - (a1 - a0) * 0.5) - 0.5 * px, px) * step(abs(p.y - mid), 2.0 + 0.5 * px)
|
|
let rl = mix(mix(h_foot(), h_side(), touch * 0.5), h_dim(), self.disabled)
|
|
var out = vec4(rl * max(rails, ends), max(rails, ends))
|
|
// Ticks on one side: every tenth 3 points, every half 5 points.
|
|
let tq = (p.x - foot) / travel * 10.0
|
|
let ti = floor(tq + 0.5)
|
|
let tx = abs(tq - ti) * travel / 10.0
|
|
let major = min(step(abs(ti - 5.0), 0.5) + step(ti, 0.5) + step(9.5, ti), 1.0)
|
|
let tl = mix(3.0, 5.0, major)
|
|
let tb = mid - 5.5 - p.y
|
|
let tick = Finish.cover(tx - 0.5 * px, px) * step(0.0, tb) * step(tb, tl) * step(-0.5, ti) * step(ti, 10.5)
|
|
out = mix(out, vec4(mix(h_dim(), h_foot(), major), 1.0), tick)
|
|
// The fill between the rails.
|
|
let fa = foot + self.fill_lo * travel
|
|
let fb = foot + self.fill_hi * travel
|
|
let fill = Finish.cover(abs(p.y - mid) - 1.5 + 0.5 * px, px) * step(fa, p.x) * step(p.x, fb)
|
|
out = mix(out, vec4(mix(h_acc() * 0.6, h_dim(), self.disabled), 1.0), fill)
|
|
// The cap.
|
|
let at = foot + self.slide_pos * travel
|
|
let cap = Material.sd_box(p, vec2(at, mid), vec2(self.cap_px * 0.5, 7.0), 0.0)
|
|
let frame = Finish.band(cap + 2.0 * px, 0.0, px, px)
|
|
let keep = Finish.band(cap + px, 0.0, px, px)
|
|
let body = Finish.cover(cap + 2.0 * px, px)
|
|
out = mix(out, vec4(mix(mix(h_acc(), h_bright(), touch), h_foot(), self.disabled), 1.0), frame)
|
|
out = mix(out, vec4(0.012, 0.035, 0.059, 1.0), keep)
|
|
let centre = Finish.cover(abs(p.x - at) - 0.5 * px, px)
|
|
var cc = mix(mix(h_bright(), vec3(0.80, 0.99, 1.0), touch), h_dim(), self.disabled)
|
|
cc = mix(cc, vec3(0.020, 0.075, 0.102), centre)
|
|
out = mix(out, vec4(cc, 1.0), body)
|
|
let g = h_glow(cap, 0.25 * max(self.drag, self.focus) * live, 3.0) * (1.0 - Finish.cover(cap, px))
|
|
return h_add(out, h_halo_ink(cap), g)
|
|
}
|
|
|
|
// ROTARIES. A dial ring in the display's two inks of the one hue: a bright
|
|
// cyan for what is lit and for the inner instrument, a teal for the rest.
|
|
// A thick outer ring cut into three segments with chamfered ends, lit from
|
|
// the start of the scale (or from the origin) to the value and teal past
|
|
// it; the pointer a cyan bar across the ring standing out of it as a tab;
|
|
// a thin cyan inner ring with a dot under each break; low solid tabs out of
|
|
// the ring in its own ink; a run of fine ticks over the top segment; a row
|
|
// of small blocks in the dead zone. A large dial adds partial arcs, a
|
|
// bracket outside the ring in the ring's ink and a reticle or a bar stack
|
|
// at the centre. The gap at the bottom is the scale's two ends, each
|
|
// marked by a short stop. The pointer cuts its own slot through whatever it
|
|
// crosses. Focus is four corner brackets off the dial's diagonals.
|
|
// Angles run clockwise from straight down, the way the scale reads: a
|
|
// quarter turn is the left, a half the top.
|
|
let h_dial_ang = fn(q: vec2) -> float {
|
|
var a = atan2(0.0 - q.x, q.y)
|
|
if a < 0.0 {
|
|
a = a + 6.2831853
|
|
}
|
|
return a
|
|
}
|
|
// An angle brought into -pi..pi.
|
|
let h_dial_wrap = fn(x: float) -> float {
|
|
return fract(x / 6.2831853 + 0.5) * 6.2831853 - 3.1415927
|
|
}
|
|
let h_dir = fn(a: float) -> vec2 {
|
|
return vec2(0.0 - sin(a), cos(a))
|
|
}
|
|
// A box laid along the radius at angle `a`, from `r0` out to `r1`, `hw`
|
|
// either side of it: a tick, a tab, a pointer.
|
|
let h_spoke = fn(q: vec2, a: float, r0: float, r1: float, hw: float) -> float {
|
|
let d = h_dir(a)
|
|
let t = dot(q, d)
|
|
let n = abs(q.x * d.y - q.y * d.x)
|
|
let along = abs(t - (r0 + r1) * 0.5) - (r1 - r0) * 0.5
|
|
let across = n - hw
|
|
return length(vec2(max(along, 0.0), max(across, 0.0))) + min(max(along, across), 0.0)
|
|
}
|
|
// A band along the circle `r`, `hw` either side, from angle `b0` to `b1`,
|
|
// moved out by `off`; `k` cuts its corners at 45 degrees (start inside, end
|
|
// inside, end outside, start outside). `rl` and `a` are the pixel's radius
|
|
// and angle.
|
|
let h_dial_seg = fn(rl: float, a: float, b0: float, b1: float, r: float, hw: float, off: float, k: vec4) -> float {
|
|
let u = (a - (b0 + b1) * 0.5) * rl
|
|
let v = rl - r - off
|
|
return Finish.sd_notch(vec2(u, v), vec2(0.0, 0.0), vec2((b1 - b0) * 0.5 * rl, hw), k)
|
|
}
|
|
// A thin arc with square ends, through angle 0 as well.
|
|
let h_thin_arc = fn(rl: float, a: float, r: float, hw: float, b0: float, b1: float) -> float {
|
|
let da = h_dial_wrap(a - (b0 + b1) * 0.5)
|
|
let along = (abs(da) - (b1 - b0) * 0.5) * rl
|
|
let across = abs(rl - r) - hw
|
|
return length(vec2(max(along, 0.0), max(across, 0.0))) + min(max(along, across), 0.0)
|
|
}
|
|
// A bracket outside the ring: an arc at `r`, `hw` either side, from `b0`
|
|
// to `b1`, its two ends turned in to `r_in`.
|
|
let h_bracket = fn(q: vec2, rl: float, a: float, r: float, r_in: float, b0: float, b1: float, hw: float) -> float {
|
|
let arc = h_thin_arc(rl, a, r, hw, b0, b1)
|
|
let e0 = h_spoke(q, b0 + hw / r, r_in, r + hw, hw)
|
|
let e1 = h_spoke(q, b1 - hw / r, r_in, r + hw, hw)
|
|
return min(arc, min(e0, e1))
|
|
}
|
|
// 1 while a thin mark at angle `e` stands at least `d` points clear of the
|
|
// pointer along the circle `r`, else 0. Only the end stops and the origin
|
|
// tick use it, the marks the pointer would double; they go outright, never
|
|
// left half drawn.
|
|
let h_dial_clear = fn(e: float, av: float, r: float, pw: float, d: float) -> float {
|
|
return step(d, abs(h_dial_wrap(av - e)) * r - pw * 0.5)
|
|
}
|
|
// The stretch of the ring between a segment end at `e` and the pointer,
|
|
// when the pointer stands less than `m` past it: what would be left there
|
|
// is a sliver, so the ring is cut back to the pointer instead.
|
|
let h_dial_cut = fn(a: float, av: float, e: float, m: float) -> float {
|
|
let de = h_dial_wrap(av - e)
|
|
let dp = h_dial_wrap(a - e) * sign(de)
|
|
return step(abs(de), m) * step(0.00001, abs(de)) * step(0.0, dp) * step(dp, abs(de))
|
|
}
|
|
// The dial's two inks, the cyan and the teal, and the step hover lifts the
|
|
// teal to.
|
|
let h_dial_cyan = fn() -> vec3 { return vec3(0.0, 0.898, 1.0) }
|
|
let h_dial_teal = fn() -> vec3 { return vec3(0.039, 0.498, 0.561) }
|
|
let h_dial_teal_hi = fn() -> vec3 { return vec3(0.059, 0.576, 0.647) }
|
|
let h_knob = fn(p: vec2, c: vec2, rad: float, px: float, gap: float, pos: float, org: float, arc_org: float, hover: float, focus: float, drag: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
let hov = hover * live
|
|
let drg = drag * live
|
|
let foc = focus * live
|
|
// A dial 26 points or more in radius is drawn in full; one of 13 or
|
|
// more keeps a run of ticks, a tab and a row of dots besides the ring,
|
|
// the pointer, the inner ring and the stops; a smaller one only those.
|
|
let full = step(26.0, rad)
|
|
let part = step(13.0, rad)
|
|
let span = 6.2831853 - gap
|
|
let a0 = gap * 0.5
|
|
let a1 = a0 + span
|
|
let av = a0 + span * pos
|
|
let ao = a0 + span * org
|
|
let s0 = mix(a0, min(ao, av), arc_org)
|
|
let s1 = mix(av, max(ao, av), arc_org)
|
|
let q = p - c
|
|
let rl = length(q)
|
|
let a = h_dial_ang(q)
|
|
|
|
var teal = mix(h_dial_teal(), h_dial_teal_hi(), hov)
|
|
teal = mix(teal, h_dim(), disabled)
|
|
let cyan = mix(h_dial_cyan(), h_foot(), disabled)
|
|
|
|
// The radii, each a share of the dial's under a floor. The pointer
|
|
// stands out of the ring further than the tabs do.
|
|
let w = Finish.snap(clamp(0.13 * rad, 2.0, 6.5), px)
|
|
let rm = rad - w * 0.5
|
|
let tab = Finish.snap(clamp(0.08 * rad, 2.5, 4.5), px)
|
|
let tht = Finish.snap(clamp(0.045 * rad, 1.5, 2.5), px)
|
|
let rr = rad - w - clamp(0.06 * rad, 1.5, 3.0)
|
|
let pw = Finish.snap(clamp(0.035 * rad, 1.0, 2.0), px)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
|
|
// The thick ring: three segments, the breaks slanted by the chamfers.
|
|
// On a large dial the middle one steps out over the top and the last
|
|
// one is thinner, its inside edge stepped out.
|
|
let bk = max(1.5, 0.05 * rad) / rm
|
|
let b1 = a0 + span * 0.31
|
|
let b2 = a0 + span * 0.69
|
|
let ct = min(w * 0.6, 3.0)
|
|
let st = Finish.snap(clamp(0.035 * rad, 1.0, 2.0), px) * full
|
|
var ds = h_dial_seg(rl, a, a0, b1 - bk * 0.5, rm, w * 0.5, 0.0, vec4(0.0, 0.0, ct, 0.0))
|
|
ds = min(ds, h_dial_seg(rl, a, b1 + bk * 0.5, b2 - bk * 0.5, rm, w * 0.5, 0.0, vec4(ct, 0.0, ct, 0.0)))
|
|
let pl = h_dial_seg(rl, a, a0 + span * 0.43, a0 + span * 0.57, rm, (w + st) * 0.5, st * 0.5, vec4(0.0, 0.0, st, st))
|
|
ds = min(ds, mix(1000.0, pl, full))
|
|
ds = min(ds, h_dial_seg(rl, a, b2 + bk * 0.5, a1, rm, (w - st) * 0.5, st * 0.5, vec4(ct, 0.0, 0.0, 0.0)))
|
|
// Where the pointer stands just past a segment end the ring is cut
|
|
// back to it, so no piece shorter than the ring is wide sits beside
|
|
// it; at a break the piece is measured along the ring's middle line,
|
|
// where the chamfer takes half its depth.
|
|
let mc = (ct * 0.5 + pw * 0.5 + px + w) / rm
|
|
let mend = (pw * 0.5 + px + w) / rm
|
|
var cut = h_dial_cut(a, av, a0, mend)
|
|
cut = max(cut, h_dial_cut(a, av, b1 - bk * 0.5, mc))
|
|
cut = max(cut, h_dial_cut(a, av, b1 + bk * 0.5, mc))
|
|
cut = max(cut, h_dial_cut(a, av, b2 - bk * 0.5, mc))
|
|
cut = max(cut, h_dial_cut(a, av, b2 + bk * 0.5, mc))
|
|
cut = max(cut, h_dial_cut(a, av, a1, mend))
|
|
let dsc = ds + cut * 1000.0
|
|
// Low solid tabs out of the ring's outside edge, in the ring's own ink:
|
|
// a pair at a fifth of the scale on a large dial, one at three quarters
|
|
// on a small one. They stay put; the pointer cuts through them.
|
|
let thw = clamp(0.05 * rad, 2.0, 2.5)
|
|
let et = mix(a0 + span * 0.75, a0 + span * 0.2, full)
|
|
let et2 = et + (2.0 * thw + max(3.0, 1.4 * thw)) / rad
|
|
let tb = min(h_spoke(q, et, rm, rad + tht, thw), mix(1000.0, h_spoke(q, et2, rm, rad + tht, thw), full))
|
|
let ring = max(Finish.cover(dsc, px), Finish.cover(tb, px) * part)
|
|
// Lit is told by the pixel's angle, so whatever stands on the ring
|
|
// takes the ink of the stretch it stands on.
|
|
let dl = max(s0 - a, a - s1) * rl
|
|
let lon = Finish.cover(dl, px)
|
|
let rink = mix(teal, cyan, lon)
|
|
out = mix(out, vec4(rink, 1.0), ring)
|
|
|
|
// Outside the ring on a large dial, a solid bracket over the last
|
|
// fifth of the scale, its ends turned in onto the ring, in the ring's
|
|
// ink.
|
|
let bw = Finish.snap(clamp(0.05 * rad, 1.5, 3.0), px)
|
|
let ob0 = a0 + span * 0.8
|
|
let ob1 = a0 + span * 0.96
|
|
let od = h_bracket(q, rl, a, rad + 1.5 + bw * 0.5, rad - px, ob0, ob1, bw * 0.5)
|
|
out = mix(out, vec4(rink, 1.0), Finish.cover(od, px) * full)
|
|
|
|
// The stops: a short line a little past each end of the scale, out
|
|
// through the ring's edge, gone while the pointer stands on that end.
|
|
let sg = 2.0 / rm
|
|
let sclr = 3.0 + px
|
|
let sp0 = Finish.cover(h_spoke(q, a0 - sg, rm, rad + tht + px, 0.5 * px), px) * h_dial_clear(a0 - sg, av, rm, pw, sclr)
|
|
let sp1 = Finish.cover(h_spoke(q, a1 + sg, rm, rad + tht + px, 0.5 * px), px) * h_dial_clear(a1 + sg, av, rm, pw, sclr)
|
|
out = mix(out, vec4(teal, 1.0), max(sp0, sp1))
|
|
// A dial that fills from its origin marks it with a tick outside.
|
|
let om = Finish.cover(h_spoke(q, ao, rad + px, rad + tab + px, 0.5 * px), px) * h_dial_clear(ao, av, rad, pw, sclr)
|
|
out = mix(out, vec4(teal, 1.0), om * arc_org)
|
|
|
|
// The inner ring, one device pixel of cyan, and a dot on it under each
|
|
// break.
|
|
out = mix(out, vec4(cyan, 1.0), Finish.cover(abs(rl - rr) - 0.5 * px, px))
|
|
let dts = min(h_spoke(q, b1, rr - px, rr + px, px), h_spoke(q, b2, rr - px, rr + px, px))
|
|
out = mix(out, vec4(cyan, 1.0), Finish.cover(dts, px))
|
|
|
|
// The ruler inside the inner ring, over the middle segment only, every
|
|
// fifth tick longer, counted from the top. Sixty to the turn where they
|
|
// stay 4.9 device pixels apart, else forty, thirty or twenty; the
|
|
// steps fall between the dial sizes the controls use.
|
|
let rt = rr - clamp(0.035 * rad, 1.0, 1.5)
|
|
let tl = clamp(0.06 * rad, 1.6, 3.0)
|
|
let cir = 6.2831853 * rt
|
|
var nt = 20.0
|
|
nt = mix(nt, 30.0, step(147.0 * px, cir))
|
|
nt = mix(nt, 40.0, step(196.0 * px, cir))
|
|
nt = mix(nt, 60.0, step(294.0 * px, cir))
|
|
let dt = 6.2831853 / nt
|
|
let tq = (a - 3.1415927) / dt
|
|
let ti = floor(tq + 0.5)
|
|
let major = step(fract(ti / 5.0 + 0.01), 0.05)
|
|
let tx = abs(tq - ti) * dt * rl
|
|
let tr0 = rt - tl * mix(1.0, 2.2, major)
|
|
let ta = ti * dt + 3.1415927
|
|
let run = step(b1 - 0.001, ta) * step(ta, b2 + 0.001)
|
|
let tk = Finish.cover(tx - 0.5 * px, px) * step(tr0, rl) * step(rl, rt) * run * part
|
|
out = mix(out, vec4(teal, 1.0), tk)
|
|
|
|
// Partial arcs inside the ruler, at two radii.
|
|
let ri = rt - tl * 2.2 - 2.5
|
|
var ia = h_thin_arc(rl, a, ri, 0.5 * px, 1.75, 2.95)
|
|
ia = min(ia, h_thin_arc(rl, a, ri, px, 3.35, 3.75))
|
|
ia = min(ia, h_thin_arc(rl, a, ri - 3.0, 0.5 * px, 4.0, 5.2))
|
|
out = mix(out, vec4(teal, 1.0), Finish.cover(ia, px) * full)
|
|
|
|
// A row of small blocks in the dead zone, centred under the dial,
|
|
// where the gap leaves room for them between the stops: five on a
|
|
// large dial, three dots on a small one.
|
|
let gl = gap * rm
|
|
let bp = mix(3.0, 4.0, full)
|
|
let nb = mix(1.0, 2.0, full)
|
|
let room = step((nb + 0.5) * bp + 3.0, gl * 0.5 - 2.0) * part
|
|
let bu = h_dial_wrap(a) * rm / bp
|
|
let bi = floor(bu + 0.5)
|
|
let bd = max(abs(bu - bi) * bp - mix(px, 1.0, full), abs(rl - rm) - mix(px, w * 0.25, full))
|
|
let brow = Finish.cover(bd, px) * step(abs(bi), nb) * room
|
|
out = mix(out, vec4(teal, 1.0), brow)
|
|
|
|
// The centre: a cyan reticle between a pair of arcs like parentheses on
|
|
// a dial under 50 points in radius, a stack of five bars on a larger
|
|
// one, lit over the same stretch as the ring (from the foot, or from
|
|
// the origin, to the value) and teal elsewhere.
|
|
let big = step(50.0, rad) * full
|
|
let rp = 0.38 * rad
|
|
let pa = min(h_thin_arc(rl, a, rp, 0.5 * px, 1.05, 2.09), h_thin_arc(rl, a, rp, 0.5 * px, 4.19, 5.24))
|
|
out = mix(out, vec4(teal, 1.0), Finish.cover(pa, px) * full * (1.0 - big))
|
|
let rc = clamp(0.12 * rad, 2.0, 6.0)
|
|
let rh = rc * 2.2
|
|
var rt_d = abs(rl - rc) - 0.5 * px
|
|
rt_d = min(rt_d, max(abs(q.y) - 0.5 * px, max(abs(q.x) - rh, rc + 1.5 - abs(q.x))))
|
|
let small_mark = step(14.0, rad) * (1.0 - full)
|
|
let ret = Finish.cover(rt_d, px) * (1.0 - big) * max(full, small_mark)
|
|
out = mix(out, vec4(cyan, 1.0), ret)
|
|
let cdot = max(abs(q.x), abs(q.y)) - px
|
|
out = mix(out, vec4(cyan, 1.0), Finish.cover(cdot, px) * (1.0 - big))
|
|
let bh = Finish.snap(max(0.033 * rad, 1.0), px)
|
|
let pitch = Finish.snap(bh * 2.0, px)
|
|
let bwid = 0.42 * rad
|
|
let yy = q.y + pitch * 2.0
|
|
let bj = floor(yy / pitch + 0.5)
|
|
let bjc = clamp(bj, 0.0, 4.0)
|
|
let bar = max(abs(yy - bjc * pitch) - bh * 0.5, abs(q.x) - bwid * (0.2 + 0.2 * bjc) * 0.5)
|
|
let ff = (4.5 - bjc) / 5.0
|
|
let f0 = (s0 - a0) / max(span, 0.001)
|
|
let f1 = (s1 - a0) / max(span, 0.001)
|
|
// No bar is lit while nothing is: a dial resting on its origin has an
|
|
// empty stretch, whatever the rounding of its two ends.
|
|
let bon = step(f0, ff) * step(ff, f1) * step(0.002, f1 - f0)
|
|
out = mix(out, vec4(mix(teal, cyan, bon), 1.0), Finish.cover(bar, px) * big)
|
|
|
|
// The pointer: a cyan bar across the ring, out past its edge as a
|
|
// wider tab. First its slot, the pointer grown by a device pixel to the
|
|
// sides and past its tip, is cleared of everything under it: ring,
|
|
// tabs, bracket alike.
|
|
var slot = h_spoke(q, av, rad - w - 1.0, rad + tab + px, pw * 0.5 + px)
|
|
slot = min(slot, h_spoke(q, av, rad, rad + tab + px, pw * 0.5 + 2.0 * px))
|
|
out = mix(out, vec4(0.0, 0.0, 0.0, 0.0), Finish.cover(slot, px))
|
|
let pd = min(h_spoke(q, av, rad - w - 1.0, rad + tab, pw * 0.5), h_spoke(q, av, rad + px, rad + tab, pw * 0.5 + px))
|
|
let pc = Finish.cover(pd, px)
|
|
// A faint glow round the lit stretch and the pointer under the hand,
|
|
// kept out of the stretch the cut took away.
|
|
let gsrc = min(pd, max(dsc, dl))
|
|
let lg = h_glow(gsrc, 0.12 * drg + 0.06 * hov, 3.0) * (1.0 - max(pc, ring * lon))
|
|
out = h_add(out, cyan, lg)
|
|
out = mix(out, vec4(cyan, 1.0), pc)
|
|
|
|
// Focus: corner brackets, one device pixel of cyan, on the square whose
|
|
// arms end 2 points clear of the pointer's tab, so they sit off the
|
|
// dial's diagonals inside its own box and clear of any readout.
|
|
let arm = clamp(0.2 * rad, 3.0, 7.0)
|
|
let rcl = rad + tab + 2.0
|
|
let rf = Finish.snap((arm + sqrt(2.0 * rcl * rcl - arm * arm)) * 0.5, px)
|
|
let fc = h_brackets(p, c - vec2(rf, rf), c + vec2(rf, rf), arm, px) * foc
|
|
out = mix(out, vec4(cyan, 1.0), fc)
|
|
return out
|
|
}
|
|
// The quad is grown by 6 points for the pointer's tab and the bracket. A
|
|
// box with no room for a dial of 4 points in radius draws nothing.
|
|
mod.widgets.RotaryFlat.clip_x = false
|
|
mod.widgets.RotaryFlat.clip_y = false
|
|
mod.widgets.RotaryFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.RotaryFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
let dia = min(self.rect_size.x, self.rect_size.y - 22.0)
|
|
let r = dia * 0.5 - 4.0
|
|
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px), Finish.snap(self.rect_pos.y + 22.0 + dia * 0.5, px)) - self.rect_pos
|
|
return h_knob(p, c, max(r, 4.0), px, self.gap * 0.017453, self.slide_pos, self.origin_pos, self.arc_origin, self.hover, self.focus, self.drag, self.disabled) * step(4.0, r)
|
|
}
|
|
mod.widgets.RotaryKnob.clip_x = false
|
|
mod.widgets.RotaryKnob.clip_y = false
|
|
mod.widgets.RotaryKnob.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.RotaryKnob.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
let dia = min(self.rect_size.x, self.rect_size.y)
|
|
let r = dia * 0.5 - 2.5
|
|
let c = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px), Finish.snap(self.rect_pos.y + self.rect_size.y - dia * 0.5, px)) - self.rect_pos
|
|
return h_knob(p, c, max(r, 4.0), px, self.gap * 0.017453, self.slide_pos, self.origin_pos, self.arc_origin, self.hover, self.focus, self.drag, self.disabled) * step(4.0, r)
|
|
}
|
|
mod.widgets.RotaryKnob.draw_text.color = #7fb3bc
|
|
mod.widgets.RotaryKnob.draw_text.color_hover = #b8eef5
|
|
mod.widgets.RotaryKnob.draw_text.color_focus = #b8eef5
|
|
mod.widgets.RotaryKnob.draw_text.color_drag = #e6fdff
|
|
mod.widgets.RotaryKnob.draw_text.color_disabled = #33545b
|
|
mod.widgets.RotaryKnob.draw_text.text_style = mod.theme.font_regular{font_size: 6.5}
|
|
|
|
// READOUTS. The value beside a dial or a slider is a data readout: a well
|
|
// darker than the ground, its frame lit on the top edge, one cut corner.
|
|
let h_readout = fn(p: vec2, o: vec2, s: vec2, px: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + s.x, px), Finish.snap(o.y + s.y, px)) - o
|
|
let kq = vec4(3.0, 0.0, 0.0, 0.0)
|
|
let d = Finish.sd_notch(p, (lo + hi) * 0.5, (hi - lo) * 0.5, kq)
|
|
let ny = h_facing(p, lo, hi, kq, px)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(vec3(0.008, 0.027, 0.043) * inside, inside)
|
|
out = mix(out, vec4(0.03, 0.06, 0.075, 1.0), Finish.band(d, px, 2.0 * px, px) * step(p.y, lo.y + 2.5 * px))
|
|
out = mix(out, vec4(h_ink3(ny, h_side(), h_foot(), h_foot()), 1.0), Finish.band(d, 0.0, px, px))
|
|
return out
|
|
}
|
|
mod.widgets.RotaryFlat.text_input.min_height = 0.0
|
|
mod.widgets.RotaryFlat.text_input.padding = mod.turtle.Inset{left: 5.0 right: 5.0 top: 2.0 bottom: 2.0}
|
|
mod.widgets.RotaryFlat.text_input.draw_text.color = #b8eef5
|
|
mod.widgets.RotaryFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.0}
|
|
// The value is selected while the dial is dragged, so typing replaces
|
|
// it; the selection is never painted, in any state, so the number
|
|
// stays readable under the hand.
|
|
mod.widgets.RotaryFlat.text_input.draw_selection.color = #0000
|
|
mod.widgets.RotaryFlat.text_input.draw_selection.color_hover = #0000
|
|
mod.widgets.RotaryFlat.text_input.draw_selection.color_focus = #0000
|
|
mod.widgets.RotaryFlat.text_input.draw_selection.color_down = #0000
|
|
mod.widgets.RotaryFlat.text_input.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return h_readout(self.pos * self.rect_size, self.rect_pos, self.rect_size, px)
|
|
}
|
|
// The value inside a slider is bare data in the accent: the field row's
|
|
// height must not reach it, or its text drops below the track's line.
|
|
mod.widgets.SliderMinimal.text_input.min_height = 0.0
|
|
mod.widgets.SliderFlat.text_input.min_height = 0.0
|
|
mod.widgets.SliderRoundFlat.text_input.min_height = 0.0
|
|
mod.widgets.SliderFlat.text_input.draw_text.color = #36c8dc
|
|
mod.widgets.SliderMinimal.text_input.draw_text.color = #36c8dc
|
|
mod.widgets.SliderRoundFlat.text_input.draw_text.color = #36c8dc
|
|
mod.widgets.SliderFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.0}
|
|
mod.widgets.SliderMinimal.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.0}
|
|
mod.widgets.SliderRoundFlat.text_input.draw_text.text_style = mod.theme.font_regular{font_size: 7.0}
|
|
mod.widgets.ComboBoxFlat.input.min_height = 0.0
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TEXT FIELDS. A well below the ground in a square frame of the dim line,
|
|
// its top edge a step brighter, and four corner brackets 6 points long that
|
|
// carry the state: the side line at rest, the top line under the pointer,
|
|
// the accent on focus with the short bloom of the one focused thing.
|
|
let h_field = fn(p: vec2, o: vec2, s: vec2, px: float, hover: float, down: float, focus: float, disabled: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + s.x, px), Finish.snap(o.y + s.y, px)) - o
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 0.0)
|
|
let live = 1.0 - disabled
|
|
let hv = hover * live
|
|
let fc = focus * live
|
|
let inside = Finish.cover(d, px)
|
|
let dn = down * live
|
|
// Held, the well lifts a step toward the accent's deep ink.
|
|
let wc = mix(h_well() + vec3(0.004, 0.008, 0.01) * hv * (1.0 - dn) + vec3(0.014, 0.042, 0.050) * dn, vec3(0.012, 0.035, 0.059), disabled)
|
|
var out = vec4(wc * 0.95 * inside, 0.95 * inside)
|
|
let ny = h_facing(p, lo, hi, vec4(0.0, 0.0, 0.0, 0.0), px)
|
|
var lc = h_ink3(ny, h_dim() * 1.3, h_dim(), h_dim() * 0.9)
|
|
lc = Finish.lift(lc, 0.08 * hv)
|
|
lc = mix(lc, h_ink3(ny, h_acc() * 0.5, h_acc() * 0.3, h_acc() * 0.3), dn)
|
|
lc = mix(lc, h_ink3(ny, h_acc() * 0.62, h_acc() * 0.36, h_acc() * 0.36), fc)
|
|
lc = mix(lc, h_dim() * 0.8, disabled)
|
|
let edge = Finish.band(d, 0.0, px, px)
|
|
out = mix(out, vec4(lc, 1.0), edge)
|
|
let br = h_brackets(p, lo, hi, 6.0, px)
|
|
var bc = mix(Finish.lift(h_side(), 0.08), Finish.lift(h_top(), 0.18), hv)
|
|
// Held, the brackets take the accent before the focus lands.
|
|
bc = mix(bc, h_acc(), dn)
|
|
bc = mix(bc, h_acc(), fc)
|
|
bc = mix(bc, h_dim(), disabled)
|
|
out = mix(out, vec4(bc, 1.0), br)
|
|
let ex = min(p.x - lo.x, hi.x - p.x)
|
|
let ey = min(p.y - lo.y, hi.y - p.y)
|
|
let near = (1.0 - smoothstep(5.0, 8.0, ex)) * (1.0 - smoothstep(5.0, 8.0, ey))
|
|
let g = h_glow(d, 0.30 * fc, 3.0) * near * (1.0 - inside)
|
|
return h_add(out, h_halo_ink(d), g)
|
|
}
|
|
mod.widgets.TextInputFlat.clip_x = false
|
|
mod.widgets.TextInputFlat.clip_y = false
|
|
mod.widgets.TextInputFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
|
|
}
|
|
mod.widgets.TextInputFlat.draw_bg.pixel = fn() {
|
|
if self.border_size < 0.01 {
|
|
return vec4(0.0, 0.0, 0.0, 0.0)
|
|
}
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
|
|
return h_field(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.disabled)
|
|
}
|
|
mod.widgets.TextInputFlat.padding.left = 8.0
|
|
mod.widgets.TextInputFlat.padding.right = 8.0
|
|
mod.widgets.NumberField.clip_x = false
|
|
mod.widgets.NumberField.clip_y = false
|
|
mod.widgets.NumberField.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
|
|
}
|
|
mod.widgets.NumberField.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
|
|
return h_field(p, self.rect_pos, self.rect_size, px, self.hover, 0.0, self.focus, self.disabled)
|
|
}
|
|
// The stepper: its own cell behind a dim divider, split across the middle;
|
|
// the half under the pointer takes the accent at a tenth, the held half at
|
|
// a fifth, and the arrows are the accent.
|
|
mod.widgets.NumberSpin.draw_text.color = #36c8dc
|
|
mod.widgets.NumberSpin.draw_text.text_style = mod.theme.font_regular{font_size: 5.5 line_spacing: 1.0}
|
|
mod.widgets.NumberSpin.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let mid = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
|
|
let upper = step(p.y, mid)
|
|
let live = 1.0 - self.disabled
|
|
let hot = mix(max(0.0, 0.0 - self.hot), max(0.0, self.hot), upper) * live
|
|
let down = mix(max(0.0, 0.0 - self.down), max(0.0, self.down), upper) * live
|
|
let wash = 0.10 * hot + 0.22 * down
|
|
var out = vec4(h_acc() * wash, wash)
|
|
let x0 = Finish.snap(self.rect_pos.x, px) - self.rect_pos.x
|
|
let groove = Finish.cover(abs(p.y - mid - 0.5 * px) - 0.5 * px, px) * step(x0 + 3.0, p.x) * step(p.x, self.rect_size.x - 3.0)
|
|
let side = Finish.cover(abs(p.x - x0 - 0.5 * px) - 0.5 * px, px) * step(4.0, p.y) * step(p.y, self.rect_size.y - 4.0)
|
|
return mix(out, vec4(h_dim(), 1.0), max(groove, side))
|
|
}
|
|
|
|
// DROP-DOWNS. The button's frame and face, cut at the top left only, a dim
|
|
// divider 20 points from the right edge and an outlined down triangle in
|
|
// the accent in the cell it makes; open, the frame takes the accent and the
|
|
// triangle fills.
|
|
let h_dropdown = fn(p: vec2, o: vec2, s: vec2, px: float, hover: float, down: float, focus: float, open: float, disabled: float) -> vec4 {
|
|
let live = 1.0 - disabled
|
|
var f = h_button(p, o, s, px, vec4(1.0, 0.0, 0.0, 0.0), max(hover, open), down * hover, focus, disabled, mix(0.0, 2.0, open))
|
|
let dx = Finish.snap(o.x + s.x - 20.0, px) - o.x
|
|
let div = Finish.cover(abs(p.x - dx - 0.5 * px) - 0.5 * px, px) * step(5.0, p.y) * step(p.y, s.y - 5.0)
|
|
f = mix(f, vec4(mix(h_dim(), h_foot(), hover * live), 1.0), div)
|
|
let c = vec2(s.x - 10.0, Finish.snap(o.y + s.y * 0.5, px) - o.y + 0.5)
|
|
// An outlined triangle 7 wide and 4.5 tall, point down.
|
|
let a = c + vec2(-3.5, -2.25)
|
|
let b = c + vec2(3.5, -2.25)
|
|
let e = c + vec2(0.0, 2.25)
|
|
let t = min(min(h_seg(p, a, b), h_seg(p, b, e)), h_seg(p, e, a))
|
|
let q = p - c
|
|
let inner = step(-2.25, q.y) * step(abs(q.x) * 0.643 + q.y, 2.25 - 0.1)
|
|
let ink = mix(mix(h_acc(), h_bright(), max(hover, open) * live), h_dim(), disabled)
|
|
f = mix(f, vec4(ink, 1.0), Finish.cover(t - 0.5 * px, px))
|
|
return mix(f, vec4(ink, 1.0), inner * open * live)
|
|
}
|
|
mod.widgets.DropDownFlat.clip_x = false
|
|
mod.widgets.DropDownFlat.clip_y = false
|
|
mod.widgets.DropDownFlat.padding.left = 12.0
|
|
mod.widgets.DropDownFlat.padding.right = 28.0
|
|
mod.widgets.DropDownFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.DropDownFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
return h_dropdown(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, self.active, self.disabled)
|
|
}
|
|
mod.widgets.ComboBoxFlat.clip_x = false
|
|
mod.widgets.ComboBoxFlat.clip_y = false
|
|
mod.widgets.ComboBoxFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.ComboBoxFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
return h_dropdown(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, 0.0, self.disabled)
|
|
}
|
|
mod.widgets.ComboBoxFlat.draw_popup_bg.border_radius = 0.0
|
|
mod.widgets.ComboBoxFlat.draw_popup_bg.color = #0a1a22
|
|
mod.widgets.ComboBoxFlat.draw_popup_bg.border_color = #36707c
|
|
mod.widgets.ComboBoxFlat.draw_item.color_hover = #10262f
|
|
mod.widgets.ComboBoxFlat.draw_item.color_active = #0e2f37
|
|
|
|
// The select wears a button built before this sheet: it is given the
|
|
// drop-down's face, triangle and all, and the same grown quad.
|
|
mod.widgets.Select.clip_x = false
|
|
mod.widgets.Select.clip_y = false
|
|
mod.widgets.MultiSelect.clip_x = false
|
|
mod.widgets.MultiSelect.clip_y = false
|
|
mod.widgets.Select.face.clip_x = false
|
|
mod.widgets.Select.face.clip_y = false
|
|
mod.widgets.Select.face.padding = mod.turtle.Inset{left: 12.0 right: 28.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.Select.face.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.Select.face.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
return h_dropdown(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, 0.0, self.disabled)
|
|
}
|
|
mod.widgets.MultiSelect.face.clip_x = false
|
|
mod.widgets.MultiSelect.face.clip_y = false
|
|
mod.widgets.MultiSelect.face.padding = mod.turtle.Inset{left: 12.0 right: 28.0 top: 4.0 bottom: 4.0}
|
|
mod.widgets.MultiSelect.face.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.MultiSelect.face.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
return h_dropdown(p, self.rect_pos, self.rect_size, px, self.hover, self.down, self.focus, 0.0, self.disabled)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PROGRESS. The outline, a device pixel of ground, the well, and the fill
|
|
// in the intent's ink with a bright leading pixel and a short bloom that
|
|
// crosses the frame. The unknown amount is the family's hatch held still:
|
|
// 45 degree hairlines across the well (a face may not read the pass clock,
|
|
// so it does not sweep).
|
|
let h_hues = fn(i: float) -> vec3 {
|
|
var h = vec3(0.212, 0.784, 0.863)
|
|
if i > 0.5 { h = vec3(0.373, 0.878, 0.627) }
|
|
if i > 1.5 { h = vec3(0.945, 0.937, 0.227) }
|
|
if i > 2.5 { h = vec3(1.0, 0.376, 0.408) }
|
|
if i > 3.5 { h = vec3(0.212, 0.784, 0.863) }
|
|
if i > 4.5 { h = vec3(0.435, 0.663, 0.702) }
|
|
return h
|
|
}
|
|
mod.widgets.ProgressBarFlat.clip_x = false
|
|
mod.widgets.ProgressBarFlat.clip_y = false
|
|
mod.widgets.ProgressBarFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
|
|
}
|
|
mod.widgets.ProgressBarFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
|
|
let o = self.rect_pos
|
|
let hue = h_hues(self.intent)
|
|
let th = min(max(self.thickness, 6.0), self.rect_size.y)
|
|
let y0 = Finish.snap(o.y + (self.rect_size.y - th) * 0.5, px) - o.y
|
|
let y1 = y0 + Finish.snap(th, px)
|
|
let x0 = Finish.snap(o.x + self.rect_size.x * self.start, px) - o.x
|
|
let x1 = Finish.snap(o.x + self.rect_size.x * self.end, px) - o.x
|
|
let lo = vec2(x0, y0)
|
|
let hi = vec2(x1, y1)
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 0.0)
|
|
let ny = h_facing(p, lo, hi, vec4(0.0, 0.0, 0.0, 0.0), px)
|
|
let inside = Finish.cover(d, px) * self.track
|
|
var out = vec4(h_well() * inside, inside)
|
|
out = mix(out, vec4(h_ink3(ny, h_side(), h_foot(), h_foot() * 0.85), 1.0), Finish.band(d, 0.0, px, px) * self.track)
|
|
let fi = d + 2.0 * px
|
|
if self.value >= 0.0 {
|
|
let xv = mix(x0 + 2.0 * px, x1 - 2.0 * px, clamp((self.value - self.start) / max(self.end - self.start, 0.0001), 0.0, 1.0))
|
|
let fd = max(fi, p.x - xv)
|
|
let fa = Finish.cover(fd, px) * step(x0 + 2.5 * px, xv)
|
|
let lead = step(xv - px, p.x)
|
|
let g = h_glow(fd, 0.18, 2.6) * (1.0 - fa) * step(p.x, xv + 2.0)
|
|
out = h_add(out, h_halo_ink(fd) * (hue / vec3(0.212, 0.784, 0.863)) * 0.5 + hue * 0.1, g)
|
|
out = mix(out, vec4(mix(hue, mix(hue, vec3(1.0, 1.0, 1.0), 0.45), lead), 1.0), fa)
|
|
} else {
|
|
let u = p.x + (p.y - y0)
|
|
let pitch = 6.0 * 1.4142
|
|
let hl = Finish.cover(abs(fract(u / pitch) * pitch - pitch * 0.5) - 0.5 * px * 1.4142, px)
|
|
out = mix(out, vec4(hue * 0.45, 1.0), hl * Finish.cover(fi, px))
|
|
}
|
|
return out * self.opacity
|
|
}
|
|
|
|
// RINGS, ARCS AND GAUGES. The family's ring gauge: a slim stroke with
|
|
// square ends, round(0.07 D) device pixels with D in points (2 at 24
|
|
// points, 3 at 48, at most 6), the unlit arc #0b2c34 under it, the lit arc
|
|
// in the accent (an intent's ink only where a caller sets one) with a
|
|
// bright device pixel at its head, and no halo. A ring sweeps 270 degrees
|
|
// from the lower left; where there is room, 21 ticks in the dim line stand
|
|
// inside it, every fifth a little longer. The unknown amount is a still
|
|
// quarter arc fading toward its tail (a face may not read the pass clock).
|
|
let h_ang = fn(q: vec2) -> float {
|
|
var a = atan2(0.0 - q.x, q.y)
|
|
if a < 0.0 {
|
|
a = a + 6.2831853
|
|
}
|
|
return a
|
|
}
|
|
// Distance to the part f0..f1 of a ring (centre radius rc, stroke w), cut
|
|
// square at both ends; f is the point's fraction along the sweep.
|
|
let h_ring_seg = fn(rl: float, f: float, f0: float, f1: float, rc: float, w: float, sweep: float) -> float {
|
|
let dr = abs(rl - rc) - w * 0.5
|
|
let da = max(f0 - f, f - f1) * sweep * rl
|
|
return max(dr, da)
|
|
}
|
|
let h_ring_stroke = fn(dia: float, px: float) -> float {
|
|
return clamp(floor(0.07 * dia + 0.5), 2.0, 6.0) * px
|
|
}
|
|
let h_ring_ticks = fn(rl: float, f: float, f0: float, f1: float, r1: float, rad: float, sweep: float, px: float) -> vec2 {
|
|
let tq = f * 20.0
|
|
let ti = floor(tq + 0.5)
|
|
let tx = abs(tq - ti) * sweep / 20.0 * rl
|
|
let major = step(fract(ti / 5.0 + 0.01), 0.05)
|
|
let r0 = r1 - mix(0.06, 0.10, major) * rad
|
|
let tk = Finish.cover(tx - 0.5 * px, px) * step(r0, rl) * step(rl, r1) * step(-0.5, ti) * step(ti, 20.5) * step(f0 - 0.001, ti / 20.0) * step(ti / 20.0, f1 + 0.001)
|
|
return vec2(tk, major)
|
|
}
|
|
let h_ring = fn(q: vec2, rad: float, inset: float, px: float, sweep: float, dir: float, f0: float, f1: float, value: float, ink: vec3, track: float, ticks: float) -> vec4 {
|
|
let w = h_ring_stroke(2.0 * rad, px)
|
|
let rc = rad - inset - w * 0.5 - px
|
|
let rl = length(q)
|
|
let a0 = (6.2831853 - sweep) * 0.5
|
|
var f = (h_ang(q) - a0) / max(sweep, 0.001)
|
|
f = mix(f, 1.0 - f, step(dir, 0.0))
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let tk = h_ring_ticks(rl, f, f0, f1, rc - w * 0.5 - 2.0 * px, rad, sweep, px)
|
|
out = mix(out, vec4(mix(h_dim(), h_foot(), tk.y * 0.5), 1.0), tk.x * ticks)
|
|
let td = h_ring_seg(rl, f, f0, f1, rc, w, sweep)
|
|
out = mix(out, vec4(0.043, 0.173, 0.204, 1.0), Finish.cover(td, px) * track)
|
|
if value >= 0.0 {
|
|
let v = clamp(value, f0, f1)
|
|
let on = step(0.0005, v - f0)
|
|
let ld = h_ring_seg(rl, f, f0, v, rc, w, sweep)
|
|
let head = step((v - f) * sweep * rl, px)
|
|
let lit = mix(ink, mix(ink, vec3(1.0, 1.0, 1.0), 0.45), head)
|
|
out = mix(out, vec4(lit, 1.0), Finish.cover(ld, px) * on)
|
|
} else {
|
|
let qe = f0 + 0.25 * (f1 - f0)
|
|
let ld = h_ring_seg(rl, f, f0, qe, rc, w, sweep)
|
|
let k = clamp((f - f0) / max(qe - f0, 0.001), 0.0, 1.0)
|
|
out = mix(out, vec4(ink * mix(0.25, 1.0, k * k), 1.0), Finish.cover(ld, px))
|
|
}
|
|
return out
|
|
}
|
|
mod.widgets.ProgressRingFlat.draw_bg.sweep = 4.712389
|
|
mod.widgets.ProgressRingFlat.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 = vec2(self.rect_size.x * 0.5, self.rect_size.y * self.center_y)
|
|
let rad = min(self.rect_size.x * 0.5, self.rect_size.y * self.center_y)
|
|
let ink = mix(h_hues(self.intent), h_foot(), step(4.5, self.intent))
|
|
let ticks = step(44.0, 2.0 * rad) * step(self.inset, 0.01)
|
|
return h_ring(p - c, rad, self.inset, px, self.sweep, self.direction, self.start, self.end, self.value, ink, self.track, ticks) * self.opacity
|
|
}
|
|
// Nested rings are one reading each, in the one accent: the unlit arcs
|
|
// keep them apart, not three hues.
|
|
mod.widgets.ActivityRings.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 = vec2(self.rect_size.x * 0.5, self.rect_size.y * self.center_y)
|
|
let rad = min(self.rect_size.x * 0.5, self.rect_size.y * self.center_y)
|
|
let ink = mix(h_acc(), h_foot(), step(4.5, self.intent))
|
|
return h_ring(p - c, rad, self.inset, px, self.sweep, self.direction, self.start, self.end, self.value, ink, self.track, 0.0) * self.opacity
|
|
}
|
|
|
|
// The gauge: the same ring over 270 degrees, lit in the accent to the
|
|
// warning mark, caution yellow to the critical one, the red role above;
|
|
// the unreached warning and critical parts keep a fifth of their ink.
|
|
// The needle is a device pixel of the text ink to just inside the ticks
|
|
// with a bright 2 by 2 tip, on a 3 point hollow square pivot. Laid flat, a
|
|
// 6 point bar in the progress bar's frame with a needle across it.
|
|
let h_zone = fn(f: float, warn: float, crit: float) -> vec3 {
|
|
var z = vec3(0.212, 0.784, 0.863)
|
|
if f > warn {
|
|
z = vec3(0.945, 0.937, 0.227)
|
|
}
|
|
if f > crit {
|
|
z = vec3(1.0, 0.376, 0.408)
|
|
}
|
|
return z
|
|
}
|
|
let h_gauge_arc = fn(q: vec2, rad: float, px: float, sweep: float, v: float, warn: float, crit: float, needle: float) -> vec4 {
|
|
let w = h_ring_stroke(2.0 * rad, px)
|
|
let rc = rad - w * 0.5 - px
|
|
let rl = length(q)
|
|
let a0 = (6.2831853 - sweep) * 0.5
|
|
let f = (h_ang(q) - a0) / max(sweep, 0.001)
|
|
let z = h_zone(f, warn, crit)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
let r1 = rc - w * 0.5 - 2.0 * px
|
|
let tk = h_ring_ticks(rl, f, 0.0, 1.0, r1, rad, sweep, px)
|
|
out = mix(out, vec4(mix(h_dim(), h_foot(), tk.y * 0.5), 1.0), tk.x)
|
|
let td = h_ring_seg(rl, f, 0.0, 1.0, rc, w, sweep)
|
|
let unlit = mix(vec3(0.043, 0.173, 0.204), z * 0.2, step(warn, f))
|
|
out = mix(out, vec4(unlit, 1.0), Finish.cover(td, px))
|
|
let ld = h_ring_seg(rl, f, 0.0, v, rc, w, sweep)
|
|
let head = step((v - f) * sweep * rl, px)
|
|
out = mix(out, vec4(mix(z, mix(z, vec3(1.0, 1.0, 1.0), 0.45), head), 1.0), Finish.cover(ld, px) * step(0.001, v))
|
|
let av = a0 + v * sweep
|
|
let dir = vec2(0.0 - sin(av), cos(av))
|
|
let tip = dir * (r1 - 0.12 * rad)
|
|
let nd = h_seg(q, dir * 2.5, tip)
|
|
let pv = abs(max(abs(q.x), abs(q.y)) - 1.5 + 0.5 * px) - 0.5 * px
|
|
out = mix(out, vec4(0.722, 0.933, 0.961, 1.0), Finish.cover(min(nd - 0.5 * px, pv), px) * needle)
|
|
let sq = max(abs(q.x - tip.x), abs(q.y - tip.y)) - px
|
|
out = mix(out, vec4(h_bright(), 1.0), Finish.cover(sq, px) * needle)
|
|
return out
|
|
}
|
|
let h_gauge_bar = fn(p: vec2, o: vec2, s: vec2, px: float, v: float, warn: float, crit: float, needle: float) -> vec4 {
|
|
let bh = Finish.snap(min(6.0, s.y), px)
|
|
let y0 = Finish.snap(o.y + (s.y - bh) * 0.5, px) - o.y
|
|
let lo = vec2(Finish.snap(o.x, px) - o.x, y0)
|
|
let hi = vec2(floor((o.x + s.x) / px) * px - o.x, y0 + bh)
|
|
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 0.0)
|
|
let f = clamp((p.x - lo.x) / max(hi.x - lo.x, 1.0), 0.0, 1.0)
|
|
let z = h_zone(f, warn, crit)
|
|
let inside = Finish.cover(d, px)
|
|
let ny = h_facing(p, lo, hi, vec4(0.0, 0.0, 0.0, 0.0), px)
|
|
var out = vec4(h_well() * inside, inside)
|
|
out = mix(out, vec4(h_ink3(ny, h_side(), h_foot(), h_foot() * 0.85), 1.0), Finish.band(d, 0.0, px, px))
|
|
let fi = d + 2.0 * px
|
|
let xv = mix(lo.x + 2.0 * px, hi.x - 2.0 * px, v)
|
|
out = mix(out, vec4(z * 0.2, 1.0), Finish.cover(fi, px) * step(warn, f) * step(xv, p.x))
|
|
out = mix(out, vec4(z, 1.0), Finish.cover(max(fi, p.x - xv), px) * step(0.001, v))
|
|
let nx = Finish.snap(o.x + xv, px) - o.x
|
|
let nl = Finish.cover(abs(p.x - nx - 0.5 * px) - 0.5 * px, px) * step(lo.y - 4.0, p.y) * step(p.y, hi.y + 4.0)
|
|
out = mix(out, vec4(0.722, 0.933, 0.961, 1.0), nl * needle)
|
|
let sq = max(abs(p.x - nx - 0.5 * px), abs(p.y - lo.y + 4.0)) - px
|
|
return mix(out, vec4(h_bright(), 1.0), Finish.cover(sq, px) * needle)
|
|
}
|
|
mod.widgets.Gauge.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let v = clamp(self.value, 0.0, 1.0)
|
|
var out = vec4(0.0, 0.0, 0.0, 0.0)
|
|
if self.linear > 0.5 {
|
|
out = h_gauge_bar(p, self.rect_pos, self.rect_size, px, v, self.zone_warn, self.zone_critical, self.needle)
|
|
} else {
|
|
let c = self.rect_size * 0.5
|
|
let rad = min(self.rect_size.x, self.rect_size.y) * 0.5
|
|
out = h_gauge_arc(p - c, rad, px, self.sweep, v, self.zone_warn, self.zone_critical, self.needle)
|
|
}
|
|
return out * self.opacity
|
|
}
|
|
mod.widgets.Gauge.draw_range.color = #52868e
|
|
|
|
// LEVEL METERS. A cell ladder in the frame: 3 point cells with a 1 point
|
|
// gap, the accent up to 80 %, caution yellow to 90 %, the red role above;
|
|
// an unlit cell keeps 8 % of its ink; the held peak burns bright; the over
|
|
// lamp stands beside the ladder.
|
|
mod.widgets.LevelMeter.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p0 = self.pos * self.rect_size
|
|
let along = mix(self.rect_size.x, self.rect_size.y, self.vertical)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, self.vertical)
|
|
let p = vec2(mix(p0.x, self.rect_size.y - p0.y, self.vertical), mix(p0.y, p0.x, self.vertical))
|
|
let lamp_room = self.lamp * (self.lamp_size + self.lamp_gap)
|
|
let bar = max(4.0, along - lamp_room)
|
|
let lo = vec2(0.0, 0.0)
|
|
let hi = vec2(Finish.snap(bar, px), Finish.snap(across, px))
|
|
let d = Material.sd_box(p, hi * 0.5, hi * 0.5, 0.0)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(h_well() * inside, inside)
|
|
out = mix(out, vec4(h_foot(), 1.0), Finish.band(d, 0.0, px, px))
|
|
let pad = 3.0 * px
|
|
let span = max(1.0, bar - pad * 2.0)
|
|
let pitch = 4.0
|
|
let n = max(floor((span + 1.0) / pitch), 1.0)
|
|
let ci = floor((p.x - pad) / pitch)
|
|
let cx0 = pad + ci * pitch
|
|
let share = (ci + 0.5) / n
|
|
var hue = vec3(0.212, 0.784, 0.863)
|
|
if share > 0.8 {
|
|
hue = vec3(0.945, 0.937, 0.227)
|
|
}
|
|
if share > 0.9 {
|
|
hue = vec3(1.0, 0.376, 0.408)
|
|
}
|
|
let cell = Material.sd_box(p, vec2(cx0 + 1.5, across * 0.5), vec2(1.5, across * 0.5 - pad), 0.0)
|
|
let inbar = step(0.0, ci) * step(ci, n - 1.0)
|
|
let lit = step(share, self.level)
|
|
let held = step(abs(share - self.hold), 0.5 / n) * step(0.002, self.hold)
|
|
let k = max(lit, held)
|
|
let ink = mix(hue * 0.08 + h_well(), mix(hue, mix(hue, vec3(1.0, 1.0, 1.0), 0.4), held), k)
|
|
out = mix(out, vec4(ink, 1.0), Finish.cover(cell, px) * inbar)
|
|
if self.lamp > 0.5 {
|
|
let lc = vec2(bar + self.lamp_gap + self.lamp_size * 0.5, across * 0.5)
|
|
let ld = Material.sd_box(p, lc, vec2(self.lamp_size * 0.5, across * 0.5), 0.0)
|
|
let on = step(0.5, self.over)
|
|
var lcol = mix(vec3(0.08, 0.03, 0.035), vec3(1.0, 0.376, 0.408), on)
|
|
out = mix(out, vec4(lcol, 1.0), Finish.cover(ld, px))
|
|
out = mix(out, vec4(mix(vec3(0.24, 0.10, 0.12), vec3(1.0, 0.6, 0.62), on), 1.0), Finish.band(ld, 0.0, px, px))
|
|
}
|
|
return out * self.opacity
|
|
}
|
|
|
|
// SCROLL BARS. A dim hairline track with end ticks; the thumb a 2 point
|
|
// accent bar at 0.6, full and 3 points wide under the hand.
|
|
mod.widgets.ScrollBar.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p0 = self.pos * self.rect_size
|
|
let along = mix(self.rect_size.x, self.rect_size.y, self.is_vertical)
|
|
let across = mix(self.rect_size.y, self.rect_size.x, self.is_vertical)
|
|
let p = vec2(mix(p0.x, p0.y, self.is_vertical), mix(p0.y, p0.x, self.is_vertical))
|
|
let o = mix(self.rect_pos.y, self.rect_pos.x, self.is_vertical)
|
|
let mid = Finish.snap(o + across * 0.5, px) - o
|
|
let rail = Finish.cover(abs(p.y - mid - 0.5 * px) - 0.5 * px, px) * step(2.0, p.x) * step(p.x, along - 2.0)
|
|
var out = vec4(h_dim() * rail, rail)
|
|
let ends = Finish.cover(abs(abs(p.x - along * 0.5) - (along * 0.5 - 2.0)) - 0.5 * px, px) * step(abs(p.y - mid - 0.5 * px), 1.5)
|
|
out = mix(out, vec4(h_foot(), 1.0), ends)
|
|
let a = along * self.norm_scroll
|
|
let b = a + along * self.norm_handle
|
|
let warm = max(self.hover, self.drag)
|
|
let w = mix(1.0, 1.5, warm)
|
|
let d = Material.sd_box(p, vec2((a + b) * 0.5, mid + 0.5 * px), vec2(max((b - a) * 0.5 - 1.0, 2.0), w), 0.0)
|
|
out = mix(out, vec4(h_acc() * mix(0.6, 1.0, warm), 1.0), Finish.cover(d, px))
|
|
return out * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TABS. A dim baseline under the strip; a quiet tab is its dim title and
|
|
// nothing else; the pointer lifts its face a little; the tab in use has a
|
|
// lifted face, corner brackets, a 2 point accent underline with its short
|
|
// bloom and a pale pixel line under it.
|
|
let h_tab = fn(p: vec2, o: vec2, s: vec2, px: float, hover: float, focus: float, active: float, down: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + s.x, px), Finish.snap(o.y + s.y, px)) - o
|
|
let c = (lo + hi) * 0.5
|
|
let inside = step(lo.x, p.x) * step(p.x, hi.x) * step(lo.y, p.y) * step(p.y, hi.y)
|
|
let wash = (0.45 * hover * (1.0 - active) + 0.9 * active) * inside
|
|
var face = mix(vec3(0.051, 0.122, 0.153), vec3(0.063, 0.149, 0.184), active)
|
|
face = mix(face, vec3(0.055, 0.184, 0.216), down * (1.0 - active))
|
|
var out = vec4(face * wash, wash)
|
|
let br = h_brackets(p, lo, hi - vec2(0.0, 4.0 * px), 5.0, px) * max(active, hover * 0.6)
|
|
out = mix(out, vec4(mix(h_foot(), h_side(), active), 1.0), br)
|
|
// The underline: three device pixels of accent, one pale pixel below.
|
|
let ul = step(hi.y - 4.0 * px, p.y) * step(p.y, hi.y - px) * step(lo.x, p.x) * step(p.x, hi.x) * active
|
|
let pale = step(hi.y - px, p.y) * step(p.y, hi.y) * step(lo.x, p.x) * step(p.x, hi.x) * active
|
|
let ud = max(max(lo.x - p.x, p.x - hi.x), max(hi.y - 4.0 * px - p.y, 0.0))
|
|
// The underline is a lit line of the design: a bloom the eye can see,
|
|
// about 30 % one device pixel out, 15 % at 2 points, gone at 5.
|
|
let g = h_glow(ud, 0.32 * active, 5.0) * (1.0 - ul) * (1.0 - pale) * step(p.y, hi.y)
|
|
out = h_add(out, h_halo_ink(ud), g)
|
|
out = mix(out, vec4(h_acc(), 1.0), ul)
|
|
out = mix(out, vec4(0.373, 0.561, 0.592, 1.0), pale)
|
|
// Focus brackets stand off the box above and at the sides; the lower
|
|
// arms sit on the tab's own last device row, because a strip or a
|
|
// matrix that ends with its tabs clips at their foot.
|
|
return h_focus(out, p, lo, hi - vec2(0.0, 4.0 * px), px, focus * (1.0 - active))
|
|
}
|
|
mod.widgets.Tabs.height = 26.0
|
|
mod.widgets.Tabs.clip_x = false
|
|
mod.widgets.Tabs.clip_y = false
|
|
mod.widgets.Tabs.draw_tab_active.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.Tabs.draw_tab_active.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
return h_tab(p, self.rect_pos, self.rect_size, px, 0.0, 0.0, 1.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_tab_hover.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return h_tab(self.pos * self.rect_size, self.rect_pos, self.rect_size, px, 1.0, 0.0, 0.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_tab.pixel = fn() {
|
|
return vec4(0.0, 0.0, 0.0, 0.0)
|
|
}
|
|
mod.widgets.Tabs.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let y = Finish.snap(self.rect_pos.y + self.rect_size.y, px) - self.rect_pos.y
|
|
let rail = step(y - px, p.y) * step(p.y, y)
|
|
return vec4(h_foot() * rail, rail)
|
|
}
|
|
mod.widgets.Tabs.draw_sep.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let l = Finish.cover(abs(p.x - self.rect_size.x * 0.5) - px * 0.5, px) * step(6.0, p.y) * step(p.y, self.rect_size.y - 6.0)
|
|
return vec4(h_dim() * l, l)
|
|
}
|
|
mod.widgets.Tabs.draw_text.color = #7fb3bc
|
|
mod.widgets.Tabs.draw_text_active.color = #e6fdff
|
|
mod.widgets.Tabs.draw_text_active.text_style = mod.theme.font_regular{font_size: mod.theme.font_size_p}
|
|
mod.widgets.Tabs.draw_mark.color = #52868e
|
|
|
|
// The dock's tab: the same face.
|
|
mod.widgets.Tab.clip_x = false
|
|
mod.widgets.Tab.clip_y = false
|
|
mod.widgets.Tab.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.Tab.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
return h_tab(p, self.rect_pos, self.rect_size, px, self.hover, 0.0, self.active, 0.0)
|
|
}
|
|
mod.widgets.Tab.draw_text.color = #7fb3bc
|
|
mod.widgets.Tab.draw_text.color_hover = #b8eef5
|
|
mod.widgets.Tab.draw_text.color_active = #e6fdff
|
|
|
|
// The radio tab reads as a tab.
|
|
mod.widgets.RadioButtonTabFlat.clip_x = false
|
|
mod.widgets.RadioButtonTabFlat.clip_y = false
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
let live = 1.0 - self.disabled
|
|
// A disabled tab is its dim title and nothing else.
|
|
return h_tab(p, self.rect_pos, self.rect_size, px, self.hover * live, self.focus * live, self.active * live, self.down * live)
|
|
}
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color = #7fb3bc
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_hover = #b8eef5
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_active = #e6fdff
|
|
mod.widgets.RadioButtonTabFlat.draw_text.color_disabled = #33545b
|
|
|
|
// SEGMENTED. One frame cut at the top left and the bottom right round the
|
|
// row; the chosen segment gets a lifted face and the accent underline.
|
|
mod.widgets.SegmentedControl.segment_height = 24.0
|
|
mod.widgets.SegmentedControlFlat.segment_height = 24.0
|
|
mod.widgets.SegmentedControlFlat.segment_padding = 10.0
|
|
mod.widgets.SegmentedControlFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
// Both far edges are floored to a device pixel, so the lower and the
|
|
// right line stay inside the quad.
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(floor((o.x + self.rect_size.x) / px) * px, floor((o.y + self.rect_size.y) / px) * px) - o
|
|
let k = Finish.snap(clamp(0.3 * (hi.y - lo.y), 3.0, 8.0), px)
|
|
let kq = vec4(k, 0.0, k, 0.0)
|
|
let d = Finish.sd_notch(p, (lo + hi) * 0.5, (hi - lo) * 0.5, kq)
|
|
let ny = h_facing(p, lo, hi, kq, px)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(vec3(0.043, 0.102, 0.129) * 0.7 * inside, 0.7 * inside)
|
|
return mix(out, vec4(h_ink3(ny, h_top(), h_side(), h_foot()), 1.0), Finish.band(d, 0.0, px, px))
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_pill.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o + vec2(2.0 * px, 2.0 * px)
|
|
let hi = vec2(Finish.snap(o.x + self.rect_size.x, px), Finish.snap(o.y + self.rect_size.y, px)) - o - vec2(2.0 * px, 2.0 * px)
|
|
let inb = step(lo.x, p.x) * step(p.x, hi.x) * step(lo.y, p.y) * step(p.y, hi.y)
|
|
var out = vec4(vec3(0.063, 0.149, 0.184) * inb, inb)
|
|
let ul = step(hi.y - 3.0 * px, p.y) * inb
|
|
out = mix(out, vec4(h_acc(), 1.0), ul)
|
|
return out
|
|
}
|
|
mod.widgets.SegmentedControlFlat.draw_text.color = #7fb3bc
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.color = #e6fdff
|
|
mod.widgets.SegmentedControlFlat.draw_text_selected.text_style = mod.theme.font_regular{}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// THE GROUND under everything: the window's own face. The ground ink with
|
|
// a faint lift at the centre, a quiet dot grid (one device pixel every 16
|
|
// points), a small cross at every fourth node, corner brackets at the
|
|
// window's corners, and a one-level dither against banding.
|
|
let h_ground = fn(p: vec2, size: vec2, px: float) -> vec3 {
|
|
var col = vec3(0.012, 0.035, 0.059)
|
|
let hd = max(length(size * 0.5), 1.0)
|
|
let r = length(p - size * vec2(0.5, 0.42)) / hd
|
|
col = col + vec3(0.006, 0.020, 0.026) * (1.0 - smoothstep(0.0, 0.85, r))
|
|
col = col * (0.99 + 0.02 * (p.y / max(size.y, 1.0)))
|
|
let ix = floor(p.x / px)
|
|
let iy = floor(p.y / px)
|
|
let dot = step(fract(ix / 24.0 + 0.001), 0.01) * step(fract(iy / 24.0 + 0.001), 0.01)
|
|
let mx = ix - 96.0 * floor(ix / 96.0 + 0.5)
|
|
let my = iy - 96.0 * floor(iy / 96.0 + 0.5)
|
|
// Dots and crosses at one strength (+0.05): the 16 point grid reads,
|
|
// and a cross with 2 point arms marks every fourth node without
|
|
// standing out of the grid as a stray tick.
|
|
let cross = max(step(abs(mx), 0.5) * step(abs(my), 3.5), step(abs(my), 0.5) * step(abs(mx), 3.5))
|
|
col = col + vec3(0.030, 0.056, 0.063) * max(dot, cross)
|
|
// Corner brackets 24 points long, 8 points in from each window corner.
|
|
// Counted in device pixels, so each arm is exactly one pixel wide.
|
|
let ins = floor(8.0 / px)
|
|
let arm = floor(24.0 / px)
|
|
let wx = floor(size.x / px) - 1.0 - ins
|
|
let wy = floor(size.y / px) - 1.0 - ins
|
|
let bx = min(ix - ins, wx - ix)
|
|
let by = min(iy - ins, wy - iy)
|
|
let bk = max(step(abs(bx), 0.5) * step(-0.5, by) * step(by, arm), step(abs(by), 0.5) * step(-0.5, bx) * step(bx, arm))
|
|
col = mix(col, vec3(0.114, 0.227, 0.259), bk)
|
|
return col + vec3(1.0, 1.0, 1.0) * Finish.grain(p / px, 1.0 / 255.0)
|
|
}
|
|
mod.widgets.Window.show_bg = true
|
|
mod.widgets.Window.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
return vec4(h_ground(self.pos * self.rect_size, self.rect_size, px), 1.0)
|
|
}
|
|
|
|
// DIVIDERS: one dim device pixel with a brighter tick at each end.
|
|
mod.widgets.Hr.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let y = Finish.snap(self.rect_pos.y + self.rect_size.y * 0.5, px) - self.rect_pos.y
|
|
let line = step(y - px, p.y) * step(p.y, y)
|
|
let tick = step(min(p.x, self.rect_size.x - p.x), 3.0)
|
|
return vec4(mix(h_dim(), h_foot(), tick) * line, line)
|
|
}
|
|
mod.widgets.Vr.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let x = Finish.snap(self.rect_pos.x + self.rect_size.x * 0.5, px) - self.rect_pos.x
|
|
let line = step(x - px, p.x) * step(p.x, x)
|
|
let tick = step(min(p.y, self.rect_size.y - p.y), 3.0)
|
|
return vec4(mix(h_dim(), h_foot(), tick) * line, line)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SURFACES. A panel is a translucent tint over the ground (the dot grid
|
|
// shows through), its top third lifted, in a frame cut at the top left and
|
|
// bottom right with a bright run and its bloom on the top line, stepped
|
|
// notches on the long sides, a filled accent tab hanging from the top line
|
|
// and a pair of marks in the lower left corner. The inset panel is a screen
|
|
// well: darker than the ground, lifted at the centre, a neutral grid.
|
|
let h_panel = fn(p: vec2, o: vec2, s: vec2, px: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + s.x, px), Finish.snap(o.y + s.y, px)) - o
|
|
let c = (lo + hi) * 0.5
|
|
let ww = hi.x - lo.x
|
|
let hh = hi.y - lo.y
|
|
let k = Finish.snap(min(12.0, 0.2 * min(ww, hh)), px)
|
|
let kq = vec4(k, 0.0, k, 0.0)
|
|
let d = Finish.sd_notch(p, c, (hi - lo) * 0.5, kq)
|
|
let yn = clamp((p.y - lo.y) / max(hh, 1.0), 0.0, 1.0)
|
|
let inside = Finish.cover(d, px)
|
|
var face = vec3(0.039, 0.102, 0.133) + vec3(0.016, 0.026, 0.030) * (1.0 - smoothstep(0.0, 0.3, yn))
|
|
var out = vec4(face * 0.85 * inside, 0.85 * inside)
|
|
let ny = h_facing(p, lo, hi, kq, px)
|
|
let edge = Finish.band(d, 0.0, px, px)
|
|
out = mix(out, vec4(h_ink3(ny, h_top(), h_side(), h_foot()), 1.0), edge)
|
|
// The run and its bloom on the top line.
|
|
let x0 = lo.x + k
|
|
let rl = 0.3 * (ww - k)
|
|
let rx = smoothstep(x0 - px, x0 + 2.0, p.x) * (1.0 - smoothstep(x0 + rl - 10.0, x0 + rl, p.x))
|
|
out = mix(out, vec4(0.749, 0.914, 0.937, 1.0), edge * step(p.y, lo.y + 1.5 * px) * rx)
|
|
let above = 1.0 - smoothstep(lo.y - px, lo.y + px, p.y)
|
|
out = h_add(out, h_halo_ink(d), h_glow(d, 0.40, 4.5) * rx * above * (1.0 - inside))
|
|
out = h_add(out, h_halo_ink(d), h_glow(0.0 - d - px, 0.14, 2.5) * rx * inside * (1.0 - edge) * step(p.y, lo.y + 4.0))
|
|
// Notches: a 20 point step 2 points in on each long side, halfway down.
|
|
let ny0 = c.y
|
|
let side = step(abs(p.y - ny0), 10.0)
|
|
let nl = Finish.cover(abs(p.x - lo.x - 2.0 - 0.5 * px) - 0.5 * px, px) * side
|
|
let nr = Finish.cover(abs(hi.x - p.x - 2.0 - 0.5 * px) - 0.5 * px, px) * side
|
|
let stubs = Finish.cover(abs(abs(p.y - ny0) - 10.0) - 0.5 * px, px) * max(step(p.x, lo.x + 2.5), step(hi.x - 2.5, p.x)) * inside
|
|
out = mix(out, vec4(h_foot(), 1.0), max(max(nl, nr), stubs) * step(40.0, hh))
|
|
// The filled tab: a third of the width, 45 degree ends,
|
|
// two points under the top line at the right, 4 points tall.
|
|
let tw = min(0.36 * ww, 110.0)
|
|
let tx1 = hi.x - 10.0
|
|
let ty0 = lo.y + 2.0
|
|
let tq = p - vec2(tx1 - tw * 0.5, ty0 + 2.0)
|
|
let tab = max(abs(tq.y) - 2.0, abs(tq.x) + (tq.y + 2.0) - tw * 0.5)
|
|
let tf = Finish.cover(tab, px)
|
|
out = h_add(out, h_halo_ink(tab), h_glow(tab, 0.32, 5.0) * (1.0 - tf) * inside)
|
|
out = mix(out, vec4(h_acc(), 1.0), tf)
|
|
// Two marks in the lower left corner.
|
|
let mq = p - vec2(lo.x + 5.0, hi.y - 5.0)
|
|
let m1 = max(abs(mq.x), abs(mq.y)) - 0.75
|
|
let m2 = max(abs(mq.x - 3.5), abs(mq.y)) - 0.75
|
|
out = mix(out, vec4(h_acc() * 0.72, 1.0), Finish.cover(min(m1, m2), px) * step(40.0, hh))
|
|
return out
|
|
}
|
|
let h_screen = fn(p: vec2, o: vec2, s: vec2, px: float) -> vec4 {
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + s.x, px), Finish.snap(o.y + s.y, px)) - o
|
|
let c = (lo + hi) * 0.5
|
|
let kq = vec4(4.0, 0.0, 0.0, 0.0)
|
|
let d = Finish.sd_notch(p, c, (hi - lo) * 0.5, kq)
|
|
let hd = max(length((hi - lo) * 0.5), 1.0)
|
|
let r = length(p - c) / hd
|
|
var col = vec3(0.008, 0.027, 0.043) + vec3(0.012, 0.030, 0.036) * (1.0 - smoothstep(0.0, 1.0, r))
|
|
// The neutral screen grid, every 26 points from the centre.
|
|
let gq = p - c
|
|
let gx = abs(gq.x - 26.0 * floor(gq.x / 26.0 + 0.5))
|
|
let gy = abs(gq.y - 26.0 * floor(gq.y / 26.0 + 0.5))
|
|
let grid = max(Finish.cover(gx - 0.5 * px, px), Finish.cover(gy - 0.5 * px, px))
|
|
col = col + vec3(0.050, 0.055, 0.058) * grid
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(col * inside, inside)
|
|
out = mix(out, vec4(0.03, 0.06, 0.075, 1.0), Finish.band(d, px, 2.0 * px, px) * step(p.y, lo.y + 2.5 * px))
|
|
let ny = h_facing(p, lo, hi, kq, px)
|
|
return mix(out, vec4(h_ink3(ny, h_side(), h_foot(), h_foot()), 1.0), Finish.band(d, 0.0, px, px))
|
|
}
|
|
mod.widgets.PanelView.clip_x = false
|
|
mod.widgets.PanelView.clip_y = false
|
|
mod.widgets.PanelView.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(6.0, 6.0), self.rect_size + vec2(12.0, 12.0))
|
|
}
|
|
mod.widgets.PanelView.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(12.0, 12.0)) - vec2(6.0, 6.0)
|
|
if self.sunken > 0.5 {
|
|
return h_screen(p, self.rect_pos, self.rect_size, px)
|
|
}
|
|
return h_panel(p, self.rect_pos, self.rect_size, px)
|
|
}
|
|
|
|
// Cards are tiles: a dim square frame with bright corner brackets, the
|
|
// panel's translucent face, a small filled tab under the top line at the
|
|
// right, and the dark contact line along the foot.
|
|
mod.widgets.Card.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let m = self.shadow_radius
|
|
let p = self.pos * self.rect_size3 - vec2(m, m) - self.rect_shift
|
|
let o = self.rect_pos
|
|
let s = self.rect_size
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(Finish.snap(o.x + s.x, px), Finish.snap(o.y + s.y, px)) - o
|
|
let c = (lo + hi) * 0.5
|
|
let d = Material.sd_box(p, c, (hi - lo) * 0.5, 0.0)
|
|
let yn = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
|
|
let inside = Finish.cover(d, px)
|
|
let hv = self.hover
|
|
// The appearance the card hands in: Outlined draws a line (a border
|
|
// size), Filled has the highest container fill, Elevated the low one.
|
|
let outlined = step(0.01, self.border_size)
|
|
let filled = step(0.1, Finish.lum(self.color.rgb)) * (1.0 - outlined)
|
|
var face = vec3(0.039, 0.102, 0.133) + vec3(0.016, 0.026, 0.030) * (1.0 - smoothstep(0.0, 0.3, yn))
|
|
face = mix(face, self.color.rgb, filled)
|
|
face = face + vec3(0.016, 0.022, 0.026) * hv
|
|
face = mix(face, face * 0.7, self.down)
|
|
// Outlined has no face at all: the ground and its grid show; the
|
|
// pointer brings a faint one.
|
|
let fa = mix(mix(0.85, 0.97, filled), 0.5 * hv, outlined)
|
|
var out = vec4(face * fa * inside, fa * inside)
|
|
out = mix(out, vec4(0.020, 0.047, 0.063, 1.0), Finish.band(d, px, 2.0 * px, px) * step(hi.y - 2.5 * px, p.y) * (1.0 - outlined))
|
|
// The frame: the dim line on Elevated, none on Filled (the face is the
|
|
// edge), the foot line all round on Outlined.
|
|
let fc = mix(h_dim(), Finish.lift(h_foot(), 0.1 * hv), outlined)
|
|
out = mix(out, vec4(fc, 1.0), Finish.band(d, 0.0, px, px) * (1.0 - filled))
|
|
let ny = h_facing(p, lo, hi, vec4(0.0, 0.0, 0.0, 0.0), px)
|
|
let arm = min(0.25 * min(hi.x - lo.x, hi.y - lo.y), 8.0)
|
|
let br = h_brackets(p, lo, hi, arm, px)
|
|
out = mix(out, vec4(Finish.lift(h_ink3(ny, h_top(), h_side(), h_foot()), 0.2 * hv), 1.0), br)
|
|
let tq = p - vec2(hi.x - 14.0 - 12.0, lo.y + 2.0 + 1.5)
|
|
let tab = max(abs(tq.y) - 1.5, abs(tq.x) + (tq.y + 1.5) - 12.0)
|
|
let tf = Finish.cover(tab, px)
|
|
out = h_add(out, h_halo_ink(tab), h_glow(tab, 0.32, 5.0) * (1.0 - tf) * inside)
|
|
return mix(out, vec4(h_acc(), 1.0), tf)
|
|
}
|
|
|
|
// A card's title is a surface caption like a panel's: bold at the label
|
|
// size in the dim legend ink, so every surface is headed one way.
|
|
mod.widgets.H4.draw_text.color = #7fb3bc
|
|
mod.widgets.H4.draw_text.text_style = mod.theme.font_bold{font_size: 8.25}
|
|
|
|
// LIST ROWS. 22 points tall; a dim separator under each row; the pointer
|
|
// lifts the face; the chosen row takes the accent at an eighth, a line of
|
|
// the accent round it and a 2 point bar at its left.
|
|
mod.widgets.ListItem.height = 22.0
|
|
mod.widgets.ListItem.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let live = 1.0 - self.disabled
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y, px)) - o
|
|
let hi = vec2(floor((o.x + self.rect_size.x) / px) * px, floor((o.y + self.rect_size.y) / px) * px) - o
|
|
let wash = (0.5 * self.hover + 0.7 * self.down) * (1.0 - self.selected) * live
|
|
var out = vec4(vec3(0.051, 0.122, 0.153) * wash, wash)
|
|
let sep = step(hi.y - px, p.y) * step(p.y, hi.y)
|
|
out = mix(out, vec4(0.055, 0.106, 0.125, 1.0), sep * (1.0 - self.selected))
|
|
let sel = self.selected * live
|
|
let d = Material.sd_box(p, (lo + hi) * 0.5, (hi - lo) * 0.5, 0.0)
|
|
out = mix(out, vec4(0.031, 0.114, 0.141, 1.0), sel * Finish.cover(d, px))
|
|
out = mix(out, vec4(h_acc() * 0.7, 1.0), sel * Finish.band(d, 0.0, px, px))
|
|
let bar = step(lo.x, p.x) * step(p.x, lo.x + 3.0 * px) * sel
|
|
out = mix(out, vec4(h_acc(), 1.0), bar)
|
|
return out
|
|
}
|
|
mod.widgets.ListItem.draw_text.color = #b8eef5
|
|
|
|
// The rounded view keeps its caller's colours, but its corners are cut at
|
|
// 45 degrees at the top left and bottom right, and its edge is a true
|
|
// device pixel.
|
|
mod.widgets.RoundedView.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let lower = vec2(self.border_inset.x, self.border_inset.y)
|
|
let upper = self.rect_size - vec2(self.border_inset.z, self.border_inset.w)
|
|
let c = (lower + upper) * 0.5
|
|
let h = max((upper - lower) * 0.5, vec2(0.5, 0.5))
|
|
let k = min(self.border_radius * 1.5, min(min(h.x, h.y), 8.0))
|
|
let d = Finish.sd_notch(p, c, h, vec4(k, 0.0, k, 0.0))
|
|
var fill = self.color
|
|
if self.color_2.x > -0.5 {
|
|
let dir = if self.gradient_fill_horizontal > 0.5 self.pos.x else self.pos.y
|
|
fill = mix(self.color, self.color_2, dir)
|
|
}
|
|
var stroke = self.border_color
|
|
if self.border_color_2.x > -0.5 {
|
|
let dir = if self.gradient_border_horizontal > 0.5 self.pos.x else self.pos.y
|
|
stroke = mix(self.border_color, self.border_color_2, dir)
|
|
}
|
|
var col = vec4(fill.rgb * fill.a, fill.a)
|
|
let bw = max(self.border_size, px) * step(0.001, self.border_size)
|
|
let edge = Finish.band(d, 0.0, bw, px) * stroke.a
|
|
col = mix(col, vec4(stroke.rgb, 1.0), edge)
|
|
return col * Finish.cover(d, px)
|
|
}
|
|
|
|
// CHIPS. A small frame cut at two corners with the rest face; the pointer
|
|
// lifts it, a press arms it.
|
|
mod.widgets.ChipFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + 2.0, px)) - o
|
|
let hi = vec2(floor((o.x + self.rect_size.x) / px) * px, Finish.snap(o.y + self.rect_size.y - 2.0, px)) - o
|
|
let kq = vec4(4.0, 0.0, 4.0, 0.0)
|
|
let d = Finish.sd_notch(p, (lo + hi) * 0.5, (hi - lo) * 0.5, kq)
|
|
let yn = clamp((p.y - lo.y) / max(hi.y - lo.y, 1.0), 0.0, 1.0)
|
|
var face = mix(vec3(0.051, 0.114, 0.145), vec3(0.039, 0.090, 0.118), yn)
|
|
face = face + vec3(0.016, 0.022, 0.026) * self.hover
|
|
face = mix(face, vec3(0.055, 0.184, 0.216), self.down)
|
|
let inside = Finish.cover(d, px)
|
|
var out = vec4(face * 0.9 * inside, 0.9 * inside)
|
|
let ny = h_facing(p, lo, hi, kq, px)
|
|
let lc = Finish.lift(h_ink3(ny, h_top(), h_side(), h_foot()), 0.16 * max(self.hover, self.down))
|
|
return mix(out, vec4(lc, 1.0), Finish.band(d, 0.0, px, px))
|
|
}
|
|
|
|
// BADGES. A count is a data chip: a solid block of the intent's ink cut at
|
|
// two corners with ground-dark figures, as the family's label chips are.
|
|
mod.widgets.BadgePalette.neutral = #36c8dc
|
|
mod.widgets.BadgePalette.on_neutral = #03090f
|
|
mod.widgets.BadgeFlat.draw_text.text_style = mod.theme.font_bold{font_size: 6.5}
|
|
mod.widgets.Badge.draw_text.text_style = mod.theme.font_bold{font_size: 6.5}
|
|
mod.widgets.BadgeFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * self.rect_size
|
|
let o = self.rect_pos
|
|
let bh = min(14.0, self.rect_size.y)
|
|
let lo = vec2(Finish.snap(o.x, px), Finish.snap(o.y + (self.rect_size.y - bh) * 0.5, px)) - o
|
|
let hi = vec2(floor((o.x + self.rect_size.x) / px) * px - o.x, lo.y + Finish.snap(bh, px))
|
|
let k = 3.0
|
|
let d = Finish.sd_notch(p, (lo + hi) * 0.5, (hi - lo) * 0.5, vec4(k, 0.0, k, 0.0))
|
|
let a = Finish.cover(d, px) * self.color.a
|
|
var out = vec4(self.color.rgb * a, a)
|
|
if self.border_size > 0.0 {
|
|
out = mix(out, vec4(self.border_color.rgb, 1.0), Finish.band(d, 0.0, px, px) * self.border_color.a)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// SPINNERS: the bars face, the family's segments; the stock face turns on
|
|
// the pass clock, which only the library's own shader may read.
|
|
mod.widgets.SpinnerFlat.face = mod.widgets.SpinnerFace.Bars
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------- 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 = 4.0
|
|
mod.widgets.RangeSliderFlat.draw_bg.vertex = fn() {
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(4.0, 4.0), self.rect_size + vec2(8.0, 8.0))
|
|
}
|
|
mod.widgets.RangeSliderFlat.draw_bg.pixel = fn() {
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let p = self.pos * (self.rect_size + vec2(8.0, 8.0)) - vec2(4.0, 4.0)
|
|
let far = h_slider(p, self.rect_pos, self.rect_size, px, 24.0, 2.0, self.end_pos, self.start_pos, self.hot_end, self.focus, self.drag * self.hot_end, self.disabled, 1.0)
|
|
let near = h_slider(p, self.rect_pos, self.rect_size, px, 24.0, 2.0, self.start_pos, self.end_pos, self.hot_start, self.focus, self.drag * self.hot_start, self.disabled, 1.0)
|
|
let mid = 2.0 + (self.start_pos + self.end_pos) * 0.5 * (self.rect_size.x - 4.0)
|
|
return mix(near, far, step(mid, p.x))
|
|
}
|
|
|
|
// ---------------------------------------------------------------- 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.clip_x = false
|
|
mod.widgets.ToggleSlide.clip_x = false
|
|
mod.widgets.ToggleRocker.clip_y = false
|
|
mod.widgets.ToggleSlide.clip_y = false
|
|
mod.widgets.ToggleRocker.label_walk.margin.left = 37.0
|
|
mod.widgets.ToggleSlide.label_walk.margin.left = 37.0
|
|
mod.widgets.ToggleRocker.draw_bg.size = uniform(14.0)
|
|
mod.widgets.ToggleSlide.draw_bg.size = uniform(14.0)
|
|
mod.widgets.ToggleRocker.draw_bg.pill_aspect = uniform(2.4286)
|
|
mod.widgets.ToggleSlide.draw_bg.pill_aspect = uniform(2.4286)
|
|
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
|
|
|
|
// THE METER sits in the same window as the readouts and the screen: the
|
|
// library's display housing drawn round its face -- the bezel ring, a shade
|
|
// of whatever ground it is set in with a device pixel of dark at its edge;
|
|
// the face with the surround's shadow falling across it from the top and a
|
|
// device pixel of black where the glass meets the bezel; the glass's quiet
|
|
// diagonal band and the light line inside its top edge; the lip of light on
|
|
// the ground under it -- with the same corner, so the meter and the
|
|
// readouts read as one family. The scale, the red zone, the needle and its
|
|
// pivot are the library's own.
|
|
mod.widgets.NeedleMeter.draw_bg.border_radius = uniform(theme.corner_radius * 0.5)
|
|
mod.widgets.NeedleMeter.draw_bg.color_bezel = uniform(theme.color_d_05)
|
|
mod.widgets.NeedleMeter.draw_bg.color_lip = uniform(theme.color_u_15)
|
|
mod.widgets.NeedleMeter.draw_bg.pixel = fn() {
|
|
let p = self.pos * self.rect_size
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let lo = vec2(Finish.snap(self.rect_pos.x, px), Finish.snap(self.rect_pos.y, px)) - self.rect_pos
|
|
let hi = vec2(Finish.snap(self.rect_pos.x + self.rect_size.x, px), Finish.snap(self.rect_pos.y + self.rect_size.y, px) - px) - self.rect_pos
|
|
let c = (lo + hi) * 0.5
|
|
let ho = (hi - lo) * 0.5
|
|
let r_out = min(self.border_radius, min(ho.x, ho.y))
|
|
let outer = Material.sd_box(p, c, ho, r_out)
|
|
let b = Finish.snap(2.0, px)
|
|
let hf = max(ho - vec2(b, b), vec2(0.5, 0.5))
|
|
let rf = max(r_out - b, 0.0)
|
|
let face_d = Material.sd_box(p, c, hf, rf)
|
|
let pv = self.pivot()
|
|
let rr = self.scale_radius()
|
|
let q = p - pv
|
|
let r = length(q)
|
|
let ang = atan2(q.x, 0.0 - q.y)
|
|
let sweep = self.sweep * 0.017453293
|
|
let t = ang / sweep + 0.5
|
|
let on_scale = step(-0.0001, t) * step(t, 1.0001)
|
|
var face = self.color.rgb
|
|
// The scale: a hairline arc and a tick at every division.
|
|
let arc = Finish.cover(abs(r - rr) - px * 0.5, px) * on_scale
|
|
let n = max(floor(self.majors + 0.5), 1.0) * max(floor(self.minors + 0.5), 1.0)
|
|
let k = floor(clamp(t, 0.0, 1.0) * n + 0.5)
|
|
let ak = (k / n - 0.5) * sweep
|
|
let perp = abs(q.x * cos(ak) + q.y * sin(ak))
|
|
let mn = max(floor(self.minors + 0.5), 1.0)
|
|
let major = 1.0 - step(0.5, k - mn * floor(k / mn + 0.0001))
|
|
let len = mix(self.tick_minor, self.tick_major, major)
|
|
let radial = max(rr - len - r, r - rr)
|
|
let tick = Finish.cover(perp - px * mix(0.5, 1.0, major), px) * Finish.cover(radial, px) * step(0.0, 0.0 - q.y * cos(ak) + q.x * sin(ak))
|
|
face = mix(face, self.color_scale.rgb, max(arc, tick))
|
|
if self.zone < 0.999 {
|
|
let zone_t = Finish.cover((self.zone - t) * sweep * r, px) * Finish.cover((t - 1.0) * sweep * r, px)
|
|
let zone_r = Finish.cover(max(rr + px - r, r - rr - 2.5), px)
|
|
face = mix(face, self.color_zone.rgb, zone_t * zone_r)
|
|
}
|
|
// The needle, from under the pivot cover to just past the scale.
|
|
let na = self.needle_angle(self.value)
|
|
let nd = vec2(sin(na), 0.0 - cos(na))
|
|
let along = dot(q, nd)
|
|
let across = abs(q.x * nd.y - q.y * nd.x)
|
|
let needle = Finish.cover(across - px * 0.5, px) * Finish.cover(along - (rr + 3.0), px) * Finish.cover(0.0 - along - max(self.pivot_size - px, 0.0), px)
|
|
face = mix(face, self.color_needle.rgb, needle)
|
|
let pd = r - self.pivot_size
|
|
face = mix(face, mix(self.color_needle.rgb, self.color.rgb, 0.35), Finish.cover(pd, px))
|
|
face = mix(face, face * 0.55, Finish.band(pd, 0.0, px, px))
|
|
// The housing: the surround's shadow across the face from the top, the
|
|
// black device pixel at the glass's edge, and the glass over it all.
|
|
let insh = Finish.inset(p, c, hf, rf, vec2(0.0, 2.0), 1.8)
|
|
face = face * (1.0 - 0.55 * insh)
|
|
face = mix(face, face * 0.4, Finish.band(face_d, 0.0, px, px))
|
|
let u = (p - (c - hf)) / max(hf * 2.0, vec2(1.0, 1.0))
|
|
let diag = u.x + u.y * 0.6
|
|
let band = smoothstep(0.0, 0.12, diag) * (1.0 - smoothstep(0.28, 0.5, diag))
|
|
let top = Finish.cover(abs(p.y - (c.y - hf.y) - px * 1.5) - px * 0.5, px) * 0.6
|
|
let ga = clamp(0.03 * (band + top), 0.0, 1.0) * Finish.cover(face_d + px, px)
|
|
face = face * (1.0 - ga) + vec3(ga, ga, ga)
|
|
let bz = self.color_bezel
|
|
var col = vec4(bz.rgb * bz.a, bz.a)
|
|
col = Finish.over(col, vec4(0.0, 0.0, 0.0, 0.45 * Finish.band(outer, 0.0, px, px)))
|
|
col = mix(col, vec4(face, 1.0), Finish.cover(face_d, px))
|
|
col = col * Finish.cover(outer, px)
|
|
let lip = Finish.ring_out(outer, 0.0, px, px) * step(hi.y, p.y)
|
|
let la = self.color_lip.a * lip
|
|
return Finish.over(vec4(self.color_lip.rgb * la, la), col) * self.color.a * self.opacity
|
|
}
|
|
|
|
// ---------------------------------------------------------------- lamps
|
|
// LAMPS: an LED drawn in light. Its light is added, never painted, screened
|
|
// over whatever is there.
|
|
let h_led_lamp_add = fn(res: vec4, light: vec3, ground: vec3) -> vec4 {
|
|
let est = res.rgb + ground * (1.0 - res.a)
|
|
let add = (vec3(1.0, 1.0, 1.0) - exp(-light)) * max(vec3(1.0, 1.0, 1.0) - est, vec3(0.0, 0.0, 0.0))
|
|
return vec4(res.rgb + add, res.a)
|
|
}
|
|
// An LED's colour at full strength: its brightest channel at one.
|
|
let h_led_lamp_full = fn(ink: vec3) -> vec3 {
|
|
return ink / max(max(ink.x, max(ink.y, ink.z)), 0.01)
|
|
}
|
|
// The same colour at the emitter's own chroma: the hue kept and its
|
|
// saturation raised by `vivid` (0 leaves a pale colour pale; a colour with
|
|
// next to no hue, a white or a plain lamp, stays neutral either way).
|
|
let h_led_lamp_hue = fn(ink: vec3, vivid: float) -> vec3 {
|
|
let f = h_led_lamp_full(ink)
|
|
let mn = min(f.x, min(f.y, f.z))
|
|
let ch = 1.0 - mn
|
|
let pure = (f - vec3(mn, mn, mn)) / max(ch, 0.001)
|
|
return mix(f, pure, clamp(vivid, 0.0, 1.0) * 0.72 * smoothstep(0.03, 0.12, ch))
|
|
}
|
|
// The same LED where the sheet draws with light alone (a vector display, a
|
|
// phosphor screen, a neon tube): no glass and no bezel, only the emitter --
|
|
// a white-hot core at full current, its rim the most saturated colour, a
|
|
// tight halo in its hue gone in about two radii -- and, out, a dark lens
|
|
// rimmed by a faint line of the colour it would light. Premultiplied over
|
|
// `dst`.
|
|
let h_led_lamp_light = fn(dst: vec4, p: vec2, c: vec2, hb: vec2, lit: float, ink: vec3, vivid: float, px: float, ground: vec3, room: float) -> vec4 {
|
|
let rt = max(min(hb.x, hb.y), 0.4)
|
|
let d = Material.sd_box(p, c, hb, rt)
|
|
let L = clamp(lit, 0.0, 1.0)
|
|
let b = h_led_lamp_full(ink)
|
|
let hue = h_led_lamp_hue(ink, vivid)
|
|
let pw = L * sqrt(L)
|
|
let white = vec3(1.0, 1.0, 1.0)
|
|
var res = dst
|
|
let x = max(d, 0.0)
|
|
let reach = max(room, 1.0)
|
|
let win = 1.0 - smoothstep(0.55 * reach, reach, x)
|
|
if L > 0.001 {
|
|
let halo = exp(-x / max(0.32 * rt, 0.45)) * 1.6 + exp(-x / max(0.7 * rt, 0.8)) * 0.35
|
|
let hwin = 1.0 - smoothstep(1.1 * rt, 2.0 * rt + 1.0, x)
|
|
res = h_led_lamp_add(res, mix(hue, white, 0.12 * pw) * halo * pw * hwin * win, ground)
|
|
}
|
|
let ex = max(hb.x - hb.y, 0.0)
|
|
let ey = max(hb.y - hb.x, 0.0)
|
|
let qx = p.x - c.x
|
|
let qy = p.y - c.y
|
|
let q = vec2(qx - clamp(qx, 0.0 - ex, ex), qy - clamp(qy, 0.0 - ey, ey)) / rt
|
|
let t = clamp(length(q), 0.0, 1.0)
|
|
var on = mix(b, hue, 0.5) * (0.35 + 0.65 * L)
|
|
on = mix(on, vec3(1.0, 0.99, 0.98), exp(-t * t / 0.22) * smoothstep(0.5, 1.0, L) * 0.9)
|
|
on = mix(on, hue * (0.4 + 0.6 * L), smoothstep(0.6, 1.0, t))
|
|
var off = mix(vec3(0.02, 0.02, 0.03), b * 0.2, 0.15)
|
|
off = mix(off, hue * 0.45, Finish.band(d, 0.0, max(px * 1.2, 0.5), px))
|
|
let col = mix(off, min(on, white), smoothstep(0.0, 0.3, L))
|
|
let a = Finish.cover(d, px)
|
|
return Finish.over(res, vec4(col * a, a))
|
|
}
|
|
|
|
// The Lamp widget and the bar lamp: the light above, vector light: no glass and no bezel, the light alone.
|
|
// The halo's room is held in the lamp's own box (the library reserves a
|
|
// lens thickness either side once a halo is asked for), so the light is
|
|
// never cut at its edge.
|
|
mod.widgets.Lamp.halo = 0.3
|
|
mod.widgets.Lamp.halo_reach = 1.0
|
|
mod.widgets.Lamp.draw_bg.vertex = fn() {
|
|
let m = 2.0
|
|
self.vertex_pos = self.clip_and_transform_vertex(self.rect_pos - vec2(m, m), self.rect_size + vec2(2.0 * m, 2.0 * m))
|
|
}
|
|
mod.widgets.Lamp.draw_bg.pixel = fn() {
|
|
let m = 2.0
|
|
let p = self.pos * (self.rect_size + vec2(2.0 * m, 2.0 * m)) - vec2(m, m)
|
|
let px = 1.0 / max(self.draw_pass.dpi_factor, 0.5)
|
|
let reach = clamp(self.reach, 0.0, 1.0)
|
|
let t = self.rect_size.y / (1.0 + 2.0 * reach)
|
|
let room = t * reach
|
|
let c = self.rect_size * 0.5
|
|
let half_len = max((self.rect_size.x - 2.0 * room) * 0.5, t * 0.5)
|
|
// The lens is the lamp's size; its socket stands round it in the room.
|
|
let hb = vec2(mix(t * 0.5, half_len, self.bar), t * 0.5)
|
|
let res = h_led_lamp_light(vec4(0.0, 0.0, 0.0, 0.0), p, c, hb, self.lit, self.intent_color().rgb, 1.0 - step(3.5, self.intent), px, vec3(0.012, 0.035, 0.059), max(room - 0.5, 2.0))
|
|
return res * self.opacity
|
|
}
|
|
true
|