灵符lingfu
L

ling-wallet

↓ 0 downloads

Ling Wallet: a post-quantum privacy-coin wallet, full node and HyperTorus miner for LNG

Windows · x86-64 ✕Linux · x86-64 ✕LingOS · x86-64LingOS · Pi 5 ARM64 ✕
Keywords wallet, cryptocurrency, lng, node, miner, ling
lingfu install ling-wallet
LicenseMIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (116)

ling/ui/widgets.ling
# widgets.ling — immediate-mode widgets: buttons, text fields, toggles,
# sliders, steppers, segmented choices, and shared layout pieces.

# Button; style is "plain" or "primary". Fires on press, or on Enter when `enter`.
fn button(x, y, w, h, text, col0, style, enabled, enter, icon) {
    bind hot = u_hover(x, y, w, h) && enabled
    bind held = hot && u_down()
    bind col = sel(enabled, col0, C_LINE_SOFT)
    if style == "primary" {
        fill_a(x, y, w, h, col, sel(held, 0.30, sel(hot, 0.24, 0.16)))
        hatch(x + 2, y + 2, w - 4, h - 4, col, true, 3, sel(hot, 0.40, 0.25))
    } else if hot {
        fill_a(x, y, w, h, col, sel(held, 0.18, 0.10))
        hatch(x + 2, y + 2, w - 4, h - 4, col, true, 3, 0.22)
    }
    outline(x, y, w, h, col, sel(enabled && !hot, 0.7, 1))
    brackets(x, y, w, h, 5, sel(enabled, lift(col, 0.35), C_LINE), 1)
    bind tc = lift(col, 0.45)
    if !enabled { bind tc = C_TEXT_FAINT }
    if enabled && (style == "primary" || hot) { bind tc = C_TEXT_STRONG }
    bind up = str_upper(text)
    bind tw = t_w_sp(F_UI, 15, up, 1.6)
    bind tx = x + round((w - tw) / 2)
    if icon != "" {
        bind tx = tx + 10
        icon_draw(icon, tx - 22, y + round(h / 2), tc)
    }
    t_text_sp(F_UI, 15, tx, y + round(h / 2) + t_cap(F_UI, 15) / 2, up, tc, 1.6)
    bind fired = enabled && ((hot && u_pressed()) || (enter && u_key("enter")))
    if fired && enter { u_eat("enter") }
    return fired
}
fn btn(x, y, w, h, text, col) { return button(x, y, w, h, text, col, "plain", true, false, "") }

# Copy link-button; returns clicked.
fn copy_btn(x, y, text, col) {
    bind w = round(t_w_sp(F_UI, 15, str_upper(text), 1.6)) + 48
    return button(x, y, w, 36, text, col, "plain", true, false, "copy")
}
fn copy_btn_w(text) { return round(t_w_sp(F_UI, 15, str_upper(text), 1.6)) + 48 }

# Text fields. Values live in the store under "tf:<id>"; kind is one of
# "text", "password", "address", "amount", "word".
fn tf_get(id) { return st_get("tf:" + id, "") }
fn tf_set(id, v) {
    st_set("tf:" + id, v)
    if st_get("text_owner", "") == id { text_set(v) }
}
fn tf_clear(id) { tf_set(id, "") }

bind DIGITS = "0123456789"
bind ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

fn tf_max(kind) {
    if kind == "amount" { return 20 }
    if kind == "word" { return 16 }
    if kind == "address" { return 120 }
    if kind == "memo" { return 64 }
    return 128
}

fn tf_filter(kind, s) {
    if kind == "text" || kind == "password" || kind == "memo" { return substr(s, 0, tf_max(kind)) }
    bind out = ""
    bind dot = false
    bind n = str_len(s)
    bind i = 0
    while i < n && str_len(out) < tf_max(kind) {
        bind ch = substr(s, i, 1)
        if kind == "amount" {
            if str_find(DIGITS, ch) >= 0 { bind out = out + ch }
            if ch == "." && !dot {
                bind out = out + ch
                bind dot = true
            }
        } else if kind == "word" {
            if str_find(ALPHA, ch) >= 0 { bind out = out + str_lower(ch) }
        } else {
            if str_find(ALPHA, ch) >= 0 || str_find(DIGITS, ch) >= 0 { bind out = out + ch }
        }
        bind i = i + 1
    }
    return out
}

