L
ling-miao
↓ 0 ダウンロードmiao: a Ling-native version control system — temples, souls, rites and talismans instead of repos, branches, commits and tags; adaptive whole/delta/chunk storage; SSH temple fairs for sharing; a LingStationX-themed GUI; ROYGBIV everywhere
キーワード vcs, version-control, git, miao, ling
lingfu install ling-miaoコード / ファイル (268)
ling/core.ling
# core.ling - input frame, hit-testing, colour and text helpers, the widget toolkit.
# All shared state lives in the native store (st_get / st_set) so plain functions can reach it.
# Colours are packed numbers r*65536 + g*256 + b. Every widget is drawn each frame and
# returns its state; `blk` > 0 blocks input to everything below an overlay.
# ── frame begin / end ────────────────────────────────────────────────────────
fn u_begin() {
bind mx = mouse_x()
bind my = mouse_y()
bind dn = 0.0
if mouse_down() { bind dn = 1.0 }
bind rdn = 0.0
if mouse_down_right() { bind rdn = 1.0 }
bind mdn = 0.0
if mouse_down_middle() { bind mdn = 1.0 }
bind scr = mouse_scroll()
# scripted input for screenshots / UI tests (--drive "frame,x,y,down;...")
if st_get("fake_on", 0.0) > 0.5 {
bind fr = st_get("frame", 0.0)
while st_get("dv_i", 0.0) < st_llen("dv_f") && st_lget("dv_f", st_get("dv_i", 0.0)) <= fr {
bind k = st_get("dv_i", 0.0)
st_set("fake_x", st_lget("dv_x", k))
st_set("fake_y", st_lget("dv_y", k))
st_set("fake_d", st_lget("dv_d", k))
st_set("dv_i", k + 1.0)
}
bind mx = st_get("fake_x", 0.0)
bind my = st_get("fake_y", 0.0)
bind dn = st_get("fake_d", 0.0)
bind scr = 0.0
}
bind prev = st_get("md", 0.0)
bind rprev = st_get("mrd", 0.0)
bind mprev = st_get("mmd", 0.0)
bind now = time_now()
st_set("wheel_used", 0.0)
st_set("bar_busy", 0.0)
st_set("mp", 0.0)
st_set("mr", 0.0)
st_set("mrp", 0.0)
st_set("mmp", 0.0)
st_set("dbl", 0.0)
if dn > 0.5 && prev < 0.5 {
st_set("mp", 1.0)
st_set("press_x", mx)
st_set("press_y", my)
bind lt = st_get("lastclick_t", 0.0 - 10.0)
bind lx = st_get("lastclick_x", 0.0)
bind ly = st_get("lastclick_y", 0.0)
if now - lt < 0.4 && abs(mx - lx) < 5.0 && abs(my - ly) < 5.0 {
st_set("dbl", 1.0)
st_set("lastclick_t", 0.0 - 10.0)
} else {
st_set("lastclick_t", now)
st_set("lastclick_x", mx)
st_set("lastclick_y", my)
}
}
if dn < 0.5 && prev > 0.5 { st_set("mr", 1.0) }
if rdn > 0.5 && rprev < 0.5 { st_set("mrp", 1.0) }
if mdn > 0.5 && mprev < 0.5 { st_set("mmp", 1.0) }
st_set("pmx", st_get("mx", mx))
st_set("pmy", st_get("my", my))
st_set("md", dn)
st_set("mrd", rdn)
st_set("mmd", mdn)
st_set("mx", mx)
st_set("my", my)
st_set("scroll", scr)
bind sh = 0.0
if key_down("lshift") || key_down("rshift") { bind sh = 1.0 }
bind ct = 0.0
if key_down("lctrl") || key_down("rctrl") { bind ct = 1.0 }
bind al = 0.0
if key_down("lalt") || key_down("ralt") { bind al = 1.0 }
st_set("shift", sh)
st_set("ctrl", ct)
st_set("alt", al)
st_set("dt", clampf(now - st_get("now", now), 0.001, 0.05))
st_set("now", now)
st_set("tip", "")
st_set("cursor_hand", 0.0)
}
fn u_end() {
if st_get("md", 0.0) < 0.5 { st_set("active", "") }
}
fn u_mx() { return st_get("mx", 0.0) }
fn u_my() { return st_get("my", 0.0) }
fn u_dx() { return st_get("mx", 0.0) - st_get("pmx", 0.0) }
fn u_dy() { return st_get("my", 0.0) - st_get("pmy", 0.0) }
fn u_down() { return st_get("md", 0.0) > 0.5 }
fn u_rdown() { return st_get("mrd", 0.0) > 0.5 }
fn u_mdown() { return st_get("mmd", 0.0) > 0.5 }
fn u_pressed() { return st_get("mp", 0.0) > 0.5 }
fn u_released() { return st_get("mr", 0.0) > 0.5 }
fn u_rpressed() { return st_get("mrp", 0.0) > 0.5 }
fn u_mpressed() { return st_get("mmp", 0.0) > 0.5 }
fn u_double() { return st_get("dbl", 0.0) > 0.5 }
fn u_shift() { return st_get("shift", 0.0) > 0.5 }
fn u_ctrl() { return st_get("ctrl", 0.0) > 0.5 }
fn u_alt() { return st_get("alt", 0.0) > 0.5 }
fn u_scroll() { return st_get("scroll", 0.0) }
fn u_dt() { return st_get("dt", 0.016) }
fn u_now() { return st_get("now", 0.0) }
# pointer inside a rect, input not blocked by an overlay, and not scrolled out of a clip region
fn u_hot(x, y, w, h) {
if st_get("blk", 0.0) > 0.5 { return false }
bind mx = st_get("mx", 0.0)
bind my = st_get("my", 0.0)
if mx < x || my < y || mx >= x + w || my >= y + h { return false }
if st_get("clip_on", 0.0) > 0.5 {
if mx < st_get("clip_x0", 0.0) || mx >= st_get("clip_x1", 0.0) || my < st_get("clip_y0", 0.0) || my >= st_get("clip_y1", 0.0) { return false }
}
return true
}
fn u_in(x, y, w, h) {
bind mx = st_get("mx", 0.0)
bind my = st_get("my", 0.0)
return mx >= x && my >= y && mx < x + w && my < y + h
}
fn u_clip(x, y, w, h) {
px_clip(x, y, w, h)
st_set("clip_on", 1.0)
st_set("clip_x0", x)
st_set("clip_y0", y)
st_set("clip_x1", x + w)
st_set("clip_y1", y + h)
}
fn u_unclip() {
px_unclip()
st_set("clip_on", 0.0)
}
fn u_active() { return st_get("active", "") }
fn u_set_active(id) { st_set("active", id) }
fn u_tip(text) {
st_set("tip", text)
st_set("tip_t", st_get("now", 0.0))
}
# ── small helpers ────────────────────────────────────────────────────────────
fn sel(cond, a, b) {
if cond { return a }
return b
}
fn clampf(v, lo, hi) {
if v < lo { return lo }
if v > hi { return hi }
return v
}
fn lerpf(a, b, t) { return a + (b - a) * t }
fn fmt1(v) { return num_str(round(v * 10.0) / 10.0) }
fn fmt2(v) { return num_str(round(v * 100.0) / 100.0) }
fn fmt3(v) { return num_str(round(v * 1000.0) / 1000.0) }
fn pad2(v) {
if v < 10.0 { return "0" + num_str(floor(v)) }
return num_str(floor(v))
}
fn bool_num(b) {
if b { return 1.0 }
return 0.0
}
# mm:ss.ff time readout
fn fmt_time(secs) {
bind m = floor(secs / 60.0)
bind s = secs - m * 60.0
bind f = floor((s - floor(s)) * 100.0)
return pad2(m) + ":" + pad2(floor(s)) + "." + pad2(f)
}
# ── colour helpers ───────────────────────────────────────────────────────────
fn c_r(c) { return floor(c / 65536.0) }
fn c_g(c) { return floor(c / 256.0) % 256.0 }
fn c_b(c) { return c % 256.0 }
fn c_rgb(r, g, b) { return floor(clampf(r, 0.0, 255.0)) * 65536.0 + floor(clampf(g, 0.0, 255.0)) * 256.0 + floor(clampf(b, 0.0, 255.0)) }
fn c_mix(a, b, t) {
return c_rgb(c_r(a) + (c_r(b) - c_r(a)) * t, c_g(a) + (c_g(b) - c_g(a)) * t, c_b(a) + (c_b(b) - c_b(a)) * t)
}
fn c_tint(c, k) {
return c_rgb(min(255.0, c_r(c) * k), min(255.0, c_g(c) * k), min(255.0, c_b(c) * k))
}
# "r,g,b" text <-> packed
fn c_parse(s) {
bind p = str_split(s, ",")
if len(p) < 3.0 { return to_number(s) }
return c_rgb(to_number(list_get(p, 0)), to_number(list_get(p, 1)), to_number(list_get(p, 2)))
}
fn c_text(c) { return num_str(c_r(c)) + "," + num_str(c_g(c)) + "," + num_str(c_b(c)) }
# ── text ─────────────────────────────────────────────────────────────────────
# the text size setting (the A- / % / A+ buttons at the top right, Ctrl+Shift+= / Ctrl+Shift+- / Ctrl+0): every text drawn
# through these helpers is scaled by it, grown around its own middle so it stays where the layout put it
fn text_k() {
bind k = st_get("font_scale", 1.0)
if k < 0.69 || k > 1.61 { return 1.0 }
return k
}
fn text_size_set(k) {
bind v = clampf(round(k * 20.0) / 20.0, 0.7, 1.6)
st_set("font_scale", v)
st_set("status", "Text size " + num_str(round(v * 100.0)) + " % (Ctrl+Shift+= / Ctrl+Shift+-, Ctrl+0 resets)")
settings_save()
}
fn g_text(x, y, size, c, s) {
bind k = text_k()
px_text(st_get("font", 0.0), x, y - size * (k - 1.0) * 0.5, size * k, c, s)
}
fn g_text_w(size, s) { return font_width(st_get("font", 0.0), size * text_k(), s) }
fn g_text_c(cx, y, size, c, s) { g_text(cx - g_text_w(size, s) * 0.5, y, size, c, s) }
fn g_text_r(rx, y, size, c, s) { g_text(rx - g_text_w(size, s), y, size, c, s) }
fn g_text_fit(x, y, maxw, size, c, s) {
g_text(x, y, size, c, px_fit(st_get("font", 0.0), size * text_k(), maxw, s))
}
fn g_fit(size, maxw, s) { return px_fit(st_get("font", 0.0), size, maxw, s) }
# word-wrapped text; returns the height used
fn g_text_wrap(x, y, maxw, size, c, s) {
bind f = st_get("font", 0.0)
bind ks = size * text_k()
bind words = str_split(s, " ")
bind line = ""
bind ly = y
bind lh = ks + 4.0
bind i = 0.0
while i < len(words) {
bind wd = list_get(words, i)
bind trial = sel(line == "", wd, line + " " + wd)
if line != "" && font_width(f, ks, trial) > maxw {
g_text(x, ly, size, c, px_fit(f, ks, maxw, line))
bind ly = ly + lh
bind line = wd
} else {
bind line = trial
}
bind i = i + 1.0
}
if line != "" {
g_text(x, ly, size, c, px_fit(f, ks, maxw, line))
bind ly = ly + lh
}
return ly - y
}
fn g_text_outlined(x, y, size, c, s) {
g_text(x - 1.0, y, size, 0.0, s)
g_text(x + 1.0, y, size, 0.0, s)
g_text(x, y - 1.0, size, 0.0, s)
g_text(x, y + 1.0, size, 0.0, s)
g_text(x, y, size, c, s)
}
# ── shapes ───────────────────────────────────────────────────────────────────
fn g_rect(x, y, w, h, c) { px_rect(x, y, w, h, c) }
fn g_rect_a(x, y, w, h, c, a) { px_rect(x, y, w, h, c, a) }
fn g_frame(x, y, w, h, c) { px_outline(x, y, w, h, c) }
fn g_frame_a(x, y, w, h, c, a) { px_outline(x, y, w, h, c, a) }
fn g_rrect(x, y, w, h, r, c) { px_rrect(x, y, w, h, r, c) }
fn g_rrect_a(x, y, w, h, r, c, a) { px_rrect(x, y, w, h, r, c, a) }
fn g_rframe(x, y, w, h, r, c) { px_rrect_outline(x, y, w, h, r, c) }
fn g_line(x0, y0, x1, y1, c) { px_line(x0, y0, x1, y1, c) }
fn g_line_a(x0, y0, x1, y1, c, a) { px_line(x0, y0, x1, y1, c, a) }
fn g_disc(cx, cy, r, c) { px_disc(cx, cy, r, c) }
fn g_disc_a(cx, cy, r, c, a) { px_disc(cx, cy, r, c, a) }
fn g_ring(cx, cy, r, c) { px_ring(cx, cy, r, c) }
fn g_hsep(x, y, w) { g_rect(x, y, w, 1.0, C_LINE) }
fn g_vsep(x, y, h) { g_rect(x, y, 1.0, h, C_LINE) }
fn u_hsep(x, y, w) { g_hsep(x, y, w) }
fn u_vsep(x, y, h) { g_vsep(x, y, h) }
# filled triangle from horizontal spans (works on every runtime: only px_hline)
fn g_tri_a(x0, y0, x1, y1, x2, y2, c, a) {
bind ay = y0
bind by = y1
bind cy = y2
bind ax = x0
bind bx = x1
bind cx = x2
if by < ay { bind t = ay bind ay = by bind by = t bind t = ax bind ax = bx bind bx = t }
if cy < ay { bind t = ay bind ay = cy bind cy = t bind t = ax bind ax = cx bind cx = t }
if cy < by { bind t = by bind by = cy bind cy = t bind t = bx bind bx = cx bind cx = t }
# degenerate projections (a vertex at the near plane) give coordinates in the 1e15 range,
# where y + 1.0 == y and this loop never ends: skip anything off the screen by miles
if !(ay > 0.0 - 100000.0 && cy < 100000.0 && ax > 0.0 - 100000.0 && ax < 100000.0 && bx > 0.0 - 100000.0 && bx < 100000.0 && cx > 0.0 - 100000.0 && cx < 100000.0) { return 0.0 }
bind y = floor(ay)
bind yend = ceil(cy)
if yend - y > 4000.0 { return 0.0 }
bind rows = 0.0
while y <= yend {
bind rows = rows + 1.0
if rows > 5000.0 { return 0.0 }
bind sy = y + 0.5
if sy >= ay && sy <= cy {
bind xa = ax
if cy - ay > 0.0001 { bind xa = ax + (cx - ax) * (sy - ay) / (cy - ay) }
bind xb = ax
if sy < by {
if by - ay > 0.0001 { bind xb = ax + (bx - ax) * (sy - ay) / (by - ay) }
} else {
bind xb = bx
if cy - by > 0.0001 { bind xb = bx + (cx - bx) * (sy - by) / (cy - by) }
}
bind lo = min(xa, xb)
bind hi = max(xa, xb)
px_hline(round(lo), y, max(1.0, round(hi) - round(lo)), c, a)
}
bind y = y + 1.0
}
return 0.0
}
fn g_tri(x0, y0, x1, y1, x2, y2, c) { g_tri_a(x0, y0, x1, y1, x2, y2, c, 1.0) }
# diamond (keyframes)
fn g_diamond(cx, cy, r, c) {
g_tri(cx - r, cy, cx, cy - r, cx + r, cy, c)
g_tri(cx - r, cy, cx, cy + r, cx + r, cy, c)
}
# ── widgets ──────────────────────────────────────────────────────────────────
# push button, click = release inside. fill > 0 picks the key colour (blue, or red for a red fill)
fn u_button(id, x, y, w, h, label, fill) {
bind hot = u_hot(x, y, w, h)
bind act = u_active() == id
if hot && u_pressed() { u_set_active(id) }
bind col = ds_col("ink")
if fill > 0.5 { bind col = sel(c_r(fill) > c_b(fill) + 30.0, ds_col("asm"), ds_col("call")) }
px_rect(x, y, w, h, c_mix(C_BG_PANEL, 0.0, 0.35))
if fill > 0.5 { ds_hatch(x + 1.0, y + 1.0, w - 2.0, h - 2.0, col, 45.0, 5.0, 0.22) }
if hot { ds_hatch(x + 1.0, y + 1.0, w - 2.0, h - 2.0, col, 45.0, 4.0, 0.3) }
if act && hot { px_rect(x, y, w, h, col, 0.25) }
px_outline(x, y, w, h, col, sel(hot || fill > 0.5, 0.95, 0.6))
if hot || fill > 0.5 { ds_brackets(x, y, w, h, min(5.0, h * 0.3), col) }
g_text_c(x + w * 0.5, y + (h - 12.0) * 0.5 - 1.0, 12.0, C_TEXT_STRONG, g_fit(12.0, w - 8.0, label))
if hot { st_set("cursor_hand", 1.0) }
return act && hot && u_released()
}
# button with a vector icon; on = toggled look
fn u_icon_button(id, x, y, w, h, icon, size, tint, on) {
bind hot = u_hot(x, y, w, h)
bind act = u_active() == id
if hot && u_pressed() { u_set_active(id) }
bind bg = C_BTN
if on { bind bg = C_SEL }
if hot { bind bg = c_tint(bg, 1.15) }
if act && hot { bind bg = c_tint(bg, 0.85) }
g_rrect(x, y, w, h, 3.0, bg)
icon_draw(icon, x + w * 0.5, y + h * 0.5, size, tint)
return act && hot && u_released()
}
# flat chip (toggle look)
fn u_chip(id, x, y, w, h, label, on, col) {
bind hot = u_hot(x, y, w, h)
bind act = u_active() == id
if hot && u_pressed() { u_set_active(id) }
px_rect(x, y, w, h, c_mix(C_BG_PANEL, 0.0, 0.3))
if on { ds_cross(x + 1.0, y + 1.0, w - 2.0, h - 2.0, col, 3.0, 0.55) }
else if hot { ds_hatch(x + 1.0, y + 1.0, w - 2.0, h - 2.0, col, 45.0, 4.0, 0.3) }
px_outline(x, y, w, h, col, sel(on || hot, 0.95, 0.55))
bind tc = C_TEXT
if on { bind tc = C_WHITE }
g_text_c(x + w * 0.5, y + (h - 11.0) * 0.5 - 1.0, 11.0, tc, label)
return act && hot && u_released()
}
fn u_check(id, x, y, label, on) {
bind w = 20.0 + g_text_w(12.0, label)
bind hot = u_hot(x, y, w, 16.0)
bind act = u_active() == id
if hot && u_pressed() { u_set_active(id) }
bind col = sel(on, ds_col("ctl"), ds_col("ink"))
px_outline(x, y + 1.0, 14.0, 14.0, col, sel(on || hot, 0.95, 0.6))
ds_brackets(x, y + 1.0, 14.0, 14.0, 4.0, col)
if on { ds_cross(x + 2.0, y + 3.0, 10.0, 10.0, col, 3.0, 0.75) }
g_text(x + 20.0, y + 1.0, 12.0, C_TEXT, label)
return act && hot && u_released()
}
# selectable tab. returns true on click
fn u_tab(id, x, y, w, h, label, on, col) {
bind hot = u_hot(x, y, w, h)
bind act = u_active() == id
if hot && u_pressed() { u_set_active(id) }
bind kc = sel(col > 0.5, col, ds_col("call"))
if on {
px_rect(x, y, w, h, c_mix(C_BG_PANEL, 0.0, 0.3))
ds_hatch(x + 1.0, y + 1.0, w - 2.0, h - 2.0, kc, 45.0, 5.0, 0.2)
px_outline(x, y, w, h, kc, 0.9)
ds_brackets(x, y, w, h, 5.0, kc)
} else if hot {
px_outline(x, y, w, h, ds_col("ink"), 0.7)
}
bind tc = C_TEXT
if on { bind tc = C_TEXT_STRONG }
g_text_c(x + w * 0.5, y + (h - 12.0) * 0.5 - 1.0, 12.0, tc, label)
return act && hot && u_released()
}
# horizontal slider. returns new value or -999 when unchanged
fn u_hslider(id, x, y, w, h, v, lo, hi, col) {
bind hot = u_hot(x, y, w, h)
if hot && u_pressed() {
u_set_active(id)
st_set("sl_x0", u_mx())
st_set("sl_v0", v)
}
bind out = 0.0 - 999.0
if u_active() == id && u_down() {
bind t = clampf((u_mx() - x - 4.0) / (w - 8.0), 0.0, 1.0)
if u_shift() {
bind t = clampf((st_get("sl_v0", v) - lo) / (hi - lo) + (u_mx() - st_get("sl_x0", u_mx())) / (w * 8.0), 0.0, 1.0)
}
bind out = lo + t * (hi - lo)
}
if hot && u_scroll() != 0.0 && st_get("wheel_used", 0.0) < 0.5 && st_get("blk", 0.0) < 0.5 {
bind out = clampf(v + u_scroll() * (hi - lo) * 0.02, lo, hi)
st_set("wheel_used", 1.0)
}
bind cur = sel(out > 0.0 - 900.0, out, v)
bind t = clampf((cur - lo) / (hi - lo), 0.0, 1.0)
bind ty = y + h * 0.5
g_rrect(x, ty - 2.0, w, 4.0, 2.0, C_BTN_ACT)
g_rrect_a(x, ty - 2.0, max(4.0, w * t), 4.0, 2.0, col, sel(hot || u_active() == id, 0.9, 0.55))
g_disc(x + 4.0 + (w - 8.0) * t, ty, sel(hot || u_active() == id, 6.0, 5.0), c_tint(col, 1.25))
return out
}
# draggable number field (drag horizontally, Shift = fine, wheel, double-click to type). returns new value or -999
fn u_dragval(id, x, y, w, h, v, lo, hi, speed, label) {
bind hot = u_hot(x, y, w, h)
bind out = 0.0 - 999.0
bind editing = st_get("focus", "") == id
if editing {
bind t = u_textfield(id, x, y, w, h, st_get("dv_text", ""))
st_set("dv_text", t)
if key_pressed("enter") || (u_pressed() && !hot) {
bind nv = to_number(str_trim(t))
if str_trim(t) != "" { bind out = clampf(nv, lo, hi) }
if st_get("focus", "") == id { st_set("focus", "") }
}
if key_pressed("escape") { st_set("focus", "") }
return out
}
if hot && u_double() && st_get("blk", 0.0) < 0.5 {
st_set("focus", id)
st_set("dv_text", num_str(round(v * 1000.0) / 1000.0))
text_set(st_get("dv_text", ""))
return out
}
if hot && u_pressed() {
u_set_active(id)
st_set("ddv_x", u_mx())
st_set("ddv_v", v)
}
if u_active() == id && u_down() {
bind k = speed
if u_shift() { bind k = speed * 0.1 }
bind nv = clampf(st_get("ddv_v", v) + (u_mx() - st_get("ddv_x", 0.0)) * k, lo, hi)
if abs(nv - v) > 0.0000001 { bind out = nv }
}
if hot && u_scroll() != 0.0 && st_get("wheel_used", 0.0) < 0.5 {
bind out = clampf(v + u_scroll() * speed * 4.0, lo, hi)
st_set("wheel_used", 1.0)
}
g_rrect(x, y, w, h, 3.0, sel(hot || u_active() == id, C_BTN_HOV, C_BTN))
g_text_c(x + w * 0.5, y + (h - 12.0) * 0.5 - 1.0, 12.0, C_TEXT_STRONG, g_fit(12.0, w - 6.0, label))
return out
}
fn u_has(v) { return v > 0.0 - 900.0 }
# text input field. returns the (possibly edited) value
fn u_textfield(id, x, y, w, h, value) {
bind focused = st_get("focus", "") == id
bind hot = u_hot(x, y, w, h)
if focused && u_pressed() && !hot && st_get("blk", 0.0) < 0.5 {
st_set("focus", "")
bind focused = false
}
if hot && u_pressed() && !focused {
st_set("focus", id)
text_set(value)
bind focused = true
}
bind v = value
if focused { bind v = text_poll() }
g_rrect(x, y, w, h, 3.0, C_BG_DEEP)
g_rframe(x, y, w, h, 3.0, sel(focused, C_BLUE, C_LINE))
bind shown = v
bind tw = g_text_w(12.0, shown)
bind tx = x + 6.0
if focused && tw > w - 12.0 { bind tx = x + w - 6.0 - tw }
ds_clip_to(x + 2.0, y, w - 4.0, h)
g_text(tx, y + (h - 12.0) * 0.5 - 1.0, 12.0, C_TEXT_STRONG, shown)
ds_clip_end()
if focused {
bind cx = min(tx + tw, x + w - 6.0)
if floor(st_get("now", 0.0) * 2.0) % 2.0 < 0.5 { g_rect(cx, y + 4.0, 1.0, h - 8.0, C_TEXT_STRONG) }
}
return v
}
# vertical scroll region: wheel + bar. returns the new offset
fn u_scroll_y(id, x, y, w, h, content, off) {
bind maxoff = max(0.0, content - h)
bind o = clampf(off, 0.0, maxoff)
if maxoff > 0.0 && u_hot(x, y, w, h) && u_scroll() != 0.0 && !u_ctrl() && st_get("wheel_used", 0.0) < 0.5 {
bind o = clampf(o - u_scroll() * 24.0, 0.0, maxoff)
st_set("wheel_used", 1.0)
}
if maxoff > 0.0 {
bind bx = x + w - 10.0
bind th = max(28.0, h * h / content)
bind ty = y + (h - th) * (o / maxoff)
bind hot = u_hot(bx - 3.0, y, 14.0, h)
if hot && u_pressed() {
u_set_active(id)
st_set("sb_grab", sel(u_my() >= ty && u_my() <= ty + th, u_my() - ty, th * 0.5))
}
if u_active() == id && u_down() {
bind o = clampf((u_my() - st_get("sb_grab", 0.0) - y) / (h - th), 0.0, 1.0) * maxoff
bind ty = y + (h - th) * (o / maxoff)
st_set("bar_busy", 1.0)
}
if hot { st_set("bar_busy", 1.0) }
g_rect_a(bx, y + 1.0, 8.0, h - 2.0, 0.0, 0.35)
g_frame(bx, y + 1.0, 8.0, h - 2.0, C_LINE_SOFT)
g_rrect(bx + 1.0, ty + 1.0, 6.0, th - 2.0, 3.0, sel(hot || u_active() == id, C_TEXT_DIM, C_BTN_HOV))
}
return o
}
# horizontal bar that only draws and reports
fn u_bar_h(id, x, y, w, view, content, off) {
bind maxoff = max(0.0, content - view)
if maxoff <= 0.0 {
g_rect_a(x, y, w, 10.0, 0.0, 0.25)
g_frame(x, y, w, 10.0, C_LINE_SOFT)
return 0.0
}
bind o = clampf(off, 0.0, maxoff)
bind th = max(28.0, w * view / content)
bind span = max(1.0, w - th)
bind tx = x + span * (o / maxoff)
bind hot = u_hot(x, y - 2.0, w, 14.0)
if hot && u_pressed() {
u_set_active(id)
st_set("sb_grab", sel(u_mx() >= tx && u_mx() <= tx + th, u_mx() - tx, th * 0.5))
}
if u_active() == id && u_down() {
bind o = clampf((u_mx() - st_get("sb_grab", 0.0) - x) / span, 0.0, 1.0) * maxoff
bind tx = x + span * (o / maxoff)
st_set("bar_busy", 1.0)
}
if hot { st_set("bar_busy", 1.0) }
g_rect_a(x, y, w, 10.0, 0.0, 0.45)
g_frame(x, y, w, 10.0, C_LINE_SOFT)
g_rrect(tx + 1.0, y + 1.0, th - 2.0, 8.0, 3.0, sel(hot || u_active() == id, C_TEXT_DIM, C_BTN_HOV))
return o
}
# dropdown. returns the newly chosen index, or -1
fn u_combo(id, x, y, w, h, names, cur) {
bind out = 0.0 - 1.0
bind res = st_get("cb_res", "")
if res != "" {
bind parts = str_split(res, ":")
if list_get(parts, 0) == id {
bind out = to_number(list_get(parts, 1))
st_set("cb_res", "")
}
}
bind open = st_get("cb_open", "") == id
bind hot = u_hot(x, y, w, h)
g_rrect(x, y, w, h, 3.0, sel(hot || open, C_BTN_HOV, C_BTN))
if len(names) > 0.5 { g_text_fit(x + 8.0, y + (h - 12.0) * 0.5 - 1.0, w - 26.0, 12.0, C_TEXT_STRONG, list_get(names, clampf(cur, 0.0, len(names) - 1.0))) }
icon_draw("chev_d", x + w - 12.0, y + h * 0.5, 8.0, C_TEXT_DIM)
if hot && u_pressed() && len(names) > 0.5 {
st_set("cb_open", id)
st_set("cb_x", x)
st_set("cb_top", y)
st_set("cb_hgt", h)
st_set("cb_off", 0.0)
st_set("cb_w", w)
st_set("cb_items", names)
st_set("cb_cur", cur)
st_set("cb_eat", 1.0)
}
return out
}
# the open dropdown list; call after everything else each frame
fn combo_overlay() {
bind id = st_get("cb_open", "")
if id == "" { return 0.0 }
st_set("blk", 0.0)
bind names = st_get("cb_items", [])
bind n = len(names)
bind cur = st_get("cb_cur", 0.0 - 1.0)
bind ih = 22.0
bind H = get_height()
bind W = get_width()
bind top = st_get("cb_top", 0.0)
bind chh = st_get("cb_hgt", 24.0)
bind below = H - (top + chh + 2.0) - 8.0
bind above = top - 10.0
bind rows = min(n, max(1.0, floor((below - 8.0) / ih)))
bind up = false
if rows < n && above > below {
bind rows = min(n, floor((above - 8.0) / ih))
bind up = true
}
bind rows = max(1.0, rows)
bind h = rows * ih + 8.0
bind w = st_get("cb_w", 120.0)
bind i = 0.0
while i < n {
bind w = max(w, g_text_w(12.0, list_get(names, i)) + 44.0)
bind i = i + 1.0
}
bind w = min(w, W - 16.0)
bind x = min(st_get("cb_x", 0.0), W - w - 6.0)
bind y = sel(up, top - 2.0 - h, top + chh + 2.0)
bind inside = u_in(x, y, w, h)
bind off = clampf(st_get("cb_off", 0.0), 0.0, max(0.0, n - rows))
if inside && u_scroll() != 0.0 {
bind off = clampf(off - u_scroll() * 3.0, 0.0, max(0.0, n - rows))
st_set("wheel_used", 1.0)
}
st_set("cb_off", off)
px_rrect(x + 2.0, y + 3.0, w, h, 6.0, 0.0, 0.35)
g_rrect(x, y, w, h, 5.0, C_POPUP)
g_rframe(x, y, w, h, 5.0, C_POPUP_LINE)
u_clip(x, y + 2.0, w, h - 4.0)
bind pick = 0.0 - 1.0
bind r = 0.0
while r < rows {
bind idx = floor(off) + r
if idx < n {
bind ry = y + 4.0 + r * ih
bind hot = u_hot(x + 2.0, ry, w - 4.0, ih)
if hot { g_rrect_a(x + 3.0, ry, w - 6.0, ih, 3.0, C_BLUE, 0.30) }
if idx == cur { g_rrect(x + 6.0, ry + 6.0, 10.0, 10.0, 2.0, C_BLUE) }
g_text_fit(x + 24.0, ry + 4.0, w - 32.0, 12.0, sel(idx == cur, C_TEXT_STRONG, C_TEXT), list_get(names, idx))
if hot && u_pressed() && st_get("cb_eat", 0.0) < 0.5 { bind pick = idx }
}
bind r = r + 1.0
}
u_unclip()
if n > rows {
bind thumb = max(24.0, (h - 8.0) * rows / n)
bind span = h - 8.0 - thumb
bind ty = y + 4.0 + sel(n - rows > 0.0, off / (n - rows), 0.0) * span
g_rrect_a(x + w - 8.0, y + 4.0, 6.0, h - 8.0, 3.0, 0.0, 0.35)
g_rrect(x + w - 8.0, ty, 6.0, thumb, 3.0, C_BTN_HOV)
}
if pick >= 0.0 {
st_set("cb_res", id + ":" + num_str(pick))
st_set("cb_open", "")
} else if (u_pressed() && st_get("cb_eat", 0.0) < 0.5 && !inside) || key_pressed("escape") { st_set("cb_open", "") }
st_set("cb_eat", 0.0)
}
# a labelled row header inside panels
fn u_section(x, y, w, label, col) {
ds_word(x, y, 9.0, label, col, false)
bind lw = ds_word_w(9.0, label)
px_hline(x + lw + 8.0, y + 4.0, max(0.0, w - lw - 8.0), ds_col("ink"), 0.6)
}
# key/value readout line
fn u_kv(x, y, w, k, v) {
g_text(x, y, 11.0, C_TEXT_DIM, k)
g_text_r(x + w, y, 11.0, C_TEXT, g_fit(11.0, w * 0.6, v))
}