灵符lingfu
L

ling-sys

↓ 0 downloads

System activity and performance monitor in the Ling Design System — dials, meshes, drums and crosshatch for CPU, GPU, memory, disk, network, board, processes and audio, with every subsystem given a voice. A LingOS app for rpi5: live board/CPU/memory/process stats from the kernel (stats-lingos.ling).

Windows · x86-64macOS · Intel x86-64Linux · x86-64LingOS · x86-64LingOS · Pi 5 ARM64Web · WASM
lingfu install ling-sys
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (13)

lds.ling
# ============================================================
# lds.ling — the Ling Design System, drawn natively.
# Everything is line work: outline plus crosshatch. Nothing is
# filled except glyph interiors, which stand in for single-angle
# hatch at a dimmed weight. Colour is semantic, never decorative.
# Angles called "phi" are clock angles: 0 at twelve, clockwise,
# radians. Hatch angles are in degrees to match px_hatch.
# ============================================================

# ---------- palette (screen theme) ----------
fn rgbpack(r, g, b) { return r * 65536.0 + g * 256.0 + b }

fn pal_init() {
    st_set("c_paper", 0.0)
    st_set("c_ink",  rgbpack(123.0, 135.0, 151.0))
    st_set("c_asm",  rgbpack(235.0, 100.0, 102.0))
    st_set("c_lit",  rgbpack(249.0, 155.0, 56.0))
    st_set("c_bind", rgbpack(219.0, 177.0, 56.0))
    st_set("c_ctl",  rgbpack(89.0, 179.0, 113.0))
    st_set("c_call", rgbpack(80.0, 143.0, 223.0))
    st_set("c_fn",   rgbpack(115.0, 105.0, 209.0))
    st_set("c_mod",  rgbpack(177.0, 105.0, 213.0))
}

fn pcol(key) { return st_get("c_" + key, 8421504.0) }

fn col_r(c) { return floor(c / 65536.0) }
fn col_g(c) { return floor((c % 65536.0) / 256.0) }
fn col_b(c) { return c % 256.0 }

# dim a packed colour toward black (alpha on paper)
fn dim(c, k) {
    return rgbpack(floor(col_r(c) * k), floor(col_g(c) * k), floor(col_b(c) * k))
}

# set the pen to a packed colour scaled by a (also remembered as a packed
# colour, for arcs drawn from line segments where there is no draw_arc)
fn setcola(c, a) {
    bind rr = floor(col_r(c) * a)
    bind gg = floor(col_g(c) * a)
    bind bb = floor(col_b(c) * a)
    set_color(rr, gg, bb)
    st_set("pen", rgbpack(rr, gg, bb))
}

# ---------- clock-angle geometry ----------
fn ckx(cx, r, phi) { return cx + r * sin(phi) }
fn cky(cy, r, phi) { return cy - r * cos(phi) }

# An arc in the pen colour, math angles (radians, clockwise on screen). The
# host has draw_arc; LingOS does not, so there it is a run of short lines.
fn arcpx(cx, cy, r, a0, a1) {
    if st_get("os_win", 1.0) > 0.5 {
        draw_arc(cx, cy, r, a0, a1)
        return 0
    }
    bind da = a1 - a0
    bind n = ceil(abs(da) * r / 9.0)
    if n < 6.0 { bind n = 6.0 }
    if n > 56.0 { bind n = 56.0 }
    bind pen = st_get("pen", 8421504.0)
    bind x0 = cx + r * cos(a0)
    bind y0 = cy + r * sin(a0)
    bind k = 1.0
    while k <= n {
        bind a = a0 + da * k / n
        bind x1 = cx + r * cos(a)
        bind y1 = cy + r * sin(a)
        px_line(x0, y0, x1, y1, pen, 1.0)
        bind x0 = x1
        bind y0 = y1
        bind k = k + 1.0
    }
    return 0
}

# arcs take math angles; clock phi - pi/2 converts
fn arc_clock(cx, cy, r, phi0, sweep) {
    arcpx(cx, cy, r, phi0 - 1.5707963, phi0 + sweep - 1.5707963)
}