# Draws and edits one field. Returns [changed, submitted, space, pasted].
fn field(id, x, y, w, h, placeholder, accent, kind, right_pad) {
    st_lpush("order", id)
    bind hot = u_hover(x, y, w, h)
    if u_pressed() && hot {
        focus_set(id)
        st_set("focus_claimed", 1)
    }
    bind focused = focus_get() == id
    bind old = tf_get(id)
    bind v = old
    bind changed = false
    bind submitted = false
    bind space = false
    bind pasted = ""
    if focused {
        if st_get("text_owner", "") != id {
            text_set(old)
            st_set("text_owner", id)
            st_set("tf_all", "")
        }
        # u_begin already polled this frame; polling again would repeat keys
        bind v = text_get()
        if u_ctrl() && u_key("a") { st_set("tf_all", id) }
        if u_ctrl() && u_key("c") && kind != "password" && old != "" {
            clip_copy(old, "Text")
            u_eat("c")
        }
        if u_ctrl() && u_key("v") {
            bind clip = clip_paste()
            bind pasted = clip
            u_eat("v")
            if kind != "word" || len(str_split(clip, " ")) <= 1 {
                if st_get("tf_all", "") == id { bind v = "" }
                bind v = v + clip
                st_set("tf_all", "")
            }
        }
        if st_get("tf_all", "") == id && v != old {
            if str_len(v) < str_len(old) {
                bind v = ""
            } else {
                bind v = substr(v, str_len(old), str_len(v) - str_len(old))
            }
            st_set("tf_all", "")
        }
        if kind == "word" && str_find(v, " ") >= 0 {
            bind space = true
            bind v = str_replace(v, " ", "")
        }
        bind v = tf_filter(kind, v)
        if v != text_get() { text_set(v) }
        if v != old {
            st_set("tf:" + id, v)
            bind changed = true
        }
        if u_key("enter") { bind submitted = true }
    }
    fill(x, y, w, h, C_BG_DEEP)
    bind oc = C_LINE_SOFT
    if hot { bind oc = C_LINE }
    if focused { bind oc = shade(accent, 0.25) }
    outline(x, y, w, h, oc, 1)
    if focused { brackets(x, y, w, h, 6, lift(accent, 0.3), 1) }
    bind ix = x + 12
    bind iw = w - 24 - right_pad
    clip_push(ix, y + 2, iw, h - 4)
    bind mono = kind == "address" || kind == "amount"
    bind f = sel(mono, F_MONO, F_UI)
    bind size = sel(mono, 22, 18)
    bind mid = y + round(h / 2)
    bind tw = 0
    if kind == "password" {
        bind tw = str_len(v) * 12
    } else {
        bind tw = round(t_w(f, size, v))
    }
    bind x0 = ix - max(0, tw - iw + 4)
    if st_get("tf_all", "") == id && focused && v != "" { fill(x0, mid - 11, tw, 22, C_SEL) }
    if v == "" {
        if placeholder != "" { t_mid(F_UI, 17, ix, mid, placeholder, C_TEXT_FAINT) }
    } else if kind == "password" {
        bind i = 0
        while i < str_len(v) {
            fill(x0 + i * 12 + 2, mid - 3, 7, 7, C_TEXT)
            bind i = i + 1
        }
    } else {
        t_mid(f, size, x0, mid, v, C_TEXT_STRONG)
    }
    if focused && u_blink() { fill(x0 + tw + 1, mid - 11, 2, 22, lift(accent, 0.2)) }
    clip_pop()
    return [changed, submitted, space, pasted]
}

# Field with a small-caps label 9 px above it.
fn labeled(id, x, y, w, h, text, placeholder, accent, kind) {
    label(x, y - 9, text, C_TEXT_DIM)
    return field(id, x, y, w, h, placeholder, accent, kind, 0)
}

# Square switch tile with a label; returns true when clicked.
fn toggle(x, y, on, text, col) {
    bind s = 22
    bind lw = round(t_w(F_UI, 17, text))
    bind hot = u_hover(x, y, s + 12 + lw, s)
    bind lc = C_LINE_SOFT
    if hot { bind lc = C_LINE }
    if on {
        bind lc = col
        cross(x + 2, y + 2, s - 4, s - 4, col, 3, 0.7)
    }
    outline(x, y, s, s, lc, 1)
    brackets(x, y, s, s, 5, sel(on, lift(col, 0.3), C_TEXT_FAINT), 1)
    t_mid(F_UI, 17, x + s + 12, y + s / 2, text, sel(on || hot, C_TEXT_STRONG, C_TEXT))
    return hot && u_pressed()
}

