灵符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/inspector.ling
# inspector.ling - properties of the selected node: transform with key diamonds, colour,
# kind-specific fields, script / tag, keyed properties. Edits go through set_prop (auto-key aware).

# a labelled draggable number with a key diamond; returns nothing, writes the node
fn prop_num(label, x, y, w, i, col, lo, hi, speed, fmt) {
    bind id = nd_id(i)
    g_text(x, y + 6.0, 11.0, C_TEXT_DIM, label)
    bind v = nd_get(i, col)
    bind shown = sel(fmt == "deg", fmt1(v * 57.29578), sel(fmt == "int", num_str(floor(v)), fmt2(v)))
    bind fx = x + 22.0
    bind fw = w - 22.0 - 18.0
    bind hot = u_hot(fx, y, fw, 24.0)
    if hot && u_pressed() { undo_push() }
    bind nv = u_dragval("pn_" + col + num_str(id), fx, y, fw, 24.0, sel(fmt == "deg", v * 57.29578, v), lo, hi, speed, shown)
    if u_has(nv) {
        if fmt == "deg" { set_prop(i, col, nv / 57.29578) } else if fmt == "int" { set_prop(i, col, floor(nv)) } else { set_prop(i, col, nv) }
    }
    # key diamond
    bind kx = x + w - 8.0
    bind t = st_get("tl_t", 0.0)
    bind has = key_has(id, col)
    bind at = has && key_at(id, col, t) >= 0.0
    bind khot = u_hot(kx - 8.0, y + 4.0, 16.0, 16.0)
    if at { g_diamond(kx, y + 12.0, 5.0, C_RED) }
    else if has { icon_draw("key_off", kx, y + 12.0, 11.0, C_RED) }
    else { icon_draw("key_off", kx, y + 12.0, 11.0, sel(khot, C_TEXT, C_LINE)) }
    if khot {
        u_tip(sel(at, "Remove this key", sel(has, "Add a key at the playhead (right-click: clear all keys of " + col + ")", "Key " + col + " at the playhead")))
        if u_pressed() {
            undo_push()
            if at { key_del(key_at(id, col, t)) } else { key_add(id, col, t, nd_get(i, col), "cubic_in_out") }
        }
        if u_rpressed() && has { undo_push()  key_remove_prop(id, col) }
    }
}
# a text field bound to a column
fn prop_text(label, x, y, w, i, col) {
    g_text(x, y + 6.0, 11.0, C_TEXT_DIM, label)
    bind fid = "pt_" + col + num_str(nd_id(i))
    bind cur = nd_get(i, col)
    bind was_focus = st_get("focus", "") == fid
    bind v = u_textfield(fid, x + 46.0, y, w - 46.0, 24.0, cur)
    if v != cur {
        if st_get("insp_undo_fid", "") != fid { undo_push()  st_set("insp_undo_fid", fid) }
        nd_set(i, col, v)
        mark_dirty()
    }
    if !was_focus && st_get("insp_undo_fid", "") == fid && st_get("focus", "") != fid { st_set("insp_undo_fid", "") }
}
fn prop_pick(x, y, w, i, col, exts, title) {
    if u_button("pp_" + col + num_str(nd_id(i)), x, y, w, 24.0, "Pick", 0.0) {
        st_set("pending_src", nd_id(i))
        st_set("pending_col", col)
        picker_open("open", title, exts, "node.set_src", p_join(proj_root(), "assets"), "")
    }
}

