scopedvr-map-core/ui/panels/terrain_panel.gd
2026-11-01 19:31:33 +01:00

282 lines
12 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends "res://core/ui/panels/editor_panel.gd"
## Terrain-Werkzeug-Panel (aus main.gd ausgezogen, Karte t_eaccbfed U1 —
## Logik unverändert, Karte t_9aba04f1 + t_603ec1a8): großer Modus-Button +
## Pinsel-Modi, Material-Palette, Größe/Stärke-Regler. Werkzeug-Ein/Aus bleibt
## im Koordinator (main.gd _enter/_leave terrain), das Panel hält nur die
## Widgets und die Regler-Verdrahtung.
var terrain_edit_btn: Button
var terrain_panel: VBoxContainer
var terrain_mode_raise: Button
var terrain_mode_lower: Button
var terrain_mode_smooth: Button
var terrain_mode_paint: Button
var terrain_mode_flatten: Button
var terrain_mode_ramp: Button
var terrain_falloff_label: Label
var terrain_falloff_slider: HSlider
var terrain_palette: VBoxContainer
var terrain_mat_buttons: Array = [] # Gras/Sand/Fels (ButtonGroup)
var terrain_size_btn_small: Button
var terrain_size_btn_medium: Button
var terrain_size_btn_large: Button
var terrain_radius_slider: HSlider
var terrain_radius_label: Label
var terrain_strength_slider: HSlider
var terrain_strength_label: Label
var terrain_paint_hint: Label # Malen: Hinweis auf die Raster-Feinheit (24.10.)
var terrain_weich_label: Label # Malen: Kante hart ↔ weich (25.10.)
var terrain_weich_slider: HSlider
static func spec() -> Dictionary:
return {"titel": "Terrain", "gruppe": "rechts", "order": 40}
func build() -> void:
terrain_edit_btn = Button.new()
terrain_edit_btn.text = "⛰ Terrain formen"
terrain_edit_btn.custom_minimum_size = UIScale.v2(0, 48)
terrain_edit_btn.toggle_mode = true
terrain_edit_btn.tooltip_text = "Boden formen: heben, senken, glätten, einebnen, Rampe (F1)"
terrain_edit_btn.pressed.connect(_on_terrain_btn_pressed)
add_child(terrain_edit_btn)
terrain_panel = VBoxContainer.new()
terrain_panel.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster
add_child(terrain_panel)
terrain_panel.add_child(_field_label("Pinsel:"))
var mode_row := GridContainer.new()
mode_row.columns = 3
mode_row.add_theme_constant_override("h_separation", UIScale.px(8)) # U7-Raster
mode_row.add_theme_constant_override("v_separation", UIScale.px(8))
terrain_panel.add_child(mode_row)
var mode_group := ButtonGroup.new()
terrain_mode_raise = _terrain_mode_button("Heben ⬆", mode_group,
"Pinsel-Modus Heben: Boden unter dem Pinsel anheben (Taste 1)")
terrain_mode_lower = _terrain_mode_button("Senken ⬇", mode_group,
"Pinsel-Modus Senken: Boden unter dem Pinsel absenken (Taste 2)")
terrain_mode_smooth = _terrain_mode_button("Glätten ≈", mode_group,
"Pinsel-Modus Glätten: Höhenunterschiede verrutschen (Taste 3)")
terrain_mode_paint = _terrain_mode_button("Malen 🖌", mode_group,
"Pinsel-Modus Malen: Material auf den Boden malen (Taste 4)")
terrain_mode_flatten = _terrain_mode_button("Einebnen ▬", mode_group,
"Pinsel-Modus Einebnen: zieht den Boden auf die Höhe, die beim Strichstart unter dem Cursor war")
terrain_mode_ramp = _terrain_mode_button("Rampe ◢", mode_group,
"Pinsel-Modus Rampe: Klick = Start, ziehen und loslassen = Ende — Höhe läuft gleichmäßig dazwischen (Breite = Pinselgröße)")
terrain_mode_raise.button_pressed = true
terrain_mode_flatten.toggled.connect(_on_terrain_mode_changed)
terrain_mode_ramp.toggled.connect(_on_terrain_mode_changed)
terrain_mode_raise.toggled.connect(_on_terrain_mode_changed)
terrain_mode_lower.toggled.connect(_on_terrain_mode_changed)
terrain_mode_smooth.toggled.connect(_on_terrain_mode_changed)
terrain_mode_paint.toggled.connect(_on_terrain_mode_changed)
mode_row.add_child(terrain_mode_raise)
mode_row.add_child(terrain_mode_lower)
mode_row.add_child(terrain_mode_smooth)
mode_row.add_child(terrain_mode_flatten)
mode_row.add_child(terrain_mode_ramp)
# Malen hat seit 25.10. einen eigenen Tab (malen_panel.gd); der Modus-Knopf
# bleibt unsichtbar in der ButtonGroup (Taste 4, Malen-Tab-Knopf).
terrain_mode_paint.visible = false
terrain_panel.add_child(terrain_mode_paint)
# Pinselgrößen-Presets (Karte t_603ec1a8): drei große Buttons + freier Slider.
terrain_panel.add_child(_field_label("Pinselgröße:"))
var size_row := HBoxContainer.new()
size_row.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster
terrain_panel.add_child(size_row)
var size_group := ButtonGroup.new()
terrain_size_btn_small = _terrain_size_button("Klein\n(4 m)", size_group, 4.0)
terrain_size_btn_medium = _terrain_size_button("Mittel\n(12 m)", size_group, 12.0)
terrain_size_btn_large = _terrain_size_button("Groß\n(32 m)", size_group, 32.0)
# Kein Signal: Der Handler greift auf terrain_radius_slider zu, der erst
# unten erzeugt wird — beim Panel-Bau wäre das ein Crash (GUI-Start-Bug
# 15.10.). Der Slider startet unten passend mit 12 m (= Mittel).
terrain_size_btn_medium.set_pressed_no_signal(true)
size_row.add_child(terrain_size_btn_small)
size_row.add_child(terrain_size_btn_medium)
size_row.add_child(terrain_size_btn_large)
terrain_radius_label = _field_label("Pinsel-Größe: 12 m")
terrain_panel.add_child(terrain_radius_label)
terrain_radius_slider = HSlider.new()
terrain_radius_slider.min_value = 1.0
terrain_radius_slider.max_value = 64.0
terrain_radius_slider.step = 1.0
terrain_radius_slider.value = 12.0
terrain_radius_slider.custom_minimum_size = UIScale.v2(0, 32)
terrain_radius_slider.value_changed.connect(_on_terrain_radius_changed)
terrain_panel.add_child(terrain_radius_slider)
terrain_strength_label = _field_label("Stärke: 8 m pro Sekunde")
terrain_panel.add_child(terrain_strength_label)
terrain_strength_slider = HSlider.new()
terrain_strength_slider.min_value = 1.0
terrain_strength_slider.max_value = 20.0
terrain_strength_slider.step = 0.5
terrain_strength_slider.value = 8.0
terrain_strength_slider.custom_minimum_size = UIScale.v2(0, 32)
terrain_strength_slider.value_changed.connect(_on_terrain_strength_changed)
terrain_panel.add_child(terrain_strength_slider)
# Pinsel-Kante (Falloff, Karte 25.10.): ein Regler hart ↔ weich für alle
# Höhen-Pinsel (Malen hat seinen eigenen Kanten-Regler weiter unten).
terrain_falloff_label = _field_label("Kante: 50 % weich")
terrain_panel.add_child(terrain_falloff_label)
terrain_falloff_slider = HSlider.new()
terrain_falloff_slider.min_value = 0.0
terrain_falloff_slider.max_value = 100.0
terrain_falloff_slider.step = 5.0
terrain_falloff_slider.value = 50.0
terrain_falloff_slider.custom_minimum_size = UIScale.v2(0, 32)
terrain_falloff_slider.tooltip_text = "Links = harte Kante (breiter Kern), rechts = weich auslaufend"
terrain_falloff_slider.value_changed.connect(_on_terrain_falloff_changed)
terrain_panel.add_child(terrain_falloff_slider)
func _on_terrain_btn_pressed() -> void:
if terrain_edit_btn.button_pressed:
if terrain_mode_paint.button_pressed:
terrain_mode_raise.button_pressed = true # Malen hat seinen eigenen Tab
main.call("_set_tool_mode", "terrain")
refresh_panels()
else:
main.call("_set_tool_mode", "objects")
func _terrain_mode_button(text: String, group: ButtonGroup, hinweis: String) -> Button:
var btn := Button.new()
btn.text = text
btn.toggle_mode = true
btn.button_group = group
btn.custom_minimum_size = UIScale.v2(0, 40)
btn.tooltip_text = hinweis # U7: Werkzeugname + Taste im Tooltip
return btn
func _selected_terrain_mode() -> int:
if terrain_mode_lower.button_pressed:
return TerrainField.Mode.LOWER
if terrain_mode_smooth.button_pressed:
return TerrainField.Mode.SMOOTH
if terrain_mode_paint.button_pressed:
return TerrainField.Mode.PAINT
if terrain_mode_flatten != null and terrain_mode_flatten.button_pressed:
return TerrainField.Mode.FLATTEN
if terrain_mode_ramp != null and terrain_mode_ramp.button_pressed:
return TerrainField.Mode.RAMP
return TerrainField.Mode.RAISE
## Mode-Buttons (ButtonGroup) → Pinsel-Modus; Palette nur im Malen-Modus.
func _on_terrain_mode_changed(_pressed: bool) -> void:
var paint := _selected_terrain_mode() == TerrainField.Mode.PAINT
if terrain_palette != null:
terrain_palette.visible = paint
if terrain_paint_hint != null:
terrain_paint_hint.text = paint_hint_text(main.doc.get("terrain", {}) if main.doc is Dictionary else {})
refresh_panels()
if main.terrain_tool != null:
main.terrain_tool.call("set_brush_mode", _selected_terrain_mode())
## Welche Bedienflächen sind sichtbar (Terrain-Tab bzw. Malen-Tab), welcher
## Start-Knopf steht gedrückt — Terrain und Malen teilen das Pinsel-Werkzeug.
func refresh_panels() -> void:
if terrain_panel == null:
return
var aktiv: bool = str(main.get("tool_mode")) == "terrain"
var paint := _selected_terrain_mode() == TerrainField.Mode.PAINT
terrain_edit_btn.set_pressed_no_signal(aktiv and not paint)
var malen = main.get("malen_mod")
if malen != null:
malen.malen_edit_btn.set_pressed_no_signal(aktiv and paint)
if aktiv:
_show_own_tab(malen if paint else self)
func _show_own_tab(tab: Control) -> void:
var tabs = main.get("ui_right_panel")
if tabs is TabContainer and tab != null and tab.get_parent() == tabs:
(tabs as TabContainer).current_tab = tab.get_index()
## Hinweistext Malen-Modus (pure, testbar): feinste Mal-Einheit = Abstand
## zweier Rasterpunkte der Karte (size / (resolution − 1)).
static func paint_hint_text(terrain: Dictionary) -> String:
var res := SvmapIO.material_resolution(terrain) # feines Material-Raster
var size := maxf(float(terrain.get("size_x_m", 0.0)), float(terrain.get("size_z_m", 0.0)))
if res < 2 or size <= 0.0:
return "Malen setzt das Material zellenweise — Stärke gibt es hier nicht."
var m := size / float(res - 1)
var m_text := str(int(roundf(m))) if absf(m - roundf(m)) < 0.05 \
else String.num(m, 1).replace(".", ",")
return "Malen setzt das Material zellenweise: feinste Einheit auf dieser Karte ≈ %s m. Stärke gibt es hier nicht." % m_text
## Pinselgrößen-Presets (Karte t_603ec1a8): setzen den Radius-Slider
## (der Label-Text + Ring-Optik hängen am value_changed-Signal).
func _terrain_size_button(text: String, group: ButtonGroup, radius_m: float) -> Button:
var btn := Button.new()
btn.text = text
btn.toggle_mode = true
btn.button_group = group
btn.custom_minimum_size = UIScale.v2(0, 48)
btn.tooltip_text = "Pinselradius %d m" % int(radius_m)
btn.toggled.connect(_on_terrain_size_preset.bind(radius_m))
return btn
func _on_terrain_size_preset(pressed: bool, radius_m: float) -> void:
# Null-Check als Schutznetz: beim Panel-Bau existiert der Slider noch
# nicht (Programmatic-Pressed darf den Handler nie früh treffen).
if pressed and terrain_radius_slider != null:
terrain_radius_slider.value = radius_m
func _on_terrain_radius_changed(value: float) -> void:
terrain_radius_label.text = "Pinsel-Größe: %d m" % int(value)
var malen = main.get("malen_mod")
if malen != null:
malen.set_radius_silent(value)
if main.terrain_tool != null:
main.terrain_tool.call("set_radius", value)
func _on_terrain_falloff_changed(value: float) -> void:
terrain_falloff_label.text = "Kante: %d %% weich" % int(value) if value > 0.0 else "Kante: hart"
if main.terrain_tool != null:
main.terrain_tool.call("set_falloff", value / 100.0)
func _on_terrain_strength_changed(value: float) -> void:
terrain_strength_label.text = "Stärke: %.1f m pro Sekunde" % value
if main.terrain_tool != null:
main.terrain_tool.call("set_strength", value)
## Terrain-Pinsel-Größe per [ / ] (Karte t_55257792): durch die 3 Presets
## Klein/Mittel/Groß zyklisch — der Preset-Button wird mit gesetzt (set_pressed
## feuert toggled → Slider + Label + Pinssel-Ring wandern mit).
## (Aus main.gd ausgezogen, Logik unverändert; Koordinator ruft weiter.)
func terrain_brush_size_step(up: bool) -> void:
if terrain_radius_slider == null:
return
var next := MoveMath.brush_next_preset(float(terrain_radius_slider.value), up)
var btn: Button = null
match next:
4.0:
btn = terrain_size_btn_small
12.0:
btn = terrain_size_btn_medium
32.0:
btn = terrain_size_btn_large
if btn != null:
btn.set_pressed(true) # feuert _on_terrain_size_preset → Slider/Label
else:
terrain_radius_slider.value = next
main.terrain_tool.call("set_radius", next)
main.call("_set_status", "Pinsel-Größe: %d m (mit [ / ] durch Klein/Mittel/Groß zyklisch)." % int(next))