# ---------- lines ----------
fn thick(x0, y0, x1, y1, c, a) {
    px_line(x0, y0, x1, y1, c, a)
    bind dx = x1 - x0
    bind dy = y1 - y0
    bind l = sqrt(dx * dx + dy * dy)
    if l > 0.01 {
        bind nx = 0.0 - dy / l
        bind ny = dx / l
        px_line(x0 + nx, y0 + ny, x1 + nx, y1 + ny, c, a)
    }
}

fn dashline(x0, y0, x1, y1, c, a) {
    bind dx = x1 - x0
    bind dy = y1 - y0
    bind l = sqrt(dx * dx + dy * dy)
    if l < 1.0 { return 0 }
    bind ux = dx / l
    bind uy = dy / l
    bind s = 0.0
    while s < l {
        bind e = s + 6.0
        if e > l { bind e = l }
        px_line(x0 + ux * s, y0 + uy * s, x0 + ux * e, y0 + uy * e, c, a)
        bind s = s + 11.0
    }
}

# ---------- hatch ----------
# density 1 (open) .. 4 (dense): spacing multiplier, as the system specifies
fn den_mul(d) {
    if d <= 1.0 { return 3.4 }
    if d <= 2.0 { return 2.4 }
    if d <= 3.0 { return 1.6 }
    return 1.0
}

fn hatch_circle(cx, cy, r, c, angdeg, sp, a) {
    bind A = angdeg * 0.017453293
    bind nx = sin(A)
    bind ny = cos(A)
    bind tx = cos(A)
    bind ty = 0.0 - sin(A)
    bind d = 0.0 - r + sp * 0.5
    while d < r {
        bind h2 = r * r - d * d
        if h2 > 0.25 {
            bind hl = sqrt(h2) - sp * 0.4
            if hl > 0.5 {
                bind mx = cx + nx * d
                bind my = cy + ny * d
                px_line(mx - tx * hl, my - ty * hl, mx + tx * hl, my + ty * hl, c, a)
            }
        }
        bind d = d + sp
    }
    return 0
}

fn hatch_ring(cx, cy, r0, r1, c, angdeg, sp, a) {
    bind A = angdeg * 0.017453293
    bind nx = sin(A)
    bind ny = cos(A)
    bind tx = cos(A)
    bind ty = 0.0 - sin(A)
    bind d = 0.0 - r1 + sp * 0.5
    while d < r1 {
        bind h2 = r1 * r1 - d * d
        if h2 > 0.25 {
            bind ho = sqrt(h2) - sp * 0.3
            bind mx = cx + nx * d
            bind my = cy + ny * d
            bind ad = abs(d)
            if ad >= r0 {
                if ho > 0.5 {
                    px_line(mx - tx * ho, my - ty * ho, mx + tx * ho, my + ty * ho, c, a)
                }
            } else {
                bind hi2 = r0 * r0 - d * d
                bind hi = 0.0
                if hi2 > 0.0 { bind hi = sqrt(hi2) + sp * 0.3 }
                if ho > hi {
                    px_line(mx - tx * ho, my - ty * ho, mx - tx * hi, my - ty * hi, c, a)
                    px_line(mx + tx * hi, my + ty * hi, mx + tx * ho, my + ty * ho, c, a)
                }
            }
        }
        bind d = d + sp
    }
    return 0
}

# annular sector between clock angles p0 .. p0+sw, sampled runs
fn hatch_sector(cx, cy, r0, r1, p0, sw, c, angdeg, sp, a) {
    bind A = angdeg * 0.017453293
    bind nx = sin(A)
    bind ny = cos(A)
    bind tx = cos(A)
    bind ty = 0.0 - sin(A)
    bind d = 0.0 - r1 + sp * 0.5
    while d < r1 {
        bind h2 = r1 * r1 - d * d
        if h2 > 0.25 {
            bind hl = sqrt(h2)
            bind mx = cx + nx * d
            bind my = cy + ny * d
            bind s = 0.0 - hl
            bind inrun = 0
            bind runx = 0.0
            bind runy = 0.0
            bind lx = 0.0
            bind ly = 0.0
            while s <= hl {
                bind qx = mx + tx * s
                bind qy = my + ty * s
                bind ddx = qx - cx
                bind ddy = qy - cy
                bind rr = sqrt(ddx * ddx + ddy * ddy)
                bind ok = 0
                if rr >= r0 {
                    if rr <= r1 - sp * 0.2 { bind ok = 1 }
                }
                if ok == 1 {
                    bind phi = atan2(ddx, 0.0 - ddy)
                    if phi < 0.0 { bind phi = phi + 6.2831853 }
                    bind rel = phi - p0
                    while rel < 0.0 { bind rel = rel + 6.2831853 }
                    if rel > sw { bind ok = 0 }
                }
                if ok == 1 {
                    if inrun == 0 {
                        bind runx = qx
                        bind runy = qy
                        bind inrun = 1
                    }
                } else {
                    if inrun == 1 {
                        px_line(runx, runy, lx, ly, c, a)
                        bind inrun = 0
                    }
                }
                bind lx = qx
                bind ly = qy
                bind s = s + 2.5
            }
            if inrun == 1 { px_line(runx, runy, lx, ly, c, a) }
        }
        bind d = d + sp
    }
    return 0
}