fn inspector_panel(x, y, w, h) {
    g_rect(x, y, w, h, C_BG_SIDE)
    u_vsep(x, y, h)
    ds_word(x + 12.0, y + 8.0, 10.0, "INSPECTOR", ds_col("func"), true)
    u_hsep(x + 6.0, y + 31.0, w - 12.0)
    bind i = sel_index()
    bind top = y + 36.0
    bind vh = h - 40.0
    bind cx = x + 12.0
    bind cw = w - 34.0
    if i < 0.0 {
        inspector_scene(cx, top, cw)
        return 0.0
    }
    bind id = nd_id(i)
    bind kind = nd_get(i, "kind")
    bind is2d = nd_is_2d(kind)
    bind content = max(sel(is2d, 620.0, 720.0), st_get("insp_content", 700.0))
    bind off = u_scroll_y("insp_sb", x + 1.0, top, w - 1.0, vh, content, st_get("insp_scroll", 0.0))
    st_set("insp_scroll", off)
    u_clip(x + 1.0, top, w - 1.0, vh)
    bind yy = top + 6.0 - off
    # header: icon, kind, name
    icon_draw(kind_icon(kind), cx + 12.0, yy + 12.0, 20.0, kind_col(kind))
    g_text(cx + 30.0, yy + 1.0, 11.0, kind_col(kind), kind + "   #" + num_str(id) + sel(nd_get(i, "par") > 0.0, "   child of " + nd_gid(nd_get(i, "par"), "name"), ""))
    bind yy = yy + 18.0
    prop_text("Name", cx, yy, cw, i, "name")
    bind yy = yy + 30.0
    bind v = u_check("insp_vis", cx, yy, "Visible", nd_get(i, "vis") > 0.5)
    if v { undo_push()  nd_set(i, "vis", 1.0 - nd_get(i, "vis")) }
    bind v = u_check("insp_lock", cx + 90.0, yy, "Locked", nd_get(i, "lock") > 0.5)
    if v { undo_push()  nd_set(i, "lock", 1.0 - nd_get(i, "lock")) }
    bind yy = yy + 26.0
    # transform
    u_section(cx, yy, cw, "TRANSFORM", ds_col("ctl"))
    bind yy = yy + 18.0
    bind third = floor(cw / 3.0)
    if is2d {
        prop_num("X", cx, yy, third, i, "px", 0.0 - 10000.0, 10000.0, 1.0, "int")
        prop_num("Y", cx + third, yy, third, i, "py", 0.0 - 10000.0, 10000.0, 1.0, "int")
        bind yy = yy + 30.0
        prop_num("W", cx, yy, third, i, "w", 1.0, 8000.0, 1.0, "int")
        prop_num("H", cx + third, yy, third, i, "h", 1.0, 8000.0, 1.0, "int")
        bind yy = yy + 30.0
        g_text(cx, yy + 6.0, 11.0, C_TEXT_DIM, "Anchor")
        bind ancs = ["tl", "tc", "tr", "cl", "cc", "cr", "bl", "bc", "br"]
        bind an = ["top left", "top centre", "top right", "centre left", "centre", "centre right", "bottom left", "bottom centre", "bottom right"]
        bind ai = u_combo("insp_anc", cx + 46.0, yy, cw - 46.0, 24.0, an, max(0.0, l_index(ancs, nd_get(i, "anc"))))
        if ai >= 0.0 { undo_push()  nd_set(i, "anc", list_get(ancs, ai)) }
        bind yy = yy + 30.0
    } else {
        prop_num("X", cx, yy, third, i, "px", 0.0 - 100000.0, 100000.0, 0.02, "")
        prop_num("Y", cx + third, yy, third, i, "py", 0.0 - 100000.0, 100000.0, 0.02, "")
        prop_num("Z", cx + third * 2.0, yy, third, i, "pz", 0.0 - 100000.0, 100000.0, 0.02, "")
        bind yy = yy + 30.0
        g_text(cx, yy, 10.0, C_TEXT_DIM, "rotation (degrees)")
        bind yy = yy + 14.0
        prop_num("X", cx, yy, third, i, "rx", 0.0 - 3600.0, 3600.0, 0.5, "deg")
        prop_num("Y", cx + third, yy, third, i, "ry", 0.0 - 3600.0, 3600.0, 0.5, "deg")
        prop_num("Z", cx + third * 2.0, yy, third, i, "rz", 0.0 - 3600.0, 3600.0, 0.5, "deg")
        bind yy = yy + 30.0
        g_text(cx, yy, 10.0, C_TEXT_DIM, sel(kind == "model" || kind == "actor", "scale", "size (half extents)"))
        bind yy = yy + 14.0
        prop_num("X", cx, yy, third, i, "sx", 0.001, 10000.0, 0.01, "")
        prop_num("Y", cx + third, yy, third, i, "sy", 0.001, 10000.0, 0.01, "")
        prop_num("Z", cx + third * 2.0, yy, third, i, "sz", 0.001, 10000.0, 0.01, "")
        bind yy = yy + 30.0
    }
    # colour
    u_section(cx, yy, cw, "COLOUR", ds_col("asm"))
    bind yy = yy + 18.0
    bind c = nd_get(i, "col")
    bind q = floor((cw - 36.0) / 3.0)
    bind r = u_dragval("insp_r" + num_str(id), cx, yy, q, 24.0, c_r(c), 0.0, 255.0, 1.0, "R " + num_str(c_r(c)))
    bind g = u_dragval("insp_g" + num_str(id), cx + q + 4.0, yy, q, 24.0, c_g(c), 0.0, 255.0, 1.0, "G " + num_str(c_g(c)))
    bind b = u_dragval("insp_b" + num_str(id), cx + q * 2.0 + 8.0, yy, q, 24.0, c_b(c), 0.0, 255.0, 1.0, "B " + num_str(c_b(c)))
    if u_has(r) { set_prop(i, "col", c_rgb(r, c_g(c), c_b(c))) }
    if u_has(g) { set_prop(i, "col", c_rgb(c_r(c), g, c_b(c))) }
    if u_has(b) { set_prop(i, "col", c_rgb(c_r(c), c_g(c), b)) }
    g_rect(cx + cw - 24.0, yy + 2.0, 20.0, 20.0, c)
    g_frame(cx + cw - 24.0, yy + 2.0, 20.0, 20.0, C_LINE)
    bind yy = yy + 30.0
    bind k = 0.0
    while k < 9.0 {
        bind pc = sel(k < 7.0, c_pal(k), sel(k < 7.5, C_WHITE, C_TEXT_DIM))
        bind sx = cx + k * 20.0
        g_rect(sx, yy, 16.0, 14.0, pc)
        if u_hot(sx, yy, 16.0, 14.0) {
            g_frame(sx, yy, 16.0, 14.0, C_WHITE)
            if u_pressed() { undo_push()  nd_set(i, "col", pc) }
        }
        bind k = k + 1.0
    }
    bind yy = yy + 24.0
    # kind-specific
    u_section(cx, yy, cw, str_upper(kind), kind_col(kind))
    bind yy = yy + 18.0
    bind yy = inspector_kind(cx, yy, cw, i, kind)
    g_text(cx, yy + 6.0, 11.0, C_TEXT_DIM, "Extra")
    bind exv = u_textfield("insp_ex" + num_str(id), cx + 46.0, yy, cw - 46.0, 24.0, nd_get(i, "ex"))
    if exv != nd_get(i, "ex") { nd_set(i, "ex", exv)  mark_dirty() }
    if u_hot(cx + 46.0, yy, cw - 46.0, 24.0) { u_tip("k=v;k=v options: hue=0.85 gain=1.2 (lmesh colour cycling), shape=sphere (orb actor), rainbow=1, flat=1, style=text, blink=1, font=Daemon") }
    bind yy = yy + 30.0
    # script / tag
    u_section(cx, yy, cw, "SCRIPT", ds_col("mod"))
    bind yy = yy + 18.0
    prop_text("Script", cx, yy, cw - 54.0, i, "scr")
    if u_button("insp_scr_new", cx + cw - 50.0, yy, 50.0, 24.0, "Edit", 0.0) {
        bind sn = nd_get(i, "scr")
        if sn == "" { modal_text("New script name", "new_script", str_lower(str_replace(nd_get(i, "name"), " ", "_"))) }
        else { script_open(p_join(proj_root(), "scripts/" + sn + ".ling"))  st_set("dock_tab", 2.0)  st_set("show_dock", 1.0) }
    }
    bind yy = yy + 30.0
    prop_text("Tag", cx, yy, cw, i, "tag")
    bind yy = yy + 30.0
    # keys
    bind props = key_props_of(id)
    if len(props) > 0.5 {
        u_section(cx, yy, cw, "KEYS", ds_col("bind"))
        bind yy = yy + 18.0
        bind p = 0.0
        while p < len(props) {
            bind prop = list_get(props, p)
            bind ks = key_list(id, prop)
            g_diamond(cx + 6.0, yy + 8.0, 4.0, C_RED)
            g_text(cx + 16.0, yy + 2.0, 11.0, C_TEXT, prop + "  " + num_str(len(ks)) + " keys")
            if u_button("insp_kclr" + prop, cx + cw - 54.0, yy - 2.0, 54.0, 20.0, "clear", 0.0) { undo_push()  key_remove_prop(id, prop) }
            bind yy = yy + 24.0
            bind p = p + 1.0
        }
    }
    bind yy = yy + 8.0
    if u_button("insp_dup", cx, yy, 80.0, 24.0, "Duplicate", 0.0) { do_action("edit.dup") }
    if u_button("insp_focus", cx + 86.0, yy, 60.0, 24.0, "Focus", 0.0) { do_action("view.focus") }
    if u_button("insp_del", cx + cw - 70.0, yy, 70.0, 24.0, "Delete", C_RED) { do_action("edit.delete") }
    bind yy = yy + 40.0
    st_set("insp_content", yy - (top + 6.0 - off) + 20.0)
    u_unclip()
    return 0.0
}

