class_name InspectorPanel extends VBoxContainer ## Property editor for the selected object (docs/architecture/editor.md ## §7.2 `InspectorPanel`). ## ## 2D-tile-editor scope (docs/architecture/editor.md §8: the default world ## is flat, `CHUNK_SIZE_Z=1`): position exposes X/Y (world-plane) plus a ## single "height" Z for props that sit above the floor, and rotation ## exposes one yaw field (rotation about the vertical axis) rather than a ## full 3-axis gizmo -- deliberately not the volumetric 3D editing the doc ## says to defer. ## ## Pure UI: every edit calls back into `EditorCore` (`move_object` / ## `rotate_object` / `scale_object` / `delete_object`), so inspector edits ## go through the exact same undo/redo path as viewport edits. signal delete_requested(object_id: String) var _core: EditorCore = null var _object_id: String = "" var _updating := false # guards against feedback loops while refreshing fields var _id_label: Label var _type_label: Label var _asset_label: Label var _pos_x: SpinBox var _pos_y: SpinBox var _pos_z: SpinBox var _yaw: SpinBox var _scale_x: SpinBox var _scale_y: SpinBox var _scale_z: SpinBox var _delete_button: Button var _empty_label: Label var _fields_box: VBoxContainer func _init() -> void: name = "InspectorPanel" func _ready() -> void: var header := Label.new() header.text = "Inspector" add_child(header) _empty_label = Label.new() _empty_label.text = "(nothing selected)" add_child(_empty_label) _fields_box = VBoxContainer.new() _fields_box.visible = false add_child(_fields_box) _id_label = _add_readonly_row("id") _type_label = _add_readonly_row("type") _asset_label = _add_readonly_row("asset") _pos_x = _add_spinbox_row("pos x") _pos_y = _add_spinbox_row("pos y") _pos_z = _add_spinbox_row("pos z (height)") _pos_x.value_changed.connect(_on_position_changed) _pos_y.value_changed.connect(_on_position_changed) _pos_z.value_changed.connect(_on_position_changed) _yaw = _add_spinbox_row("rotation (deg)") _yaw.min_value = -360.0 _yaw.max_value = 360.0 _yaw.step = 1.0 _yaw.value_changed.connect(_on_yaw_changed) _scale_x = _add_spinbox_row("scale x") _scale_y = _add_spinbox_row("scale y") _scale_z = _add_spinbox_row("scale z") _scale_x.value_changed.connect(_on_scale_changed) _scale_y.value_changed.connect(_on_scale_changed) _scale_z.value_changed.connect(_on_scale_changed) _delete_button = Button.new() _delete_button.text = "Delete" _delete_button.pressed.connect(_on_delete_pressed) _fields_box.add_child(_delete_button) func setup(core: EditorCore) -> void: _core = core func show_object(object_id: String) -> void: _object_id = object_id if object_id == "" or _core == null: _empty_label.visible = true _fields_box.visible = false return var obj := _core.model.find_object(object_id) if obj == null: _empty_label.visible = true _fields_box.visible = false return _empty_label.visible = false _fields_box.visible = true _updating = true _id_label.text = "id: %s" % obj.id _type_label.text = "type: %s" % obj.type _asset_label.text = "asset: %s" % obj.asset_id _pos_x.value = obj.position.x _pos_y.value = obj.position.y _pos_z.value = obj.position.z _yaw.value = rad_to_deg(Quaternion(obj.rotation.x, obj.rotation.y, obj.rotation.z, obj.rotation.w).get_euler().y) _scale_x.value = obj.scale.x _scale_y.value = obj.scale.y _scale_z.value = obj.scale.z _updating = false func _on_position_changed(_v: float) -> void: if _updating or _core == null or _object_id == "": return _core.move_object(_object_id, Vector3(_pos_x.value, _pos_y.value, _pos_z.value)) func _on_yaw_changed(v: float) -> void: if _updating or _core == null or _object_id == "": return var quat := Quaternion(Vector3.UP, deg_to_rad(v)) _core.rotate_object(_object_id, Vector4(quat.x, quat.y, quat.z, quat.w)) func _on_scale_changed(_v: float) -> void: if _updating or _core == null or _object_id == "": return _core.scale_object(_object_id, Vector3(_scale_x.value, _scale_y.value, _scale_z.value)) func _on_delete_pressed() -> void: if _object_id == "": return var id := _object_id delete_requested.emit(id) func _add_readonly_row(label: String) -> Label: var l := Label.new() l.text = "%s: " % label _fields_box.add_child(l) return l func _add_spinbox_row(label_text: String) -> SpinBox: var row := HBoxContainer.new() var l := Label.new() l.text = label_text l.custom_minimum_size = Vector2(90, 0) row.add_child(l) var sb := SpinBox.new() sb.min_value = -100000.0 sb.max_value = 100000.0 sb.step = 0.1 sb.allow_greater = true sb.allow_lesser = true row.add_child(sb) _fields_box.add_child(row) return sb