# convex polygon from a flat [x0,y0,x1,y1,...] list
fn hatch_poly(pts, c, angdeg, sp, a) {
    bind n = len(pts) / 2
    if n < 3 { return 0 }
    bind A = angdeg * 0.017453293
    bind nx = sin(A)
    bind ny = cos(A)
    bind tx = cos(A)
    bind ty = 0.0 - sin(A)
    bind cmin = 999999.0
    bind cmax = 0.0 - 999999.0
    bind i = 0
    while i < n {
        bind pxx = list_get(pts, i * 2)
        bind pyy = list_get(pts, i * 2 + 1)
        bind cv = pxx * nx + pyy * ny
        if cv < cmin { bind cmin = cv }
        if cv > cmax { bind cmax = cv }
        bind i = i + 1
    }
    bind cc = cmin + sp * 0.6
    while cc < cmax - sp * 0.1 {
        bind smin = 999999.0
        bind smax = 0.0 - 999999.0
        bind hits = 0
        bind i = 0
        while i < n {
            bind j = i + 1
            if j >= n { bind j = 0 }
            bind pxx = list_get(pts, i * 2)
            bind pyy = list_get(pts, i * 2 + 1)
            bind qxx = list_get(pts, j * 2)
            bind qyy = list_get(pts, j * 2 + 1)
            bind pc = pxx * nx + pyy * ny
            bind qc = qxx * nx + qyy * ny
            bind dc = qc - pc
            if abs(dc) > 0.0001 {
                bind u = (cc - pc) / dc
                if u >= 0.0 {
                    if u <= 1.0 {
                        bind ix = pxx + (qxx - pxx) * u
                        bind iy = pyy + (qyy - pyy) * u
                        bind sv = ix * tx + iy * ty
                        if sv < smin { bind smin = sv }
                        if sv > smax { bind smax = sv }
                        bind hits = hits + 1
                    }
                }
            }
            bind i = i + 1
        }
        if hits >= 2 {
            bind s0 = smin + sp * 0.35
            bind s1 = smax - sp * 0.35
            if s1 > s0 {
                px_line(nx * cc + tx * s0, ny * cc + ty * s0, nx * cc + tx * s1, ny * cc + ty * s1, c, a)
            }
        }
        bind cc = cc + sp
    }
    return 0
}

# ---------- polygons ----------
fn mkngon(cx, cy, r, sides, rotdeg) {
    bind pts = list_new()
    bind k = 0
    while k < sides {
        bind phi = rotdeg * 0.017453293 + k * 6.2831853 / sides
        bind pts = list_push(pts, ckx(cx, r, phi))
        bind pts = list_push(pts, cky(cy, r, phi))
        bind k = k + 1
    }
    return pts
}

fn poly_outline(pts, c, a) {
    bind n = len(pts) / 2
    bind i = 0
    while i < n {
        bind j = i + 1
        if j >= n { bind j = 0 }
        thick(list_get(pts, i * 2), list_get(pts, i * 2 + 1), list_get(pts, j * 2), list_get(pts, j * 2 + 1), c, a)
        bind i = i + 1
    }
    return 0
}