# kind-specific fields; returns the next y
fn inspector_kind(cx, yy, cw, i, kind) {
    bind half = floor(cw / 2.0)
    if kind == "model" || kind == "actor" {
        prop_text("File", cx, yy, cw - 54.0, i, "src")
        prop_pick(cx + cw - 50.0, yy, 50.0, i, "src", "glb,gltf,lmesh", "Model file")
        bind yy = yy + 30.0
        bind hnd = model_handle(nd_get(i, "src"))
        bind ac = plat_model_anim_count(hnd)
        g_text(cx, yy + 2.0, 11.0, C_TEXT_DIM, sel(hnd >= 0.0, "loaded: " + num_str(plat_model_tris(hnd)) + " tris, " + num_str(ac) + " clips", sel(nd_get(i, "src") == "", "no file", sel(plat_has_models(), "could not load", "models load in the desktop runtime"))))
        bind yy = yy + 18.0
        prop_num("Clip", cx, yy, half, i, "a", 0.0, max(0.0, ac - 1.0), 0.05, "int")
        prop_num("Spd", cx + half, yy, half, i, "b", 0.0, 10.0, 0.01, "")
        bind yy = yy + 30.0
        if kind == "actor" {
            g_text(cx, yy + 6.0, 11.0, C_TEXT_DIM, "Control")
            bind ctl = ["none", "player (WASD / pad)", "AI: follow player", "AI: patrol", "AI: guard"]
            bind ci = u_combo("insp_ctl", cx + 56.0, yy, cw - 56.0, 24.0, ctl, clampf(nd_get(i, "c"), 0.0, 4.0))
            if ci >= 0.0 { undo_push()  nd_set(i, "c", ci)  if ci == 1.0 { nd_set(i, "tag", "player") } }
            bind yy = yy + 30.0
            prop_num("Speed", cx, yy, half, i, "d", 0.1, 60.0, 0.05, "")
            bind yy = yy + 30.0
        }
    } else if kind == "light" {
        prop_num("Int", cx, yy, half, i, "a", 0.0, 20.0, 0.02, "")
        prop_num("Rad", cx + half, yy, half, i, "b", 0.1, 500.0, 0.1, "")
        bind yy = yy + 30.0
    } else if kind == "camera" {
        g_text(cx, yy + 6.0, 11.0, C_TEXT_DIM, "Rig")
        bind rigs = ["fixed (pose)", "orbit target", "follow target", "dolly (timeline)"]
        bind ri = u_combo("insp_rig", cx + 46.0, yy, cw - 46.0, 24.0, rigs, clampf(nd_get(i, "b"), 0.0, 3.0))
        if ri >= 0.0 { undo_push()  nd_set(i, "b", ri) }
        bind yy = yy + 30.0
        prop_num("Zd", cx, yy, half, i, "a", 0.3, 20.0, 0.01, "")
        prop_num("Dist", cx + half, yy, half, i, "d", 0.5, 200.0, 0.05, "")
        bind yy = yy + 30.0
        prop_text("Target", cx, yy, cw, i, "txt")
        bind yy = yy + 30.0
        prop_num("Spin", cx, yy, half, i, "c", 0.0 - 3.0, 3.0, 0.005, "")
        if u_button("insp_cam_here", cx + half + 4.0, yy, half - 4.0, 24.0, "From view", 0.0) {
            undo_push()
            nd_set(i, "px", cam_x())  nd_set(i, "py", cam_y())  nd_set(i, "pz", cam_z())
            nd_set(i, "ry", cam_yaw())  nd_set(i, "rx", cam_pitch())
        }
        bind yy = yy + 30.0
    } else if kind == "spawn" {
        g_text_wrap(cx, yy, cw, 11.0, C_TEXT_DIM, "The player actor starts here (tag `player`); the Y rotation is the facing direction.")
        bind yy = yy + 34.0
    } else if kind == "trigger" {
        prop_text("Action", cx, yy, cw, i, "txt")
        bind yy = yy + 30.0
        g_text_wrap(cx, yy, cw, 10.0, C_TEXT_DIM, "goto:scenes/x.lscene  cutscene:scenes/c.lscene  say:text  sound:file  music:file  call:hook  set:k=v  add:k=v  quit")
        bind yy = yy + 36.0
        bind v = u_check("insp_once", cx, yy, "Fire once", nd_get(i, "a") > 0.5)
        if v { undo_push()  nd_set(i, "a", 1.0 - nd_get(i, "a")) }
        bind yy = yy + 26.0
    } else if kind == "audio" || kind == "music" {
        prop_text("File", cx, yy, cw - 54.0, i, "src")
        prop_pick(cx + cw - 50.0, yy, 50.0, i, "src", "wav,ogg,mp3,flac", "Sound file")
        bind yy = yy + 30.0
        prop_num("Vol", cx, yy, half, i, "a", 0.0, 1.0, 0.005, "")
        if kind == "audio" { prop_num("Spread", cx + half, yy, half, i, "d", 0.1, 50.0, 0.05, "") }
        bind yy = yy + 30.0
        bind v = u_check("insp_loop", cx, yy, "Loop", nd_get(i, "b") > 0.5)
        if v { undo_push()  nd_set(i, "b", 1.0 - nd_get(i, "b")) }
        bind v = u_check("insp_auto", cx + 80.0, yy, "Autoplay", nd_get(i, "c") > 0.5)
        if v { undo_push()  nd_set(i, "c", 1.0 - nd_get(i, "c")) }
        bind yy = yy + 26.0
    } else if kind == "midi" {
        prop_text("File", cx, yy, cw - 54.0, i, "src")
        prop_pick(cx + cw - 50.0, yy, 50.0, i, "src", "mid,midi", "MIDI file")
        bind yy = yy + 30.0
        prop_text("Patch", cx, yy, cw - 54.0, i, "txt")
        prop_pick(cx + cw - 50.0, yy, 50.0, i, "txt", "ling", "Instrument patch (.ling)")
        bind yy = yy + 30.0
        bind v = u_check("insp_vis_notes", cx, yy, "Visualise notes", nd_get(i, "b") > 0.5)
        if v { undo_push()  nd_set(i, "b", 1.0 - nd_get(i, "b")) }
        bind yy = yy + 26.0
    } else if kind == "text3d" || kind == "label" || kind == "button" {
        prop_text("Text", cx, yy, cw - 54.0, i, "txt")
        if u_button("insp_txt_big", cx + cw - 50.0, yy, 50.0, 24.0, "Edit", 0.0) { st_set("insp_field", "txt")  modal_text("Text", "node_field", nd_get(i, "txt")) }
        bind yy = yy + 30.0
        prop_num(sel(kind == "text3d", "Size", "Font"), cx, yy, half, i, "a", 0.1, 400.0, 0.1, sel(kind == "text3d", "", "int"))
        bind yy = yy + 30.0
        if kind == "button" {
            prop_text("Action", cx, yy, cw, i, "scr")
            bind yy = yy + 30.0
            g_text_wrap(cx, yy, cw, 10.0, C_TEXT_DIM, "goto:scene  cutscene:scene  back  quit  host:7777  join:ip:port  vol:0.1  toggle:k  set:k=v  call:hook")
            bind yy = yy + 30.0
            g_text(cx, yy + 6.0, 11.0, C_TEXT_DIM, "Style")
            bind sti = u_combo("insp_bstyle", cx + 46.0, yy, cw - 46.0, 24.0, ["framed (hatched box)", "text row with ink cursor"], sel(nd_ex(i, "style", "") == "text", 1.0, 0.0))
            if sti >= 0.0 { undo_push()  nd_ex_set(i, "style", sel(sti > 0.5, "text", "")) }
            bind yy = yy + 30.0
        }
        if kind == "label" {
            bind v = u_check("insp_blink", cx, yy, "Blink", nd_ex(i, "blink", "0") == "1")
            if v { undo_push()  nd_ex_set(i, "blink", sel(nd_ex(i, "blink", "0") == "1", "", "1")) }
            bind yy = yy + 26.0
        }
        if kind != "text3d" {
            g_text(cx, yy + 6.0, 11.0, C_TEXT_DIM, "Font")
            bind fv = u_textfield("insp_font" + num_str(nd_id(i)), cx + 46.0, yy, cw - 46.0, 24.0, nd_ex(i, "font", ""))
            if fv != nd_ex(i, "font", "") { nd_ex_set(i, "font", fv)  mark_dirty() }
            bind yy = yy + 30.0
        }
    } else if kind == "image" {
        prop_text("File", cx, yy, cw - 54.0, i, "src")
        prop_pick(cx + cw - 50.0, yy, 50.0, i, "src", "png,jpg,jpeg,bmp", "Image file")
        bind yy = yy + 30.0
    } else if kind == "panel" {
        prop_num("Alpha", cx, yy, half, i, "a", 0.0, 1.0, 0.005, "")
        bind yy = yy + 30.0
    } else if kind == "bar" {
        prop_num("Val", cx, yy, half, i, "a", 0.0, 100000.0, 0.5, "")
        prop_num("Max", cx + half, yy, half, i, "b", 0.001, 100000.0, 0.5, "")
        bind yy = yy + 30.0
        g_text_wrap(cx, yy, cw, 10.0, C_TEXT_DIM, "Tag `health` / `score` binds the bar (or a label's {}) to game_var of that name.")
        bind yy = yy + 30.0
    } else if kind == "swarm" {
        prop_num("Count", cx, yy, half, i, "a", 1.0, 400.0, 1.0, "int")
        prop_num("Radius", cx + half, yy, half, i, "b", 0.1, 500.0, 0.05, "")
        bind yy = yy + 30.0
        g_text(cx, yy + 6.0, 11.0, C_TEXT_DIM, "Shape")
        bind shapes = ["wire cubes", "stars (points)", "orbs (spheres)"]
        bind shi = u_combo("insp_swarm", cx + 46.0, yy, cw - 46.0, 24.0, shapes, clampf(nd_get(i, "c"), 0.0, 2.0))
        if shi >= 0.0 { undo_push()  nd_set(i, "c", shi) }
        bind yy = yy + 30.0
        prop_num("Drift", cx, yy, half, i, "d", 0.0, 5.0, 0.005, "")
        bind v = u_check("insp_rainbow", cx + half + 4.0, yy + 4.0, "Rainbow", nd_ex(i, "rainbow", "0") == "1")
        if v { undo_push()  nd_ex_set(i, "rainbow", sel(nd_ex(i, "rainbow", "0") == "1", "", "1")) }
        bind yy = yy + 30.0
        bind v = u_check("insp_flat", cx, yy, "Flat (a disc, not a ball)", nd_ex(i, "flat", "0") == "1")
        if v { undo_push()  nd_ex_set(i, "flat", sel(nd_ex(i, "flat", "0") == "1", "", "1")) }
        bind yy = yy + 26.0
    } else if kind == "knot" {
        prop_num("Count", cx, yy, half, i, "a", 1.0, 120.0, 1.0, "int")
        prop_num("Speed", cx + half, yy, half, i, "d", 0.0 - 2.0, 2.0, 0.002, "")
        bind yy = yy + 30.0
        prop_num("Outer", cx, yy, half, i, "b", 0.1, 200.0, 0.02, "")
        prop_num("Inner", cx + half, yy, half, i, "c", 0.0, 200.0, 0.02, "")
        bind yy = yy + 30.0
        bind v = u_check("insp_rainbow2", cx, yy, "Rainbow chain", nd_ex(i, "rainbow", "1") == "1")
        if v { undo_push()  nd_ex_set(i, "rainbow", sel(nd_ex(i, "rainbow", "1") == "1", "0", "1")) }
        bind yy = yy + 26.0
    } else if kind == "plane" {
        g_text_wrap(cx, yy, cw, 11.0, C_TEXT_DIM, "Tag `solid` makes a box or plane collide with the player.")
        bind yy = yy + 30.0
    } else if kind == "empty" {
        g_text_wrap(cx, yy, cw, 11.0, C_TEXT_DIM, "A group: children follow its position and Y rotation.")
        bind yy = yy + 30.0
    } else {
        g_text_wrap(cx, yy, cw, 11.0, C_TEXT_DIM, "Tag `solid` makes it collide with the player.")
        bind yy = yy + 30.0
    }
    return yy
}