# Horizontal slider over 0..1; returns the new value while dragged, else -1.
fn slider(id, x, y, w, h, v, col) {
    bind hot = u_hover(x, y, w, h)
    if hot && u_pressed() { st_set("active", id) }
    bind out = 0 - 1
    bind shown = v
    if st_get("active", "") == id {
        bind nv = clampf((u_mx() - x) / w, 0, 1)
        if abs(nv - v) > 0.0001 { bind out = nv }
        bind shown = nv
    }
    fill(x, y, w, h, C_BG_DEEP)
    outline(x, y, w, h, sel(hot, C_LINE, C_LINE_SOFT), 1)
    bind fw = floor((w - 4) * shown)
    fill_a(x + 2, y + 2, fw, h - 4, col, 0.12)
    cross(x + 2, y + 2, fw, h - 4, col, 3, 0.55)
    bind i = 1
    while i < 10 {
        vline(x + floor(w * i / 10), y + h + 3, sel(i == 5, 6, 3), C_TEXT_FAINT, 1)
        bind i = i + 1
    }
    bind hx = x + 2 + fw
    fill(hx - 1, y - 4, 3, h + 8, C_TEXT_STRONG)
    brackets(hx - 5, y - 4, 11, h + 8, 3, lift(col, 0.3), 1)
    return out
}

# [-] value [+]; returns the new value when changed, else -1.
fn stepper(x, y, v, lo, hi, col) {
    bind h = 36
    bind out = 0 - 1
    bind k = 0
    while k < 2 {
        bind bx = sel(k == 0, x, x + h + 4 + 64 + 4)
        bind d = sel(k == 0, 0 - 1, 1)
        bind en = sel(k == 0, v > lo, v < hi)
        bind hot = u_hover(bx, y, h, h) && en
        if hot {
            fill_a(bx, y, h, h, col, 0.12)
            hatch(bx + 2, y + 2, h - 4, h - 4, col, true, 3, 0.25)
        }
        bind oc = C_LINE_SOFT
        if en { bind oc = sel(hot, col, C_LINE) }
        outline(bx, y, h, h, oc, 1)
        bind gc = sel(en, C_TEXT_STRONG, C_TEXT_FAINT)
        fill(bx + h / 2 - 6, y + h / 2 - 1, 12, 2, gc)
        if d > 0 { fill(bx + h / 2 - 1, y + h / 2 - 6, 2, 12, gc) }
        if hot && u_pressed() { bind out = clampf(v + d, lo, hi) }
        bind k = k + 1
    }
    bind vx = x + h + 4
    fill(vx, y, 64, h, C_BG_DEEP)
    outline(vx, y, 64, h, C_LINE_SOFT, 1)
    brackets(vx, y, 64, h, 4, col, 1)
    t_center(F_MONO, 28, vx, y, 64, h, num_str(v), C_TEXT_STRONG)
    return out
}

# Row of segmented choices; returns the clicked index or -1.
fn segmented(x, y, total_w, items, cur, col) {
    bind n = max(1, len(items))
    bind eq = floor((total_w - 6 * (n - 1)) / n)
    bind px = x
    bind out = 0 - 1
    bind i = 0
    while i < len(items) {
        bind it = str_upper(list_get(items, i))
        bind tw = t_w_sp(F_UI, 15, it, 1.4)
        bind w = sel(total_w > 0, eq, round(tw) + 24)
        bind on = i == cur
        bind hot = u_hover(px, y, w, 32)
        if on {
            fill_a(px, y, w, 32, col, 0.14)
            hatch(px + 2, y + 2, w - 4, 28, col, true, 3, 0.35)
        } else if hot {
            fill_a(px, y, w, 32, col, 0.06)
        }
        bind oc = C_LINE_SOFT
        if hot { bind oc = C_LINE }
        if on { bind oc = col }
        outline(px, y, w, 32, oc, 1)
        if on { brackets(px, y, w, 32, 4, lift(col, 0.3), 1) }
        t_text_sp(F_UI, 15, px + round((w - tw) / 2), y + 16 + t_cap(F_UI, 15) / 2, it, sel(on, C_TEXT_STRONG, C_TEXT_DIM), 1.4)
        if hot && u_pressed() { bind out = i }
        bind px = px + w + 6
        bind i = i + 1
    }
    return out
}