# ---------- node: the typed shapes ----------
# asm octagon · lit cross · bind circle · ctl triangle · call diamond ·
# fn hexagon · mod square. Interior crosshatched; density carries activity.
fn node(key, cx, cy, r, den, a) {
    bind c = pcol(key)
    bind sp = r * 0.3 * den_mul(den)
    if sp < 2.6 { bind sp = 2.6 }
    if key == "bind" {
        setcola(c, a)
        arcpx(cx, cy, r, 0.0, 6.2831853)
        arcpx(cx, cy, r - 1.0, 0.0, 6.2831853)
        hatch_circle(cx, cy, r - 1.5, c, 45.0, sp, 0.55 * a)
        hatch_circle(cx, cy, r - 1.5, c, 315.0, sp, 0.55 * a)
        return 0
    }
    if key == "lit" {
        bind w = r * 0.38
        px_line(cx - w, cy - r, cx + w, cy - r, c, a)
        px_line(cx + w, cy - r, cx + w, cy - w, c, a)
        px_line(cx + w, cy - w, cx + r, cy - w, c, a)
        px_line(cx + r, cy - w, cx + r, cy + w, c, a)
        px_line(cx + r, cy + w, cx + w, cy + w, c, a)
        px_line(cx + w, cy + w, cx + w, cy + r, c, a)
        px_line(cx + w, cy + r, cx - w, cy + r, c, a)
        px_line(cx - w, cy + r, cx - w, cy + w, c, a)
        px_line(cx - w, cy + w, cx - r, cy + w, c, a)
        px_line(cx - r, cy + w, cx - r, cy - w, c, a)
        px_line(cx - r, cy - w, cx - w, cy - w, c, a)
        px_line(cx - w, cy - w, cx - w, cy - r, c, a)
        px_hatch(cx - w + 1.0, cy - r + 1.0, w * 2.0 - 2.0, r * 2.0 - 2.0, c, 45.0, sp, 0.5 * a)
        px_hatch(cx - r + 1.0, cy - w + 1.0, r - w - 2.0, w * 2.0 - 2.0, c, 45.0, sp, 0.5 * a)
        px_hatch(cx + w + 1.0, cy - w + 1.0, r - w - 2.0, w * 2.0 - 2.0, c, 45.0, sp, 0.5 * a)
        return 0
    }
    bind sides = 4.0
    bind rot = 45.0
    if key == "asm" { bind sides = 8.0 bind rot = 22.5 }
    if key == "ctl" { bind sides = 3.0 bind rot = 0.0 }
    if key == "call" { bind sides = 4.0 bind rot = 0.0 }
    if key == "fn" { bind sides = 6.0 bind rot = 0.0 }
    bind pts = mkngon(cx, cy, r, sides, rot)
    poly_outline(pts, c, a)
    hatch_poly(pts, c, 45.0, sp, 0.55 * a)
    hatch_poly(pts, c, 315.0, sp, 0.55 * a)
    return 0
}

# ---------- text scale: every word in the app obeys this ----------
fn tsize(s) { return s * st_get("tscale", 1.0) }

# ---------- flat Daemon: data text without the tile brackets ----------
# Same character advance as the English face (measured from it), so switching
# faces never reflows a line. Set by stext around its Daemon calls only; titles
# and headings keep their bracketed tiles.
fn flat(on) {
    st_set("tflat", on)
    return 0
}

fn flat_adv(size) {
    bind w = font_width(st_get("fe", 0.0), size * 1.35, "0")
    if w < 4.0 { return size * 0.74 }
    return w
}

