lingdev
↓ 0 downloadsLingDev: 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).
Keywords game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdevCode / 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
}