L
ling-wallet
↓ 0 downloadsLing Wallet: a post-quantum privacy-coin wallet, full node and HyperTorus miner for LNG
Keywords wallet, cryptocurrency, lng, node, miner, ling
lingfu install ling-walletCode / 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
}