# ---------- tile and word (Daemon; English face in text mode 2) ----------
fn tile(ch, x, y, rawsize, key, a) {
    bind size = tsize(rawsize)
    bind c = pcol(key)
    if st_get("tflat", 0.0) > 0.5 {
        if ch == " " { return 0 }
        bind adv = flat_adv(size)
        bind gpx = size * 1.3
        bind w = font_width(st_get("fd", 0.0), gpx, ch)
        bind lim = adv * 0.88
        if w > lim {
            if w > 0.0 {
                bind gpx = gpx * lim / w
                bind w = font_width(st_get("fd", 0.0), gpx, ch)
            }
        }
        bind gx = x + (adv - w) * 0.5
        bind gy = y + size * 0.5 - gpx * st_get("voff_d", 0.35)
        setcola(c, a)
        font_text(st_get("fd", 0.0), gx, gy, gpx, ch)
        font_text(st_get("fd", 0.0), gx + 0.55, gy, gpx, ch)
        return 0
    }
    if ch == " " {
        px_disc(x + size * 0.5, y + size * 0.5, size * 0.04 + 0.8, c, 0.7 * a)
        return 0
    }
    px_outline(x + 0.5, y + 0.5, size - 1.0, size - 1.0, c, 0.38 * a)
    bind ins = size * 0.055
    bind arm = size * 0.21
    bind x1 = x + size
    bind y1 = y + size
    px_line(x + ins, y + ins, x + ins + arm, y + ins, c, a)
    px_line(x + ins, y + ins, x + ins, y + ins + arm, c, a)
    px_line(x1 - ins, y + ins, x1 - ins - arm, y + ins, c, a)
    px_line(x1 - ins, y + ins, x1 - ins, y + ins + arm, c, a)
    px_line(x + ins, y1 - ins, x + ins + arm, y1 - ins, c, a)
    px_line(x + ins, y1 - ins, x + ins, y1 - ins - arm, c, a)
    px_line(x1 - ins, y1 - ins, x1 - ins - arm, y1 - ins, c, a)
    px_line(x1 - ins, y1 - ins, x1 - ins, y1 - ins - arm, c, a)
    bind fd = st_get("fd", 0.0)
    bind voff = st_get("voff_d", 0.35)
    if st_get("p_txt", 1.0) >= 2.0 && st_get("fe", 0.0) >= 0.0 {
        bind fd = st_get("fe", 0.0)
        bind voff = st_get("voff_e", 0.45)
    }
    bind target = size * 0.60
    bind gpx = target * st_get("fit_d", 1.3)
    bind w = font_width(fd, gpx, ch)
    if w > target {
        if w > 0.0 {
            bind gpx = gpx * target / w
            bind w = font_width(fd, gpx, ch)
        }
    }
    bind gx = x + (size - w) * 0.5
    bind gy = y + size * 0.5 - gpx * voff
    setcola(c, a)
    font_text(fd, gx, gy, gpx, ch)
    font_text(fd, gx + 0.55, gy, gpx, ch)
    return 0
}

fn word_w(text, rawsize) {
    if want_eng2() {
        return etext_w(text, rawsize)
    }
    bind size = tsize(rawsize)
    if st_get("tflat", 0.0) > 0.5 { return len(text) * flat_adv(size) }
    return len(text) * size * 1.14 - size * 0.14
}

fn word(text, x, y, rawsize, key, a) {
    if want_eng2() {
        etext(text, x, y, rawsize, key, a)
        return 0
    }
    bind size = tsize(rawsize)
    bind step = size * 1.14
    if st_get("tflat", 0.0) > 0.5 { bind step = flat_adv(size) }
    bind n = len(text)
    bind i = 0
    while i < n {
        tile(str_slice(text, i, 1), x + i * step, y, rawsize, key, a)
        bind i = i + 1
    }
    return 0
}

fn wordc(text, cx, y, size, key, a) {
    word(text, cx - word_w(text, size) * 0.5, y, size, key, a)
    return 0
}

fn wordr(text, xr, y, size, key, a) {
    word(text, xr - word_w(text, size), y, size, key, a)
    return 0
}

# Can English text be drawn at all? Yes with a loaded font (Consolas and friends on a
# desktop), and yes on LingOS (the kernel's 8x8 pixel font). A desktop host that found
# no English font has neither, so it keeps the Daemon text instead of drawing nothing.
fn eng_ok() { return st_get("fe", 0.0) >= 0.0 || st_get("os_win", 1.0) < 0.5 }
fn want_eng() { return st_get("p_txt", 1.0) >= 1.0 && eng_ok() }
fn want_eng2() { return st_get("p_txt", 1.0) >= 2.0 && eng_ok() }
# ---------- English text, for stats and readouts ----------
# Consolas on Windows. LingOS has no such file, and px_text draws NOTHING for a
# font that did not load, so there the English text falls back to the kernel's
# own 8x8 pixel font (px_text8): always present, instant, and the OS's UI face.
fn e8on() { return st_get("fe", 0.0) < 0.0 }

# the 8x8 glyph's integer scale for a word of this size
fn e8sc(size) {
    bind s = round(tsize(size) * 0.123)
    if s < 1.0 { return 1.0 }
    return s
}

