Files
modbison/scripts/editor/ui/inspector_panel.gd
T
2026-09-16 12:21:39 +00:00

170 lines
4.7 KiB
GDScript

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"
size_flags_vertical = Control.SIZE_EXPAND_FILL
func _ready() -> void:
var header := Label.new()
header.text = "Inspector"
header.add_theme_font_size_override("font_size", 15)
header.add_theme_color_override("font_color", Color(1, 0.95, 0.4))
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