fn inspector_scene(cx, top, cw) {
    bind yy = top + 6.0
    ds_word(cx, yy, 11.0, "SCENE", ds_col("call"), false)
    bind yy = yy + 24.0
    u_kv(cx, yy, cw, "Name", sc_get("name", ""))
    bind yy = yy + 18.0
    u_kv(cx, yy, cw, "Kind", sc_get("kind", "3d"))
    bind yy = yy + 18.0
    u_kv(cx, yy, cw, "File", sel(sc_get("path", "") == "", "(unsaved)", p_base(sc_get("path", ""))))
    bind yy = yy + 18.0
    u_kv(cx, yy, cw, "Nodes", num_str(nd_count()))
    bind yy = yy + 18.0
    u_kv(cx, yy, cw, "Timeline", fmt1(sc_get("len", 10.0)) + " s @ " + num_str(sc_get("fps", 30.0)) + " fps")
    bind yy = yy + 18.0
    u_kv(cx, yy, cw, "Keys / clips", num_str(key_count()) + " / " + num_str(clip_count()))
    bind yy = yy + 18.0
    if sc_get("hud", "") != "" { u_kv(cx, yy, cw, "HUD", sc_get("hud", ""))  bind yy = yy + 18.0 }
    if sc_get("next", "") != "" { u_kv(cx, yy, cw, "Next", sc_get("next", ""))  bind yy = yy + 18.0 }
    bind yy = yy + 10.0
    if u_button("insp_scn", cx, yy, 120.0, 24.0, "Scene Settings", C_BLUE) { modal_open("scene") }
    if u_button("insp_addn", cx + 126.0, yy, 80.0, 24.0, "+ Add", 0.0) { modal_open("addnode") }
    bind yy = yy + 40.0
    ds_word(cx, yy, 11.0, "PROJECT", ds_col("bind"), false)
    bind yy = yy + 24.0
    if proj_root() == "" {
        g_text_wrap(cx, yy, cw, 11.0, C_TEXT_DIM, "No project open. File > New Project, or Open Project.")
        bind yy = yy + 30.0
        if u_button("insp_np", cx, yy, 120.0, 24.0, "New Project", C_BLUE) { do_action("project.new") }
    } else {
        u_kv(cx, yy, cw, "Name", st_get("proj_name", ""))
        bind yy = yy + 18.0
        u_kv(cx, yy, cw, "Start", st_get("proj_start", ""))
        bind yy = yy + 18.0
        u_kv(cx, yy, cw, "Scenes", num_str(st_llen("proj_scenes")))
        bind yy = yy + 18.0
        u_kv(cx, yy, cw, "Window", num_str(st_get("proj_w", 1280.0)) + "x" + num_str(st_get("proj_h", 720.0)))
        bind yy = yy + 24.0
        if u_button("insp_build", cx, yy, 120.0, 24.0, "Build & Export", 0.0) { do_action("build.panel") }
    }
    bind yy = yy + 40.0
    g_text_wrap(cx, yy, cw, 11.0, C_TEXT_DIM, "Click a node in the viewport or the Hierarchy to edit it. Right-drag orbits, middle-drag pans, wheel zooms.")
}

fn inspector_text_submit(v) {
    bind i = sel_index()
    if i < 0.0 { return 0.0 }
    bind col = st_get("insp_field", "txt")
    undo_push()
    nd_set(i, col, v)
    return 0.0
}

← Back to index