# Sized so its cap height roughly matches a tile word of the same size.
fn etext_px(rawsize) { return tsize(rawsize) * 1.35 }

fn etext_w(text, size) {
    if e8on() { return len(text) * 8.0 * e8sc(size) }
    return font_width(st_get("fe", 0.0), etext_px(size), text)
}

fn etext(text, x, y, size, key, a) {
    if e8on() {
        bind sc = e8sc(size)
        px_text8(x, y + tsize(size) * 0.5 - 4.0 * sc, dim(pcol(key), a), text, sc)
        return 0
    }
    bind fe = st_get("fe", 0.0)
    bind gpx = etext_px(size)
    bind gy = y + tsize(size) * 0.5 - gpx * st_get("voff_e", 0.45)
    px_text(fe, x, gy, gpx, dim(pcol(key), a), text, 1.0)
    return 0
}

fn etextc(text, cx, y, size, key, a) {
    etext(text, cx - etext_w(text, size) * 0.5, y, size, key, a)
    return 0
}

fn etextr(text, xr, y, size, key, a) {
    etext(text, xr - etext_w(text, size), y, size, key, a)
    return 0
}

# ---------- stats text: obeys the TXT switch ----------
# p_txt 0 = all Daemon/Chesilin · 1 = stats in English (default) ·
# p_txt 2 = everything in English
fn stext(text, x, y, size, key, a) {
    if want_eng() {
        etext(text, x, y, size, key, a)
    } else {
        flat(1.0)
        word(text, x, y, size, key, a)
        flat(0.0)
    }
    return 0
}

fn stextc(text, cx, y, size, key, a) {
    if want_eng() {
        etextc(text, cx, y, size, key, a)
    } else {
        flat(1.0)
        wordc(text, cx, y, size, key, a)
        flat(0.0)
    }
    return 0
}

fn stextr(text, xr, y, size, key, a) {
    if want_eng() {
        etextr(text, xr, y, size, key, a)
    } else {
        flat(1.0)
        wordr(text, xr, y, size, key, a)
        flat(0.0)
    }
    return 0
}

fn stext_w(text, size) {
    if want_eng() { return etext_w(text, size) }
    flat(1.0)
    bind w = word_w(text, size)
    flat(0.0)
    return w
}

# ---------- ring text (Chesilin) ----------
# One glyph per annular cell between r0..r1, marching clockwise from phi0.
# mark >= 0 lights that cell (marquee). Glyphs stay upright and orbit.
fn ringtext(cx, cy, r0, r1, text, phi0, sweep, key, mark, a) {
    bind c = pcol(key)
    bind n = len(text)
    if n < 1 { return 0 }
    bind fc = st_get("fc", 1.0)
    bind voffc = st_get("voff_c", 0.25)
    bind fitc = st_get("fit_c", 0.96)
    if st_get("p_txt", 1.0) >= 2.0 && st_get("fe", 0.0) >= 0.0 {
        bind fc = st_get("fe", 1.0)
        bind voffc = st_get("voff_e", 0.45)
        bind fitc = 1.2
    }
    bind band = r1 - r0
    bind rmid = (r0 + r1) * 0.5
    bind cell = sweep / n
    bind arcw = cell * rmid
    bind gmax = band * 0.62 * fitc
    setcola(c, 0.5 * a)
    arcpx(cx, cy, r0, 0.0, 6.2831853)
    arcpx(cx, cy, r1, 0.0, 6.2831853)
    bind i = 0
    while i < n {
        bind pb = phi0 + cell * i
        px_line(ckx(cx, r0 + 1.5, pb), cky(cy, r0 + 1.5, pb), ckx(cx, r1 - 1.5, pb), cky(cy, r1 - 1.5, pb), c, 0.2 * a)
        bind i = i + 1
    }
    bind i = 0
    while i < n {
        bind ch = str_slice(text, i, 1)
        bind phi = phi0 + cell * (i + 0.5)
        if ch != " " {
            bind gpx = gmax
            bind w = font_width(fc, gpx, ch)
            bind wlim = arcw * 0.7
            if w > wlim {
                if w > 0.0 {
                    bind gpx = gpx * wlim / w
                }
            }
            if gpx < 7.0 { bind gpx = 7.0 }
            bind w = font_width(fc, gpx, ch)
            bind gx = ckx(cx, rmid, phi) - w * 0.5
            bind gy = cky(cy, rmid, phi) - gpx * voffc
            bind glow = 0.62
            if i == mark { bind glow = 1.0 }
            setcola(c, a * glow)
            font_text(fc, gx, gy, gpx, ch)
            font_text(fc, gx + 0.55, gy, gpx, ch)
        }
        bind i = i + 1
    }
    return 0
}

