251 lines
7.7 KiB
GDScript
251 lines
7.7 KiB
GDScript
extends Node
|
|
## Runtime debug panel. Toggle with F1.
|
|
## Reads DP.get_all() to build controls — adding a param to DP is all that's needed to show it here.
|
|
|
|
var _panel: Control
|
|
var _status_label: Label
|
|
var _prev_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_CAPTURED
|
|
|
|
|
|
func _ready() -> void:
|
|
_build_ui()
|
|
DP.any_changed.connect(_on_dp_changed)
|
|
|
|
|
|
func _build_ui() -> void:
|
|
var canvas := CanvasLayer.new()
|
|
canvas.layer = 128
|
|
add_child(canvas)
|
|
|
|
_panel = PanelContainer.new()
|
|
_panel.anchor_left = 1.0
|
|
_panel.anchor_right = 1.0
|
|
_panel.anchor_top = 0.0
|
|
_panel.anchor_bottom = 1.0
|
|
_panel.offset_left = -390.0
|
|
_panel.offset_right = 0.0
|
|
_panel.offset_top = 0.0
|
|
_panel.offset_bottom = 0.0
|
|
_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
_panel.visible = false
|
|
|
|
var bg := StyleBoxFlat.new()
|
|
bg.bg_color = Color(0.08, 0.08, 0.10, 0.95)
|
|
_panel.add_theme_stylebox_override("panel", bg)
|
|
canvas.add_child(_panel)
|
|
|
|
var root_vbox := VBoxContainer.new()
|
|
root_vbox.size_flags_horizontal = Control.SIZE_FILL
|
|
_panel.add_child(root_vbox)
|
|
|
|
# ── Toolbar ───────────────────────────────────────────────────────────────
|
|
var toolbar_margin := MarginContainer.new()
|
|
toolbar_margin.add_theme_constant_override("margin_left", 8)
|
|
toolbar_margin.add_theme_constant_override("margin_right", 8)
|
|
toolbar_margin.add_theme_constant_override("margin_top", 6)
|
|
toolbar_margin.add_theme_constant_override("margin_bottom", 4)
|
|
root_vbox.add_child(toolbar_margin)
|
|
|
|
var toolbar := HBoxContainer.new()
|
|
toolbar.add_theme_constant_override("separation", 6)
|
|
toolbar_margin.add_child(toolbar)
|
|
|
|
var title := Label.new()
|
|
title.text = "DEBUG [F1]"
|
|
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
toolbar.add_child(title)
|
|
|
|
_status_label = Label.new()
|
|
_status_label.modulate = Color(0.4, 1.0, 0.5)
|
|
toolbar.add_child(_status_label)
|
|
|
|
var save_btn := Button.new()
|
|
save_btn.text = "Save"
|
|
save_btn.pressed.connect(_on_save)
|
|
toolbar.add_child(save_btn)
|
|
|
|
var reset_btn := Button.new()
|
|
reset_btn.text = "Reset All"
|
|
reset_btn.pressed.connect(DP.reset_all)
|
|
toolbar.add_child(reset_btn)
|
|
|
|
root_vbox.add_child(HSeparator.new())
|
|
|
|
# ── Scrollable content ────────────────────────────────────────────────────
|
|
var scroll := ScrollContainer.new()
|
|
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
scroll.size_flags_horizontal = Control.SIZE_FILL
|
|
root_vbox.add_child(scroll)
|
|
|
|
var content_margin := MarginContainer.new()
|
|
content_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
content_margin.add_theme_constant_override("margin_left", 8)
|
|
content_margin.add_theme_constant_override("margin_right", 8)
|
|
content_margin.add_theme_constant_override("margin_top", 4)
|
|
content_margin.add_theme_constant_override("margin_bottom", 8)
|
|
scroll.add_child(content_margin)
|
|
|
|
var content := VBoxContainer.new()
|
|
content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
content_margin.add_child(content)
|
|
|
|
_build_params(content)
|
|
|
|
|
|
func _build_params(content: VBoxContainer) -> void:
|
|
# Group keys by section preserving registration order
|
|
var sections: Dictionary = {}
|
|
for key: String in DP.get_all():
|
|
var sec: String = DP.get_all()[key]["section"]
|
|
if not sections.has(sec):
|
|
sections[sec] = []
|
|
sections[sec].append(key)
|
|
|
|
for section: String in sections:
|
|
content.add_child(HSeparator.new())
|
|
|
|
var header := Label.new()
|
|
header.text = section.to_upper()
|
|
header.modulate = Color(0.75, 0.85, 1.0)
|
|
content.add_child(header)
|
|
|
|
for key: String in sections[section]:
|
|
var meta: Dictionary = DP.get_all()[key]
|
|
if meta["type"] == TYPE_FLOAT:
|
|
_add_float_row(content, key, meta)
|
|
elif meta["type"] == TYPE_BOOL:
|
|
_add_bool_row(content, key, meta)
|
|
|
|
|
|
func _add_float_row(parent: VBoxContainer, key: String, meta: Dictionary) -> void:
|
|
var row := HBoxContainer.new()
|
|
row.size_flags_horizontal = Control.SIZE_FILL
|
|
parent.add_child(row)
|
|
|
|
var lbl := Label.new()
|
|
lbl.text = key.replace("_", " ")
|
|
lbl.custom_minimum_size.x = 140
|
|
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
row.add_child(lbl)
|
|
|
|
var slider := HSlider.new()
|
|
slider.min_value = meta["min"]
|
|
slider.max_value = meta["max"]
|
|
slider.step = meta["step"]
|
|
slider.value = meta["value"]
|
|
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
slider.custom_minimum_size.x = 60
|
|
row.add_child(slider)
|
|
|
|
var spin := SpinBox.new()
|
|
spin.min_value = meta["min"]
|
|
spin.max_value = meta["max"]
|
|
spin.step = meta["step"]
|
|
spin.value = meta["value"]
|
|
spin.custom_minimum_size.x = 85
|
|
row.add_child(spin)
|
|
|
|
# Slider → SpinBox + DP (use no_signal to avoid echo)
|
|
slider.value_changed.connect(func(v: float) -> void:
|
|
spin.set_value_no_signal(v)
|
|
DP.set_value(key, v)
|
|
)
|
|
# SpinBox → Slider + DP
|
|
spin.value_changed.connect(func(v: float) -> void:
|
|
slider.set_value_no_signal(v)
|
|
DP.set_value(key, v)
|
|
)
|
|
# External changes (reset, load) → sync both controls
|
|
DP.any_changed.connect(func(changed_key: String, _val: Variant) -> void:
|
|
if changed_key != key and changed_key != "__all__":
|
|
return
|
|
var v := DP.f(key)
|
|
slider.set_value_no_signal(v)
|
|
spin.set_value_no_signal(v)
|
|
)
|
|
|
|
|
|
func _add_bool_row(parent: VBoxContainer, key: String, meta: Dictionary) -> void:
|
|
var row := HBoxContainer.new()
|
|
row.size_flags_horizontal = Control.SIZE_FILL
|
|
parent.add_child(row)
|
|
|
|
var lbl := Label.new()
|
|
lbl.text = key.replace("_", " ")
|
|
lbl.custom_minimum_size.x = 140
|
|
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
row.add_child(lbl)
|
|
|
|
var btn := Button.new()
|
|
btn.toggle_mode = true
|
|
btn.button_pressed = meta["value"]
|
|
btn.text = "HIDDEN" if not meta["value"] else "SHOWN"
|
|
_update_btn_style(btn, meta["value"])
|
|
row.add_child(btn)
|
|
|
|
btn.toggled.connect(func(v: bool) -> void:
|
|
btn.text = "HIDDEN" if not v else "SHOWN"
|
|
_update_btn_style(btn, v)
|
|
DP.set_value(key, v)
|
|
)
|
|
|
|
DP.any_changed.connect(func(changed_key: String, _val: Variant) -> void:
|
|
if changed_key != key and changed_key != "__all__":
|
|
return
|
|
var v_b: bool = DP.b(key)
|
|
btn.set_pressed_no_signal(v_b)
|
|
btn.text = "HIDDEN" if not v_b else "SHOWN"
|
|
_update_btn_style(btn, v_b)
|
|
)
|
|
|
|
|
|
func _update_btn_style(btn: Button, active: bool) -> void:
|
|
if active:
|
|
btn.add_theme_color_override("font_color", Color(0.4, 1.0, 0.5))
|
|
else:
|
|
btn.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4))
|
|
|
|
|
|
func _on_save() -> void:
|
|
DP.save()
|
|
_status_label.text = "Saved!"
|
|
get_tree().create_timer(1.5).timeout.connect(func() -> void:
|
|
_status_label.text = ""
|
|
)
|
|
|
|
|
|
func _on_dp_changed(key: String, _value: Variant) -> void:
|
|
if key == "show_collisions" or key == "__all__":
|
|
var active: bool = DP.b("show_collisions")
|
|
get_tree().debug_collisions_hint = active
|
|
# Force refresh of all collision shapes
|
|
_refresh_collisions(get_tree().root, active)
|
|
|
|
|
|
func _refresh_collisions(node: Node, active: bool) -> void:
|
|
if node is CollisionShape3D or node is CollisionPolygon3D:
|
|
node.visible = !node.visible # Trigger a notification
|
|
node.visible = !node.visible
|
|
|
|
for child in node.get_children():
|
|
_refresh_collisions(child, active)
|
|
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if _panel.visible and Input.is_key_pressed(KEY_CTRL) and event is InputEventMouseMotion:
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if not (event is InputEventKey and event.pressed and not event.echo):
|
|
return
|
|
if (event as InputEventKey).keycode != KEY_F1:
|
|
return
|
|
_panel.visible = not _panel.visible
|
|
if _panel.visible:
|
|
_prev_mouse_mode = Input.mouse_mode
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
else:
|
|
Input.mouse_mode = _prev_mouse_mode
|
|
get_viewport().set_input_as_handled()
|