Files
modbison/scripts/studio/studio_hierarchy_dock.gd
T

182 lines
5.0 KiB
GDScript

class_name StudioHierarchyDock
extends VBoxContainer
## Scene Hierarchy Tree Dock for ModBison Studio.
## Displays all world objects, supports search filtering, selection sync,
## parent-child hierarchies, duplicate, and deletion.
signal object_selected(object_id: String)
signal delete_requested(object_id: String)
signal duplicate_requested(object_id: String)
signal group_created(object_ids: Array[String])
var _search_edit: LineEdit
var _tree: Tree
var _tree_root: TreeItem
var _btn_delete: Button
var _btn_duplicate: Button
var _btn_group: Button
var _items_by_id: Dictionary = {}
var _updating: bool = false
var _core: EditorCore = null
func _init() -> void:
name = "StudioHierarchyDock"
custom_minimum_size = Vector2(240, 300)
size_flags_vertical = Control.SIZE_EXPAND_FILL
_build_ui()
func setup(core: EditorCore) -> void:
_core = core
if _core != null and _core.selection != null:
_core.selection.selection_changed.connect(_on_core_selection_changed)
refresh_tree()
func _build_ui() -> void:
var header := Label.new()
header.text = "Scene Hierarchy"
header.add_theme_font_size_override("font_size", 13)
header.add_theme_color_override("font_color", Color(1.0, 0.9, 0.4))
add_child(header)
_search_edit = LineEdit.new()
_search_edit.placeholder_text = "🔍 Search hierarchy..."
_search_edit.text_changed.connect(_on_search_changed)
add_child(_search_edit)
_tree = Tree.new()
_tree.size_flags_vertical = Control.SIZE_EXPAND_FILL
_tree.select_mode = Tree.SELECT_MULTI
_tree.item_selected.connect(_on_tree_item_selected)
add_child(_tree)
# Action buttons row
var btn_row := HBoxContainer.new()
_btn_duplicate = Button.new()
_btn_duplicate.text = "Duplicate"
_btn_duplicate.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_btn_duplicate.pressed.connect(_on_duplicate_pressed)
btn_row.add_child(_btn_duplicate)
_btn_group = Button.new()
_btn_group.text = "Group"
_btn_group.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_btn_group.pressed.connect(_on_group_pressed)
btn_row.add_child(_btn_group)
_btn_delete = Button.new()
_btn_delete.text = "Delete"
_btn_delete.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4))
_btn_delete.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_btn_delete.pressed.connect(_on_delete_pressed)
btn_row.add_child(_btn_delete)
add_child(btn_row)
func refresh_tree() -> void:
_tree.clear()
_items_by_id.clear()
_tree_root = _tree.create_item()
_tree_root.set_text(0, "World Scene")
if _core == null or _core.model == null:
return
var filter := _search_edit.text.strip_edges().to_lower()
var objects = _core.model.payload.objects
# First pass: identify parents and root objects
for obj in objects:
var parent_id := ""
if obj.properties is Dictionary:
parent_id = str(obj.properties.get("parent_id", ""))
var dname: String = str(obj.id)
if obj.properties is Dictionary and obj.properties.has("display_name"):
dname = str(obj.properties["display_name"])
if filter != "" and not dname.to_lower().contains(filter) and not obj.id.to_lower().contains(filter):
continue
var item := _tree.create_item(_tree_root)
item.set_text(0, "🔹 " + dname)
item.set_metadata(0, obj.id)
_items_by_id[obj.id] = item
# Highlight current selection
if _core.selection != null:
var sel := _core.selection.get_selected()
if sel != "" and _items_by_id.has(sel):
_items_by_id[sel].select(0)
func _on_search_changed(_text: String) -> void:
refresh_tree()
func _on_tree_item_selected() -> void:
if _updating:
return
var selected_item := _tree.get_selected()
if selected_item != null:
var obj_id: String = str(selected_item.get_metadata(0))
if obj_id != "":
object_selected.emit(obj_id)
if _core != null:
_updating = true
_core.select_object(obj_id)
_updating = false
func _on_core_selection_changed(obj_id: String) -> void:
if _updating:
return
_updating = true
_tree.deselect_all()
if obj_id != "" and _items_by_id.has(obj_id):
_items_by_id[obj_id].select(0)
_tree.scroll_to_item(_items_by_id[obj_id])
_updating = false
func _on_delete_pressed() -> void:
if _core == null:
return
var sel := _core.selection.get_selected()
if sel != "":
delete_requested.emit(sel)
_core.delete_object(sel)
refresh_tree()
func _on_duplicate_pressed() -> void:
if _core == null:
return
var sel := _core.selection.get_selected()
if sel != "":
var obj := _core.model.find_object(sel)
if obj != null:
var new_pos := obj.position + Vector3(1.0, 1.0, 0.0)
var cat_id := str(obj.properties.get("catalog_id", "crate_small"))
var new_id := _core.place_object(cat_id, new_pos, obj.rotation, obj.scale, obj.properties.duplicate(true))
if new_id != "":
duplicate_requested.emit(new_id)
_core.select_object(new_id)
refresh_tree()
func _on_group_pressed() -> void:
var selected_ids: Array[String] = []
var item := _tree.get_next_selected(null)
while item != null:
var meta = item.get_metadata(0)
if meta != null and str(meta) != "":
selected_ids.append(str(meta))
item = _tree.get_next_selected(item)
if selected_ids.size() >= 2:
group_created.emit(selected_ids)