# ---------- segment ring: alternating hatched arc segments ----------
# phase animates it; neighbouring rings should turn opposite ways.
# den 1..4 sets stroke spacing: density as data.
fn segring(cx, cy, r0, r1, nseg, phase, key, den, a) {
    bind c = pcol(key)
    setcola(c, 0.5 * a)
    arcpx(cx, cy, r0, 0.0, 6.2831853)
    arcpx(cx, cy, r1, 0.0, 6.2831853)
    bind rm = (r0 + r1) * 0.5
    bind band = r1 - r0
    bind sw = 6.2831853 / nseg
    bind sp = band * 0.5 * den_mul(den) * 0.55
    if sp < 3.5 { bind sp = 3.5 }
    bind astep = sp / rm
    bind tilt = band / rm
    bind k = 0.0
    while k < nseg {
        bind p0 = phase + k * sw
        px_line(ckx(cx, r0 + 1.0, p0), cky(cy, r0 + 1.0, p0), ckx(cx, r1 - 1.0, p0), cky(cy, r1 - 1.0, p0), c, 0.3 * a)
        if (k % 2.0) == 0.0 {
            bind s = p0 + astep * 0.6
            bind pend = p0 + sw * 0.94
            while s < pend {
                px_line(ckx(cx, r0 + 1.5, s), cky(cy, r0 + 1.5, s), ckx(cx, r1 - 1.5, s + tilt), cky(cy, r1 - 1.5, s + tilt), c, 0.45 * a)
                bind s = s + astep
            }
        }
        bind k = k + 1.0
    }
    return 0
}

# ---------- dial parts ----------
fn dialbezel(cx, cy, r, key, a) {
    bind c = pcol(key)
    setcola(c, 0.95 * a)
    arcpx(cx, cy, r, 0.0, 6.2831853)
    arcpx(cx, cy, r - 1.0, 0.0, 6.2831853)
    setcola(c, 0.6 * a)
    arcpx(cx, cy, r - 9.0, 0.0, 6.2831853)
    hatch_ring(cx, cy, r - 9.0, r - 1.0, c, 45.0, 5.0, 0.38 * a)
    bind k = 0
    while k < 60 {
        bind phi = k * 0.10471976
        bind tl = 4.0
        if k % 12 == 0 { bind tl = 9.0 }
        bind ri = r - 12.0 - tl
        bind ro = r - 12.0
        px_line(ckx(cx, ri, phi), cky(cy, ri, phi), ckx(cx, ro, phi), cky(cy, ro, phi), c, 0.6 * a)
        bind k = k + 1
    }
    bind k = 0
    while k < 4 {
        bind phi = 0.7853982 + k * 1.5707963
        bind dx = ckx(cx, r, phi)
        bind dy = cky(cy, r, phi)
        bind dpts = mkngon(dx, dy, 6.5, 4.0, 0.0)
        poly_outline(dpts, c, a)
        hatch_poly(dpts, c, 45.0, 2.2, 0.8 * a)
        bind k = k + 1
    }
    return 0
}

fn hand(cx, cy, reach, phi, wid, key, a) {
    bind c = pcol(key)
    bind tipx = ckx(cx, reach, phi)
    bind tipy = cky(cy, reach, phi)
    bind bphi = phi + 1.5707963
    bind bx = sin(bphi) * wid * 0.5
    bind by = 0.0 - cos(bphi) * wid * 0.5
    bind tailx = ckx(cx, 0.0 - reach * 0.18, phi)
    bind taily = cky(cy, 0.0 - reach * 0.18, phi)
    bind pts = list_new()
    bind pts = list_push(pts, tipx)
    bind pts = list_push(pts, tipy)
    bind pts = list_push(pts, cx + bx)
    bind pts = list_push(pts, cy + by)
    bind pts = list_push(pts, tailx)
    bind pts = list_push(pts, taily)
    bind pts = list_push(pts, cx - bx)
    bind pts = list_push(pts, cy - by)
    poly_outline(pts, c, a)
    hatch_poly(pts, c, 45.0, 3.2, 0.7 * a)
    setcola(c, a)
    arcpx(cx, cy, wid * 0.75, 0.0, 6.2831853)
    px_disc(cx, cy, 1.6, c, a)
    return 0
}

