灵符lingfu

lingdev

↓ 0 downloads

LingDev: a Godot / Unity-style game editor written in Ling. 2-D and 3-D scenes, Flash-style timeline and cutscenes, HUDs over 3-D, Ling scripting, audio / music / MIDI, builds for Windows, Linux, macOS, web, Raspberry Pi and LingOS. Ships with Ling Agent (an N64-style spy FPS), Ling Breaker (a 3-D breakout) and Moko's Picture Book (a kawaii storybook platformer).

Windows · x86-64Linux · x86-64LingOS · x86-64LingOS · Pi 5 ARM64Web · WASM
Keywords game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdev
LicenseApache-2.0 OR MIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (136)

ling/hier.ling
# hier.ling - the scene tree: rows with icons, visibility / lock toggles, key markers,
# click to select (Ctrl adds), double-click to rename, right-click for the node menu,
# drag a row onto another to parent it (top / bottom edge = reorder before / after).

fn hier_visible_rows() {
    st_lclear("hv_id")
    st_lclear("hv_depth")
    bind q = st_lower_safe(st_get("hier_q", ""))
    hier_collect(0.0, 0.0, q)
}
fn st_lower_safe(s) { return str_lower(s) }
fn hier_collect(parent_id, depth, q) {
    bind kids = nd_children_ids(parent_id)
    bind k = 0.0
    while k < len(kids) {
        bind id = list_get(kids, k)
        bind i = nd_find(id)
        if i >= 0.0 {
            bind show = q == "" || str_find(str_lower(nd_get(i, "name")), q) >= 0.0 || str_find(nd_get(i, "kind"), q) >= 0.0
            if show || q != "" {
                if show { st_lpush("hv_id", id)  st_lpush("hv_depth", depth) }
            }
            if st_get("exp:" + num_str(id), 1.0) > 0.5 || q != "" { hier_collect(id, depth + 1.0, q) }
        }
        bind k = k + 1.0
    }
}