# Rough password strength 0..4.
fn pw_strength(pw) {
    bind n = str_len(pw)
    bind lower = false
    bind upper = false
    bind digit = false
    bind other = false
    bind i = 0
    while i < n {
        bind ch = substr(pw, i, 1)
        if str_find("abcdefghijklmnopqrstuvwxyz", ch) >= 0 {
            bind lower = true
        } else if str_find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", ch) >= 0 {
            bind upper = true
        } else if str_find(DIGITS, ch) >= 0 {
            bind digit = true
        } else {
            bind other = true
        }
        bind i = i + 1
    }
    bind classes = sel(lower, 1, 0) + sel(upper, 1, 0) + sel(digit, 1, 0) + sel(other, 1, 0)
    bind pool = 26
    if classes >= 2 { bind pool = 52 }
    if classes >= 3 { bind pool = 62 }
    if classes >= 4 { bind pool = 95 }
    bind bits = n * log(pool) / log(2)
    if n < 10 { return 0 }
    if bits < 50 { return 1 }
    if bits < 70 { return 2 }
    if bits < 90 { return 3 }
    return 4
}
fn pw_strength_name(s) { return list_get(["TOO SHORT", "WEAK", "FAIR", "STRONG", "EXCELLENT"], s) }

# Four-segment strength meter with a label on the right.
fn strength_meter(x, y, w, h, pw) {
    bind score = pw_strength(pw)
    bind col = list_get([C_METER_R, C_METER_R, C_METER_Y, C_METER_G, C_METER_G], score)
    bind lw = 96
    bind gap = 4
    bind sw = floor((w - lw - gap * 3) / 4)
    bind i = 0
    while i < 4 {
        bind sx = x + i * (sw + gap)
        bind lit = pw != "" && i < max(score, 1)
        if lit {
            fill_a(sx, y, sw, h, col, 0.2)
            cross(sx, y, sw, h, col, 3, 0.8)
        }
        outline(sx, y, sw, h, sel(lit, col, C_LINE_SOFT), 1)
        bind i = i + 1
    }
    if pw != "" { label(x + w - lw + 12, y + h / 2 + t_cap(F_UI, 13) / 2, pw_strength_name(score), col) }
}

# Bordered message box with an icon, a title and wrapped body; returns its height.
fn callout(x, y, w, col, icon, title, body) {
    bind lines = t_wrap(F_UI, 16, body, w - 64)
    bind h = 44 + len(lines) * 20
    if body == "" { bind h = 30 }
    fill_a(x, y, w, h, col, 0.06)
    outline(x, y, w, h, shade(col, 0.3), 1)
    fill(x, y, 3, h, col)
    hatch(x + 3, y, 8, h, col, true, 3, 0.4)
    icon_draw(icon, x + 22, y + 22, col)
    t_text(F_UI, 17, x + 50, y + 28, title, lift(col, 0.35))
    bind i = 0
    while i < len(lines) {
        t_text(F_UI, 16, x + 50, y + 50 + i * 20, list_get(lines, i), C_TEXT)
        bind i = i + 1
    }
    return h
}

# Big heading and dim subtitle; returns the y below.
fn heading(x, y, title, sub) {
    t_text_sp(F_HEAD, 24, x, y + 24, title, C_TEXT_STRONG, 1)
    if sub == "" { return y + 40 }
    t_text(F_UI, 17, x, y + 50, sub, C_TEXT_DIM)
    return y + 68
}

# Key/value row: label left, value right; returns the next y.
fn kv(x, y, w, k, v, vcol, mono) {
    label(x, y + 18, k, C_TEXT_DIM)
    if mono {
        t_right(F_MONO, 22, x + w, y + 19, v, vcol)
    } else {
        t_right(F_UI, 18, x + w, y + 18, v, vcol)
    }
    hline(x, y + 30, w, C_LINE_FAINT, 1)
    return y + 36
}

# Step indicator: numbered tiles joined by rules.
fn steps(x, y, names, cur, col) {
    bind px = x
    bind i = 0
    while i < len(names) {
        bind on = i == cur
        bind done = i < cur
        tile(px, y, 22, num_str(i + 1), sel(on || done, col, C_LINE), on)
        bind tc = C_TEXT_FAINT
        if done { bind tc = C_TEXT }
        if on { bind tc = C_TEXT_STRONG }
        bind lw = label(px + 30, y + 16, list_get(names, i), tc)
        bind px = px + 30 + lw + 14
        if i + 1 < len(names) {
            hline(px, y + 11, 28, sel(done, col, C_LINE_SOFT), 1)
            bind px = px + 42
        }
        bind i = i + 1
    }
    return px - x
}

← Back to index