# ---------- connector with flowing packets ----------
# The dot uses the diamond: solid dot at the origin, open diamond at
# the destination, coloured by the source. kind 1 primary, 0 secondary.
fn connector(x0, y0, x1, y1, key, kind, a) {
    bind c = pcol(key)
    if kind == 1 {
        thick(x0, y0, x1, y1, c, 0.8 * a)
    } else {
        dashline(x0, y0, x1, y1, c, 0.6 * a)
    }
    px_disc(x0, y0, 3.0, c, 0.9 * a)
    bind dpts = mkngon(x1, y1, 5.0, 4.0, 0.0)
    poly_outline(dpts, c, a)
    return 0
}

# n packets travel origin -> destination at constant speed.
# Returns the number of arrivals since the last call (0..3), for sound.
fn packets(id, x0, y0, x1, y1, key, n, t, a) {
    if n < 1 { return 0 }
    bind c = pcol(key)
    bind dx = x1 - x0
    bind dy = y1 - y0
    bind l = sqrt(dx * dx + dy * dy)
    if l < 8.0 { return 0 }
    bind spd = 62.0 * st_get("motion", 1.0)
    bind base = t * spd / l
    bind k = 0
    while k < n {
        bind u = base + k / n
        bind u = u - floor(u)
        bind qx = x0 + dx * u
        bind qy = y0 + dy * u
        px_outline(qx - 2.5, qy - 2.5, 5.0, 5.0, c, a)
        px_rect(qx - 1.5, qy - 1.5, 3.0, 3.0, dim(c, 0.4), a)
        bind k = k + 1
    }
    bind ph = floor(base * n)
    bind prev = st_get("cph_" + id, ph)
    st_set("cph_" + id, ph)
    bind ev = ph - prev
    if ev < 0.0 { bind ev = 0.0 }
    if ev > 3.0 { bind ev = 3.0 }
    return ev
}

# ---------- frame ----------
fn framebox(x, y, w, h, title, key, a) {
    bind c = pcol(key)
    px_outline(x, y, w, h, c, 0.9 * a)
    px_outline(x + 1.0, y + 1.0, w - 2.0, h - 2.0, c, 0.5 * a)
    px_outline(x + 8.0, y + 8.0, w - 16.0, h - 16.0, c, 0.55 * a)
    px_hatch(x + 2.0, y + 2.0, w - 4.0, 6.0, c, 45.0, 7.0, 0.17 * a)
    px_hatch(x + 2.0, y + h - 8.0, w - 4.0, 6.0, c, 45.0, 7.0, 0.17 * a)
    px_hatch(x + 2.0, y + 8.0, 6.0, h - 16.0, c, 45.0, 7.0, 0.17 * a)
    px_hatch(x + w - 8.0, y + 8.0, 6.0, h - 16.0, c, 45.0, 7.0, 0.17 * a)
    bind arm = 24.0
    thick(x, y, x + arm, y, c, a)
    thick(x, y, x, y + arm, c, a)
    thick(x + w - arm, y, x + w, y, c, a)
    thick(x + w, y, x + w, y + arm, c, a)
    thick(x, y + h - arm, x, y + h, c, a)
    thick(x, y + h, x + arm, y + h, c, a)
    thick(x + w, y + h - arm, x + w, y + h, c, a)
    thick(x + w - arm, y + h, x + w, y + h, c, a)
    if len(title) > 0 {
        bind ts = 15.0
        bind tw = word_w(title, ts) + 14.0
        bind tx = x + 20.0
        px_rect(tx, y - 11.0, tw, 22.0, 0.0, 1.0)
        px_outline(tx, y - 11.0, tw, 22.0, c, 0.8 * a)
        word(title, tx + 7.0, y - 7.0, ts, key, a)
    }
    return 0
}

← Back to index