fn hier_panel(x, y, w, h) {
    g_rect(x, y, w, h, C_BG_SIDE)
    u_vsep(x + w - 1.0, y, h)
    ds_word(x + 10.0, y + 8.0, 10.0, "HIERARCHY", ds_col("call"), true)
    bind hx = x + w - 30.0
    if u_hot(hx, y + 5.0, 22.0, 20.0) { u_tip("Add a node (Shift+A)") }
    if u_icon_button("hier_add", hx, y + 5.0, 22.0, 20.0, "plus", 11.0, C_TEXT_STRONG, false) { modal_open("addnode") }
    bind q = u_textfield("hier_q", x + 118.0, y + 4.0, max(40.0, w - 156.0), 22.0, st_get("hier_q", ""))
    st_set("hier_q", q)
    if q == "" && st_get("focus", "") != "hier_q" { g_text(x + 126.0, y + 9.0, 11.0, C_TEXT_DIM, "search") }
    u_hsep(x + 6.0, y + 31.0, w - 12.0)
    bind top = y + 36.0
    bind vh = h - 40.0
    hier_visible_rows()
    bind n = st_llen("hv_id")
    bind rh = 22.0
    bind total = n * rh + 8.0
    bind off = u_scroll_y("hier_sb", x, top, w - 1.0, vh, total, st_get("hier_scroll", 0.0))
    st_set("hier_scroll", off)
    u_clip(x, top, w - 1.0, vh)
    bind drag_id = st_get("hdrag_id", 0.0)
    bind drop_id = 0.0
    bind drop_mode = ""
    bind r = 0.0
    while r < n {
        bind id = st_lget("hv_id", r)
        bind depth = st_lget("hv_depth", r)
        bind i = nd_find(id)
        bind ry = top + 4.0 + r * rh - off
        if i >= 0.0 && ry + rh >= top && ry <= top + vh {
            bind kind = nd_get(i, "kind")
            bind selected = sel_has(id)
            bind hot = u_hot(x, ry, w - 12.0, rh)
            if selected { g_rect(x, ry, w - 1.0, rh, C_SEL_ROW) }
            else if hot { g_rect(x, ry, w - 1.0, rh, C_HOVER_ROW) }
            bind ix = x + 8.0 + depth * 14.0
            # expander
            bind kids = nd_children_ids(id)
            if len(kids) > 0.5 {
                bind open = st_get("exp:" + num_str(id), 1.0) > 0.5
                icon_draw(sel(open, "chev_d", "chev_r"), ix + 5.0, ry + rh * 0.5, 8.0, C_TEXT_DIM)
                if u_hot(ix - 2.0, ry, 14.0, rh) && u_pressed() { st_set("exp:" + num_str(id), sel(open, 0.0, 1.0))  st_set("hier_eat", 1.0) }
            }
            bind col = kind_col(kind)
            if nd_get(i, "vis") < 0.5 { bind col = c_mix(col, C_BG_SIDE, 0.55) }
            icon_draw(kind_icon(kind), ix + 22.0, ry + rh * 0.5, 12.0, col)
            bind name = nd_get(i, "name")
            g_text_fit(ix + 34.0, ry + 5.0, w - ix - 34.0 - 66.0 + x, 12.0, sel(selected, C_TEXT_STRONG, sel(nd_get(i, "vis") > 0.5, C_TEXT, C_TEXT_DIM)), name)
            # right-side toggles: key marker, eye, lock
            bind tx = x + w - 62.0
            if key_node_has(id) { g_diamond(tx, ry + rh * 0.5, 4.0, C_RED) }
            bind ex = x + w - 46.0
            bind eye_hot = u_hot(ex - 8.0, ry, 16.0, rh)
            icon_draw(sel(nd_get(i, "vis") > 0.5, "eye", "eye_off"), ex, ry + rh * 0.5, 11.0, sel(eye_hot, C_TEXT_STRONG, sel(nd_get(i, "vis") > 0.5, C_TEXT_DIM, C_RED)))
            if eye_hot && u_pressed() { undo_push()  nd_set(i, "vis", 1.0 - nd_get(i, "vis"))  st_set("hier_eat", 1.0) }
            bind lx = x + w - 28.0
            bind lock_hot = u_hot(lx - 8.0, ry, 16.0, rh)
            if nd_get(i, "lock") > 0.5 || lock_hot { icon_draw("lock", lx, ry + rh * 0.5, 10.0, sel(nd_get(i, "lock") > 0.5, C_YELLOW, C_TEXT_DIM)) }
            if lock_hot && u_pressed() { undo_push()  nd_set(i, "lock", 1.0 - nd_get(i, "lock"))  st_set("hier_eat", 1.0) }
            # selection / drag start / context
            if hot && st_get("hier_eat", 0.0) < 0.5 {
                if u_pressed() {
                    if u_ctrl() { sel_toggle(id) } else if !selected { sel_set(id) }
                    st_set("hdrag_id", id)
                    st_set("hdrag_x", u_mx())
                    st_set("hdrag_y", u_my())
                    st_set("hdrag_live", 0.0)
                    if u_double() { modal_text("Rename node", "node_rename", name) }
                }
                if u_rpressed() {
                    if !selected { sel_set(id) }
                    ctx_open("node", num_str(id), u_mx(), u_my())
                }
            }
            # drop target feedback
            if drag_id > 0.0 && st_get("hdrag_live", 0.0) > 0.5 && drag_id != id && u_in(x, ry, w - 12.0, rh) && !nd_is_descendant(id, drag_id) {
                bind frac = (u_my() - ry) / rh
                if frac < 0.25 { g_rect(x + 4.0, ry, w - 10.0, 2.0, C_BLUE)  bind drop_mode = "before" }
                else if frac > 0.75 { g_rect(x + 4.0, ry + rh - 2.0, w - 10.0, 2.0, C_BLUE)  bind drop_mode = "after" }
                else { g_frame(x + 2.0, ry, w - 6.0, rh, C_BLUE)  bind drop_mode = "child" }
                bind drop_id = id
            }
        }
        bind r = r + 1.0
    }
    if n < 0.5 { g_text_wrap(x + 12.0, top + 8.0, w - 24.0, 12.0, C_TEXT_DIM, sel(st_get("hier_q", "") == "", "Empty scene. Use Scene > Add, the + button or Shift+A.", "(no match)")) }
    u_unclip()
    st_set("hier_eat", 0.0)
    # drag state
    if drag_id > 0.0 {
        if u_down() && (abs(u_mx() - st_get("hdrag_x", 0.0)) > 5.0 || abs(u_my() - st_get("hdrag_y", 0.0)) > 5.0) { st_set("hdrag_live", 1.0) }
        if st_get("hdrag_live", 0.0) > 0.5 && u_down() {
            bind nm = nd_gid(drag_id, "name")
            g_rrect_a(u_mx() + 10.0, u_my() + 6.0, g_text_w(12.0, nm) + 20.0, 20.0, 4.0, C_SEL, 0.9)
            g_text(u_mx() + 20.0, u_my() + 10.0, 12.0, C_TEXT_STRONG, nm)
        }
        if u_released() {
            if st_get("hdrag_live", 0.0) > 0.5 && drop_id > 0.0 { hier_drop(drag_id, drop_id, drop_mode) }
            st_set("hdrag_id", 0.0)
            st_set("hdrag_live", 0.0)
        }
        if !u_down() { st_set("hdrag_id", 0.0)  st_set("hdrag_live", 0.0) }
    }
    # click on empty space deselects
    if u_hot(x, top, w - 12.0, vh) && u_pressed() && drag_id <= 0.0 && u_my() > top + 4.0 + n * rh - off { sel_clear() }
}

fn hier_drop(id, target, mode) {
    undo_push()
    bind i = nd_find(id)
    bind ti = nd_find(target)
    if i < 0.0 || ti < 0.0 { return 0.0 }
    bind wp = nd_world_pos(i)
    if mode == "child" {
        bind lp = nd_local_from_world(target, list_get(wp, 0), list_get(wp, 1), list_get(wp, 2))
        nd_set(i, "par", target)
        if !nd_is_2d(nd_get(i, "kind")) { nd_set(i, "px", list_get(lp, 0))  nd_set(i, "py", list_get(lp, 1))  nd_set(i, "pz", list_get(lp, 2)) }
        st_set("exp:" + num_str(target), 1.0)
        nd_reorder(nd_find(id), nd_find(target) + 1.0)
    } else {
        bind newpar = nd_get(ti, "par")
        if newpar != nd_get(i, "par") {
            bind lp = nd_local_from_world(newpar, list_get(wp, 0), list_get(wp, 1), list_get(wp, 2))
            nd_set(i, "par", newpar)
            if !nd_is_2d(nd_get(i, "kind")) { nd_set(i, "px", list_get(lp, 0))  nd_set(i, "py", list_get(lp, 1))  nd_set(i, "pz", list_get(lp, 2)) }
        }
        bind from = nd_find(id)
        bind to = nd_find(target)
        if mode == "after" { bind to = to + 1.0 }
        nd_reorder(from, to)
    }
    status("Moved " + nd_gid(id, "name"))
    return 0.0
}

← Back to index