UI-Größe: zentrale Skalierung Klein/Mittel/Groß (t_297221b5) — Top-Bar-Menü, sofort wirksam (Rebuild), gespeichert in user://scopedvr_editor.cfg [ui]; Fenster startet maximiert + merkt Lage; Panel-Min-Maße/Schrift an einen Faktor gekoppelt; Layout-Überlauf-Fixes (Hilfe-Hinweis Umbruch+Grow-up, Statuszeile/Top-Bar-Ellipsis, linkes Panel scrollbar)
This commit is contained in:
parent
f4748af371
commit
c8a41c0794
7 changed files with 666 additions and 98 deletions
397
src/ui/main.gd
397
src/ui/main.gd
|
|
@ -23,6 +23,8 @@ const ShortcutPolicy := preload("res://src/editor/shortcut_policy.gd")
|
||||||
const MoveMath := preload("res://src/editor/move_math.gd")
|
const MoveMath := preload("res://src/editor/move_math.gd")
|
||||||
# Kurzhilfe-Overlay Karte 3/3 (t_11b5c25b): Gesamte Tastenliste als BBCode.
|
# Kurzhilfe-Overlay Karte 3/3 (t_11b5c25b): Gesamte Tastenliste als BBCode.
|
||||||
const HelpText := preload("res://src/ui/help_text.gd")
|
const HelpText := preload("res://src/ui/help_text.gd")
|
||||||
|
# Zentrale UI-Skalierung (Karte t_297221b5): EIN Faktor für Schrift/Panels/Zeilen.
|
||||||
|
const UIScale := preload("res://src/ui/ui_scale.gd")
|
||||||
|
|
||||||
var manifest: Dictionary = {}
|
var manifest: Dictionary = {}
|
||||||
var doc: Dictionary = {} # das Map-Dictionary (1:1 svmap-Format)
|
var doc: Dictionary = {} # das Map-Dictionary (1:1 svmap-Format)
|
||||||
|
|
@ -140,6 +142,7 @@ var help_overlay: PanelContainer # H-Kurzhilfe (nicht-blockierend)
|
||||||
var help_overlay_title: Label
|
var help_overlay_title: Label
|
||||||
var help_overlay_text: RichTextLabel
|
var help_overlay_text: RichTextLabel
|
||||||
var tool_label: Label # permanenter Werkzeugmodus + Flug-Hint
|
var tool_label: Label # permanenter Werkzeugmodus + Flug-Hint
|
||||||
|
var size_menu: MenuButton # „UI-Größe“ (Karte t_297221b5): Klein/Mittel/Groß
|
||||||
|
|
||||||
# Editor-Selbstupdate (Karte t_d95f087a): Start-Check + Banner + „Jetzt updaten“
|
# Editor-Selbstupdate (Karte t_d95f087a): Start-Check + Banner + „Jetzt updaten“
|
||||||
var update_banner: PanelContainer # Leiste „🔄 Update verfügbar …“ (versteckt)
|
var update_banner: PanelContainer # Leiste „🔄 Update verfügbar …“ (versteckt)
|
||||||
|
|
@ -170,6 +173,11 @@ func _ready() -> void:
|
||||||
manifest = _load_manifest()
|
manifest = _load_manifest()
|
||||||
_build_ui()
|
_build_ui()
|
||||||
_update_tool_label()
|
_update_tool_label()
|
||||||
|
# Fenster-Zustand (Karte t_297221b5): Default MAXIMIERT (kein Vollbild);
|
||||||
|
# wer auf Fenstergröße umschaltet, bekommt die gemerkte Lage wieder.
|
||||||
|
# Close-Request selbst behandeln, damit die Lage VOR dem Quit gemerkt wird.
|
||||||
|
get_tree().set_auto_accept_quit(false)
|
||||||
|
_apply_window_state()
|
||||||
_start_with_new_map()
|
_start_with_new_map()
|
||||||
_maybe_start_demo_mode()
|
_maybe_start_demo_mode()
|
||||||
# Selbst-Update (Karte t_d95f087a): STILLER Start-Check im Hintergrund —
|
# Selbst-Update (Karte t_d95f087a): STILLER Start-Check im Hintergrund —
|
||||||
|
|
@ -190,18 +198,18 @@ func _load_manifest() -> Dictionary:
|
||||||
|
|
||||||
func _build_ui() -> void:
|
func _build_ui() -> void:
|
||||||
var theme := Theme.new()
|
var theme := Theme.new()
|
||||||
theme.default_font_size = 16
|
theme.default_font_size = UIScale.font_size()
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
var vbox := VBoxContainer.new()
|
var vbox := VBoxContainer.new()
|
||||||
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
vbox.add_theme_constant_override("separation", 4)
|
vbox.add_theme_constant_override("separation", UIScale.px(4))
|
||||||
add_child(vbox)
|
add_child(vbox)
|
||||||
|
|
||||||
# --- Top-Bar: Menü + Rückgängig/Wiederholen ---
|
# --- Top-Bar: Menü + Rückgängig/Wiederholen ---
|
||||||
var top := PanelContainer.new()
|
var top := PanelContainer.new()
|
||||||
var top_h := HBoxContainer.new()
|
var top_h := HBoxContainer.new()
|
||||||
top_h.add_theme_constant_override("separation", 10)
|
top_h.add_theme_constant_override("separation", UIScale.px(10))
|
||||||
top.add_child(top_h)
|
top.add_child(top_h)
|
||||||
vbox.add_child(top)
|
vbox.add_child(top)
|
||||||
_update_banner_build(vbox)
|
_update_banner_build(vbox)
|
||||||
|
|
@ -209,7 +217,7 @@ func _build_ui() -> void:
|
||||||
var file_menu := MenuButton.new()
|
var file_menu := MenuButton.new()
|
||||||
file_menu.text = "Datei"
|
file_menu.text = "Datei"
|
||||||
file_menu.flat = false
|
file_menu.flat = false
|
||||||
file_menu.custom_minimum_size = Vector2(90, 40)
|
file_menu.custom_minimum_size = UIScale.v2(90, 40)
|
||||||
var popup := file_menu.get_popup()
|
var popup := file_menu.get_popup()
|
||||||
popup.add_item("Neue Map…", 0)
|
popup.add_item("Neue Map…", 0)
|
||||||
popup.add_item("Öffnen…", 1)
|
popup.add_item("Öffnen…", 1)
|
||||||
|
|
@ -227,14 +235,14 @@ func _build_ui() -> void:
|
||||||
|
|
||||||
undo_btn = Button.new()
|
undo_btn = Button.new()
|
||||||
undo_btn.text = "↩ Rückgängig"
|
undo_btn.text = "↩ Rückgängig"
|
||||||
undo_btn.custom_minimum_size = Vector2(140, 40)
|
undo_btn.custom_minimum_size = UIScale.v2(140, 40)
|
||||||
undo_btn.tooltip_text = "Letzte Aktion zurücknehmen (Strg+Z)"
|
undo_btn.tooltip_text = "Letzte Aktion zurücknehmen (Strg+Z)"
|
||||||
undo_btn.pressed.connect(_do_undo)
|
undo_btn.pressed.connect(_do_undo)
|
||||||
top_h.add_child(undo_btn)
|
top_h.add_child(undo_btn)
|
||||||
|
|
||||||
redo_btn = Button.new()
|
redo_btn = Button.new()
|
||||||
redo_btn.text = "↪ Wiederholen"
|
redo_btn.text = "↪ Wiederholen"
|
||||||
redo_btn.custom_minimum_size = Vector2(150, 40)
|
redo_btn.custom_minimum_size = UIScale.v2(150, 40)
|
||||||
redo_btn.tooltip_text = "Zurückgenommene Aktion wiederherstellen (Strg+Y)"
|
redo_btn.tooltip_text = "Zurückgenommene Aktion wiederherstellen (Strg+Y)"
|
||||||
redo_btn.pressed.connect(_do_redo)
|
redo_btn.pressed.connect(_do_redo)
|
||||||
top_h.add_child(redo_btn)
|
top_h.add_child(redo_btn)
|
||||||
|
|
@ -246,7 +254,7 @@ func _build_ui() -> void:
|
||||||
grid_btn.text = "⊞ Gitter"
|
grid_btn.text = "⊞ Gitter"
|
||||||
grid_btn.toggle_mode = true
|
grid_btn.toggle_mode = true
|
||||||
grid_btn.button_pressed = false
|
grid_btn.button_pressed = false
|
||||||
grid_btn.custom_minimum_size = Vector2(120, 40)
|
grid_btn.custom_minimum_size = UIScale.v2(120, 40)
|
||||||
grid_btn.tooltip_text = "Maßstabsgitter (100-m-Raster) im Viewport ein-/ausblenden. Über sichtbarem Terrain ist es standardmäßig aus."
|
grid_btn.tooltip_text = "Maßstabsgitter (100-m-Raster) im Viewport ein-/ausblenden. Über sichtbarem Terrain ist es standardmäßig aus."
|
||||||
grid_btn.toggled.connect(_on_grid_toggled)
|
grid_btn.toggled.connect(_on_grid_toggled)
|
||||||
top_h.add_child(grid_btn)
|
top_h.add_child(grid_btn)
|
||||||
|
|
@ -259,7 +267,7 @@ func _build_ui() -> void:
|
||||||
contours_btn.text = "≈ Höhenlinien"
|
contours_btn.text = "≈ Höhenlinien"
|
||||||
contours_btn.toggle_mode = true
|
contours_btn.toggle_mode = true
|
||||||
contours_btn.button_pressed = false
|
contours_btn.button_pressed = false
|
||||||
contours_btn.custom_minimum_size = Vector2(150, 40)
|
contours_btn.custom_minimum_size = UIScale.v2(150, 40)
|
||||||
contours_btn.tooltip_text = "Höhenlinien (1 m, alle 5 m kräftiger) FIXIERT an/aus. Ohne Fixierung erscheinen sie nur im Terrain-Werkzeug (F1). Taste L schaltet ebenfalls. Bei flachem Terrain (< 2 m) sind sie automatisch aus."
|
contours_btn.tooltip_text = "Höhenlinien (1 m, alle 5 m kräftiger) FIXIERT an/aus. Ohne Fixierung erscheinen sie nur im Terrain-Werkzeug (F1). Taste L schaltet ebenfalls. Bei flachem Terrain (< 2 m) sind sie automatisch aus."
|
||||||
contours_btn.toggled.connect(_on_contours_toggled)
|
contours_btn.toggled.connect(_on_contours_toggled)
|
||||||
top_h.add_child(contours_btn)
|
top_h.add_child(contours_btn)
|
||||||
|
|
@ -267,7 +275,7 @@ func _build_ui() -> void:
|
||||||
# Map-Fertig-Check (Karte t_c4162c5d): Klartext-Liste VOR dem Export.
|
# Map-Fertig-Check (Karte t_c4162c5d): Klartext-Liste VOR dem Export.
|
||||||
check_btn = Button.new()
|
check_btn = Button.new()
|
||||||
check_btn.text = "✅ Karte prüfen"
|
check_btn.text = "✅ Karte prüfen"
|
||||||
check_btn.custom_minimum_size = Vector2(170, 40)
|
check_btn.custom_minimum_size = UIScale.v2(170, 40)
|
||||||
check_btn.tooltip_text = "Prüft, ob die Map bereit für den Export ist — sagen wir VORHER, was fehlt"
|
check_btn.tooltip_text = "Prüft, ob die Map bereit für den Export ist — sagen wir VORHER, was fehlt"
|
||||||
check_btn.pressed.connect(_on_check_map)
|
check_btn.pressed.connect(_on_check_map)
|
||||||
top_h.add_child(check_btn)
|
top_h.add_child(check_btn)
|
||||||
|
|
@ -275,14 +283,38 @@ func _build_ui() -> void:
|
||||||
# Karte testen (Karte t_b84af40f): Map SOFORT als Spiel zum Durchlaufen.
|
# Karte testen (Karte t_b84af40f): Map SOFORT als Spiel zum Durchlaufen.
|
||||||
test_btn = Button.new()
|
test_btn = Button.new()
|
||||||
test_btn.text = "▶ Karte testen"
|
test_btn.text = "▶ Karte testen"
|
||||||
test_btn.custom_minimum_size = Vector2(190, 40)
|
test_btn.custom_minimum_size = UIScale.v2(190, 40)
|
||||||
test_btn.tooltip_text = "Startet die Map SOFORT als Spiel zum Durchlaufen — eigenes Fenster, der Editor bleibt offen. ESC im Spiel = zurück zum Editor."
|
test_btn.tooltip_text = "Startet die Map SOFORT als Spiel zum Durchlaufen — eigenes Fenster, der Editor bleibt offen. ESC im Spiel = zurück zum Editor."
|
||||||
test_btn.pressed.connect(_on_test_run)
|
test_btn.pressed.connect(_on_test_run)
|
||||||
top_h.add_child(test_btn)
|
top_h.add_child(test_btn)
|
||||||
|
|
||||||
|
# UI-Größe (Karte t_297221b5): Klein/Mittel/Groß — Sly stellt das selbst um,
|
||||||
|
# es wirkt SOFORT (kompletter UI-Neuaufbau mit dem neuen Faktor) und bleibt
|
||||||
|
# in user://scopedvr_editor.cfg gespeichert.
|
||||||
|
size_menu = MenuButton.new()
|
||||||
|
size_menu.text = "UI-Größe"
|
||||||
|
size_menu.flat = false
|
||||||
|
size_menu.custom_minimum_size = UIScale.v2(120, 40)
|
||||||
|
size_menu.tooltip_text = "Größe der Oberfläche: Klein (wie bisher), Mittel (1,25-fach), Groß (1,5-fach). Wirkt sofort und bleibt gespeichert."
|
||||||
|
var size_popup := size_menu.get_popup()
|
||||||
|
for i in UIScale.step_count():
|
||||||
|
size_popup.add_check_item(UIScale.step_label(i), i)
|
||||||
|
_sync_size_menu()
|
||||||
|
size_popup.index_pressed.connect(_on_ui_size_selected)
|
||||||
|
top_h.add_child(size_menu)
|
||||||
|
|
||||||
var title_label := Label.new()
|
var title_label := Label.new()
|
||||||
title_label.text = " %s" % APP_TITLE
|
title_label.text = " %s" % APP_TITLE
|
||||||
title_label.add_theme_font_size_override("font_size", 20)
|
title_label.add_theme_font_size_override("font_size", UIScale.f(20))
|
||||||
|
# Karte t_297221b5: bei großer Stufe darf der Titel EGALISIEREN statt die
|
||||||
|
# Top-Bar aus dem Fenster zu drücken (Mindestbreite der Schrift skaliert mit).
|
||||||
|
# Mindestbreite, damit er nicht KOMPLETT verschwindet (Ellipsis macht "Scop…").
|
||||||
|
# Expand: bei Platz bekommt er seine VOLLSTÄNDIGE natürliche Breite (wie
|
||||||
|
# Vorher bei „Klein“), erst bei knappem Fenster egalisiert er mit "…".
|
||||||
|
title_label.custom_minimum_size = UIScale.v2(180, 0)
|
||||||
|
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
title_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
|
title_label.clip_text = true
|
||||||
top_h.add_child(title_label)
|
top_h.add_child(title_label)
|
||||||
|
|
||||||
var spacer := Control.new()
|
var spacer := Control.new()
|
||||||
|
|
@ -292,13 +324,19 @@ func _build_ui() -> void:
|
||||||
file_label = Label.new()
|
file_label = Label.new()
|
||||||
file_label.text = ""
|
file_label.text = ""
|
||||||
file_label.modulate = Color(1, 1, 1, 0.6)
|
file_label.modulate = Color(1, 1, 1, 0.6)
|
||||||
|
# Karte t_297221b5: Map-Pfad darf bei großer Stufe egalisiert werden
|
||||||
|
# (Voller Name im Tooltip, sonst drückt die Zeile die Top-Bar aus dem Fenster).
|
||||||
|
# Expand: bekommt zusammen mit dem Spacer seinen Anteil, egalisiert mit "…".
|
||||||
|
file_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
|
file_label.clip_text = true
|
||||||
|
file_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
top_h.add_child(file_label)
|
top_h.add_child(file_label)
|
||||||
|
|
||||||
# --- Hauptbereich ---
|
# --- Hauptbereich ---
|
||||||
var split := HSplitContainer.new()
|
var split := HSplitContainer.new()
|
||||||
split.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
split.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
split.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
split.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
split.split_offset = -320
|
split.split_offset = UIScale.px(-320)
|
||||||
vbox.add_child(split)
|
vbox.add_child(split)
|
||||||
|
|
||||||
split.add_child(_build_left_panel())
|
split.add_child(_build_left_panel())
|
||||||
|
|
@ -308,7 +346,7 @@ func _build_ui() -> void:
|
||||||
# --- Statusleiste: Hilfs-Meldungen + Budget ---
|
# --- Statusleiste: Hilfs-Meldungen + Budget ---
|
||||||
var status_panel := PanelContainer.new()
|
var status_panel := PanelContainer.new()
|
||||||
var status_h := HBoxContainer.new()
|
var status_h := HBoxContainer.new()
|
||||||
status_h.add_theme_constant_override("separation", 16)
|
status_h.add_theme_constant_override("separation", UIScale.px(16))
|
||||||
status_panel.add_child(status_h)
|
status_panel.add_child(status_h)
|
||||||
vbox.add_child(status_panel)
|
vbox.add_child(status_panel)
|
||||||
|
|
||||||
|
|
@ -321,15 +359,22 @@ func _build_ui() -> void:
|
||||||
# in welchem Werkzeug er ist, plus Hint für den Flugmodus (Umschalt+F).
|
# in welchem Werkzeug er ist, plus Hint für den Flugmodus (Umschalt+F).
|
||||||
tool_label = Label.new()
|
tool_label = Label.new()
|
||||||
tool_label.modulate = Color(0.55, 0.8, 1.0)
|
tool_label.modulate = Color(0.55, 0.8, 1.0)
|
||||||
|
# Karte t_297221b5: Statuszeilen-Labels egalisiert (min-size-Druck bei 1.5×)
|
||||||
|
tool_label.clip_text = true
|
||||||
|
tool_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
status_h.add_child(tool_label)
|
status_h.add_child(tool_label)
|
||||||
|
|
||||||
stats_objects = Label.new()
|
stats_objects = Label.new()
|
||||||
stats_objects.modulate = Color(1, 1, 1, 0.75)
|
stats_objects.modulate = Color(1, 1, 1, 0.75)
|
||||||
|
stats_objects.clip_text = true
|
||||||
|
stats_objects.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
status_h.add_child(stats_objects)
|
status_h.add_child(stats_objects)
|
||||||
|
|
||||||
# Terrain-Anteil + Editor-FPS (Perf-Zähler, Karte t_9aba04f1)
|
# Terrain-Anteil + Editor-FPS (Perf-Zähler, Karte t_9aba04f1)
|
||||||
stats_terrain = Label.new()
|
stats_terrain = Label.new()
|
||||||
stats_terrain.modulate = Color(1, 1, 1, 0.75)
|
stats_terrain.modulate = Color(1, 1, 1, 0.75)
|
||||||
|
stats_terrain.clip_text = true
|
||||||
|
stats_terrain.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
status_h.add_child(stats_terrain)
|
status_h.add_child(stats_terrain)
|
||||||
var fps_timer := Timer.new()
|
var fps_timer := Timer.new()
|
||||||
fps_timer.wait_time = 0.5
|
fps_timer.wait_time = 0.5
|
||||||
|
|
@ -353,7 +398,7 @@ func _build_ui() -> void:
|
||||||
open_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
open_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||||
open_dialog.title = "Map öffnen"
|
open_dialog.title = "Map öffnen"
|
||||||
open_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"])
|
open_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"])
|
||||||
open_dialog.size = Vector2i(800, 560)
|
open_dialog.size = UIScale.v2i(800, 560)
|
||||||
open_dialog.file_selected.connect(_on_open_selected)
|
open_dialog.file_selected.connect(_on_open_selected)
|
||||||
add_child(open_dialog)
|
add_child(open_dialog)
|
||||||
|
|
||||||
|
|
@ -362,7 +407,7 @@ func _build_ui() -> void:
|
||||||
save_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
save_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||||
save_dialog.title = "Map speichern unter"
|
save_dialog.title = "Map speichern unter"
|
||||||
save_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"])
|
save_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"])
|
||||||
save_dialog.size = Vector2i(800, 560)
|
save_dialog.size = UIScale.v2i(800, 560)
|
||||||
save_dialog.file_selected.connect(_on_save_selected)
|
save_dialog.file_selected.connect(_on_save_selected)
|
||||||
add_child(save_dialog)
|
add_child(save_dialog)
|
||||||
|
|
||||||
|
|
@ -371,7 +416,7 @@ func _build_ui() -> void:
|
||||||
error_dialog.ok_button_text = "Alles klar"
|
error_dialog.ok_button_text = "Alles klar"
|
||||||
error_text = Label.new()
|
error_text = Label.new()
|
||||||
error_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
error_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
error_text.custom_minimum_size = Vector2(560, 0)
|
error_text.custom_minimum_size = UIScale.v2(560, 0)
|
||||||
error_dialog.add_child(error_text)
|
error_dialog.add_child(error_text)
|
||||||
add_child(error_dialog)
|
add_child(error_dialog)
|
||||||
|
|
||||||
|
|
@ -381,7 +426,7 @@ func _build_ui() -> void:
|
||||||
check_dialog.ok_button_text = "Danke!"
|
check_dialog.ok_button_text = "Danke!"
|
||||||
check_text = Label.new()
|
check_text = Label.new()
|
||||||
check_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
check_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
check_text.custom_minimum_size = Vector2(620, 0)
|
check_text.custom_minimum_size = UIScale.v2(620, 0)
|
||||||
check_dialog.add_child(check_text)
|
check_dialog.add_child(check_text)
|
||||||
add_child(check_dialog)
|
add_child(check_dialog)
|
||||||
|
|
||||||
|
|
@ -404,7 +449,7 @@ func _build_ui() -> void:
|
||||||
overlay_style.border_color = Color(0.35, 0.55, 0.7)
|
overlay_style.border_color = Color(0.35, 0.55, 0.7)
|
||||||
overlay_style.set_border_width_all(2)
|
overlay_style.set_border_width_all(2)
|
||||||
overlay_style.set_corner_radius_all(8)
|
overlay_style.set_corner_radius_all(8)
|
||||||
overlay_style.set_content_margin_all(14)
|
overlay_style.set_content_margin_all(UIScale.px(14))
|
||||||
help_overlay.add_theme_stylebox_override("panel", overlay_style)
|
help_overlay.add_theme_stylebox_override("panel", overlay_style)
|
||||||
# Zentriertes Panel statt Vollbild: der Editor bleibt rundherum bedienbar
|
# Zentriertes Panel statt Vollbild: der Editor bleibt rundherum bedienbar
|
||||||
# (nicht-blockierend), das Panel ist groß genug für die ganze Tabelle.
|
# (nicht-blockierend), das Panel ist groß genug für die ganze Tabelle.
|
||||||
|
|
@ -413,20 +458,20 @@ func _build_ui() -> void:
|
||||||
help_overlay.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
|
help_overlay.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
|
||||||
help_overlay.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
help_overlay.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||||
help_overlay.grow_vertical = Control.GROW_DIRECTION_BOTH
|
help_overlay.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||||
help_overlay.custom_minimum_size = Vector2(880, 640)
|
help_overlay.custom_minimum_size = UIScale.v2(880, 640)
|
||||||
help_overlay.visible = false
|
help_overlay.visible = false
|
||||||
var overlay_box := VBoxContainer.new()
|
var overlay_box := VBoxContainer.new()
|
||||||
overlay_box.add_theme_constant_override("separation", 6)
|
overlay_box.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
help_overlay.add_child(overlay_box)
|
help_overlay.add_child(overlay_box)
|
||||||
help_overlay_title = Label.new()
|
help_overlay_title = Label.new()
|
||||||
help_overlay_title.text = "Kurzhilfe — alle Tasten auf einen Blick (H oder Esc schließt)"
|
help_overlay_title.text = "Kurzhilfe — alle Tasten auf einen Blick (H oder Esc schließt)"
|
||||||
help_overlay_title.add_theme_font_size_override("font_size", 20)
|
help_overlay_title.add_theme_font_size_override("font_size", UIScale.f(20))
|
||||||
help_overlay_title.add_theme_color_override("font_color", Color(0.55, 0.8, 1.0))
|
help_overlay_title.add_theme_color_override("font_color", Color(0.55, 0.8, 1.0))
|
||||||
overlay_box.add_child(help_overlay_title)
|
overlay_box.add_child(help_overlay_title)
|
||||||
help_overlay_text = RichTextLabel.new()
|
help_overlay_text = RichTextLabel.new()
|
||||||
help_overlay_text.bbcode_enabled = true
|
help_overlay_text.bbcode_enabled = true
|
||||||
help_overlay_text.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
help_overlay_text.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
help_overlay_text.custom_minimum_size = Vector2(0, 0)
|
help_overlay_text.custom_minimum_size = UIScale.v2(0, 0)
|
||||||
help_overlay_text.text = HelpText.text()
|
help_overlay_text.text = HelpText.text()
|
||||||
overlay_box.add_child(help_overlay_text)
|
overlay_box.add_child(help_overlay_text)
|
||||||
add_child(help_overlay)
|
add_child(help_overlay)
|
||||||
|
|
@ -434,10 +479,19 @@ func _build_ui() -> void:
|
||||||
|
|
||||||
func _build_left_panel() -> PanelContainer:
|
func _build_left_panel() -> PanelContainer:
|
||||||
var panel := PanelContainer.new()
|
var panel := PanelContainer.new()
|
||||||
panel.custom_minimum_size = Vector2(280, 0)
|
panel.custom_minimum_size = UIScale.v2(280, 0)
|
||||||
|
# Karte t_297221b5: das GESAMTE linke Panel scrollt, wenn es bei großer
|
||||||
|
# UI-Stufe höher als das Fenster wird (wie die rechte Spalte es schon tut).
|
||||||
|
# Bei „Klein“ (alles passt) bleibt das Verhalten exakt wie vorher: der
|
||||||
|
# innere Inhalt Expand-FILL't, die Bibliothek nutzt den ganzen Platz.
|
||||||
|
var panel_scroll := ScrollContainer.new()
|
||||||
|
panel_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
|
panel.add_child(panel_scroll)
|
||||||
var vbox := VBoxContainer.new()
|
var vbox := VBoxContainer.new()
|
||||||
vbox.add_theme_constant_override("separation", 6)
|
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
panel.add_child(vbox)
|
vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
vbox.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
|
panel_scroll.add_child(vbox)
|
||||||
|
|
||||||
vbox.add_child(_header_label("Objekt-Bibliothek"))
|
vbox.add_child(_header_label("Objekt-Bibliothek"))
|
||||||
vbox.add_child(_muted_label("Objekt anklicken, dann in die Karte klicken — der Modus bleibt an bis ESC (Rechte Maustaste = Flug)."))
|
vbox.add_child(_muted_label("Objekt anklicken, dann in die Karte klicken — der Modus bleibt an bis ESC (Rechte Maustaste = Flug)."))
|
||||||
|
|
@ -447,14 +501,14 @@ func _build_left_panel() -> PanelContainer:
|
||||||
library_search = LineEdit.new()
|
library_search = LineEdit.new()
|
||||||
library_search.placeholder_text = "Suchen (Name oder ID)…"
|
library_search.placeholder_text = "Suchen (Name oder ID)…"
|
||||||
library_search.clear_button_enabled = true
|
library_search.clear_button_enabled = true
|
||||||
library_search.custom_minimum_size = Vector2(0, 36)
|
library_search.custom_minimum_size = UIScale.v2(0, 36)
|
||||||
library_search.text_changed.connect(_apply_library_filter)
|
library_search.text_changed.connect(_apply_library_filter)
|
||||||
library_search.text_submitted.connect(func(_t: String) -> void: library_search.release_focus())
|
library_search.text_submitted.connect(func(_t: String) -> void: library_search.release_focus())
|
||||||
vbox.add_child(library_search)
|
vbox.add_child(library_search)
|
||||||
|
|
||||||
# „Alle auf" / „Alle zu" (t_59fc0f21): Klapp-Zustand aller Gruppen auf einmal.
|
# „Alle auf" / „Alle zu" (t_59fc0f21): Klapp-Zustand aller Gruppen auf einmal.
|
||||||
var group_btns := HBoxContainer.new()
|
var group_btns := HBoxContainer.new()
|
||||||
group_btns.add_theme_constant_override("separation", 6)
|
group_btns.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
library_open_all_btn = Button.new()
|
library_open_all_btn = Button.new()
|
||||||
library_open_all_btn.text = "Alle auf"
|
library_open_all_btn.text = "Alle auf"
|
||||||
library_open_all_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
library_open_all_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
|
@ -471,12 +525,12 @@ func _build_left_panel() -> PanelContainer:
|
||||||
|
|
||||||
var scroll := ScrollContainer.new()
|
var scroll := ScrollContainer.new()
|
||||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
scroll.custom_minimum_size = Vector2(0, 280)
|
scroll.custom_minimum_size = UIScale.v2(0, 280)
|
||||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
vbox.add_child(scroll)
|
vbox.add_child(scroll)
|
||||||
library_box = VBoxContainer.new()
|
library_box = VBoxContainer.new()
|
||||||
library_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
library_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
library_box.add_theme_constant_override("separation", 4)
|
library_box.add_theme_constant_override("separation", UIScale.px(4))
|
||||||
scroll.add_child(library_box)
|
scroll.add_child(library_box)
|
||||||
_fill_library()
|
_fill_library()
|
||||||
|
|
||||||
|
|
@ -485,14 +539,14 @@ func _build_left_panel() -> PanelContainer:
|
||||||
|
|
||||||
var snap_btn := Button.new()
|
var snap_btn := Button.new()
|
||||||
snap_btn.text = "⚡ Alles auf den Boden setzen"
|
snap_btn.text = "⚡ Alles auf den Boden setzen"
|
||||||
snap_btn.custom_minimum_size = Vector2(0, 44)
|
snap_btn.custom_minimum_size = UIScale.v2(0, 44)
|
||||||
snap_btn.tooltip_text = "Setzt ALLE Objekte auf die Terrain-Höhe — z. B. nach dem Terrain-Formen, wenn etwas in der Luft hängt"
|
snap_btn.tooltip_text = "Setzt ALLE Objekte auf die Terrain-Höhe — z. B. nach dem Terrain-Formen, wenn etwas in der Luft hängt"
|
||||||
snap_btn.pressed.connect(_on_snap_to_ground)
|
snap_btn.pressed.connect(_on_snap_to_ground)
|
||||||
vbox.add_child(snap_btn)
|
vbox.add_child(snap_btn)
|
||||||
|
|
||||||
var grid_check := CheckButton.new()
|
var grid_check := CheckButton.new()
|
||||||
grid_check.text = "Raster-Snap (1 m)"
|
grid_check.text = "Raster-Snap (1 m)"
|
||||||
grid_check.custom_minimum_size = Vector2(0, 40)
|
grid_check.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
grid_check.tooltip_text = "Neue und verschobene Objekte rasten auf ein 1-Meter-Raster ein"
|
grid_check.tooltip_text = "Neue und verschobene Objekte rasten auf ein 1-Meter-Raster ein"
|
||||||
grid_check.toggled.connect(_on_grid_snap_changed)
|
grid_check.toggled.connect(_on_grid_snap_changed)
|
||||||
vbox.add_child(grid_check)
|
vbox.add_child(grid_check)
|
||||||
|
|
@ -503,7 +557,7 @@ func _build_left_panel() -> PanelContainer:
|
||||||
|
|
||||||
content_tree = Tree.new()
|
content_tree = Tree.new()
|
||||||
content_tree.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
content_tree.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
content_tree.custom_minimum_size = Vector2(0, 160)
|
content_tree.custom_minimum_size = UIScale.v2(0, 160)
|
||||||
content_tree.allow_reselect = true
|
content_tree.allow_reselect = true
|
||||||
content_tree.select_mode = Tree.SELECT_MULTI # Strg+Klick im Baum = Mehrfachauswahl (t_c46d31c2)
|
content_tree.select_mode = Tree.SELECT_MULTI # Strg+Klick im Baum = Mehrfachauswahl (t_c46d31c2)
|
||||||
content_tree.hide_root = true
|
content_tree.hide_root = true
|
||||||
|
|
@ -537,19 +591,19 @@ func _fill_library() -> void:
|
||||||
header.toggle_mode = true
|
header.toggle_mode = true
|
||||||
header.button_pressed = is_open
|
header.button_pressed = is_open
|
||||||
header.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
header.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||||
header.custom_minimum_size = Vector2(0, 34)
|
header.custom_minimum_size = UIScale.v2(0, 34)
|
||||||
header.tooltip_text = "Gruppe „%s“ auf-/zuklappen" % g_label
|
header.tooltip_text = "Gruppe „%s“ auf-/zuklappen" % g_label
|
||||||
header.add_theme_font_size_override("font_size", 18)
|
header.add_theme_font_size_override("font_size", UIScale.f(18))
|
||||||
header.add_theme_font_override("font", _library_header_font())
|
header.add_theme_font_override("font", _library_header_font())
|
||||||
library_box.add_child(header)
|
library_box.add_child(header)
|
||||||
var group_box := VBoxContainer.new()
|
var group_box := VBoxContainer.new()
|
||||||
group_box.add_theme_constant_override("separation", 4)
|
group_box.add_theme_constant_override("separation", UIScale.px(4))
|
||||||
group_box.visible = is_open
|
group_box.visible = is_open
|
||||||
var grid := GridContainer.new()
|
var grid := GridContainer.new()
|
||||||
grid.columns = 2
|
grid.columns = 2
|
||||||
grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
grid.add_theme_constant_override("h_separation", 6)
|
grid.add_theme_constant_override("h_separation", UIScale.px(6))
|
||||||
grid.add_theme_constant_override("v_separation", 6)
|
grid.add_theme_constant_override("v_separation", UIScale.px(6))
|
||||||
group_box.add_child(grid)
|
group_box.add_child(grid)
|
||||||
library_box.add_child(group_box)
|
library_box.add_child(group_box)
|
||||||
header.toggled.connect(_on_library_group_toggled.bind(g_key, g_label, g_ids.size(), header, group_box))
|
header.toggled.connect(_on_library_group_toggled.bind(g_key, g_label, g_ids.size(), header, group_box))
|
||||||
|
|
@ -659,7 +713,7 @@ func _add_library_button(oid: String, parent: Container) -> void:
|
||||||
btn.toggle_mode = true
|
btn.toggle_mode = true
|
||||||
btn.button_pressed = false
|
btn.button_pressed = false
|
||||||
btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
btn.custom_minimum_size = Vector2(0, 126)
|
btn.custom_minimum_size = UIScale.v2(0, 126)
|
||||||
btn.tooltip_text = "%s · %d Polys · %s" % [name_txt, poly, oid]
|
btn.tooltip_text = "%s · %d Polys · %s" % [name_txt, poly, oid]
|
||||||
btn.pressed.connect(_on_library_pressed.bind(oid))
|
btn.pressed.connect(_on_library_pressed.bind(oid))
|
||||||
var tex := LibraryBrowser.load_thumb(oid)
|
var tex := LibraryBrowser.load_thumb(oid)
|
||||||
|
|
@ -667,18 +721,18 @@ func _add_library_button(oid: String, parent: Container) -> void:
|
||||||
# Kein Thumbnail → Text-Zeile wie früher (Fehlertoleranz, t_3a998ae4).
|
# Kein Thumbnail → Text-Zeile wie früher (Fehlertoleranz, t_3a998ae4).
|
||||||
btn.text = "%s · %d Polys" % [name_txt, poly]
|
btn.text = "%s · %d Polys" % [name_txt, poly]
|
||||||
btn.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
btn.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||||
btn.custom_minimum_size = Vector2(0, 44)
|
btn.custom_minimum_size = UIScale.v2(0, 44)
|
||||||
else:
|
else:
|
||||||
# Kachel (t_59fc0f21): 96-px-Vorschaubild oben, Name klein darunter.
|
# Kachel (t_59fc0f21): 96-px-Vorschaubild oben, Name klein darunter.
|
||||||
# Kinder ignorieren die Maus, damit die Klicks am Button ankommen.
|
# Kinder ignorieren die Maus, damit die Klicks am Button ankommen.
|
||||||
var tile := VBoxContainer.new()
|
var tile := VBoxContainer.new()
|
||||||
tile.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
tile.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
tile.set_anchors_preset(Control.PRESET_FULL_RECT)
|
tile.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
tile.add_theme_constant_override("separation", 2)
|
tile.add_theme_constant_override("separation", UIScale.px(2))
|
||||||
btn.add_child(tile)
|
btn.add_child(tile)
|
||||||
var tr := TextureRect.new()
|
var tr := TextureRect.new()
|
||||||
tr.texture = tex
|
tr.texture = tex
|
||||||
tr.custom_minimum_size = Vector2(LibraryBrowser.THUMB_DISPLAY_PX, LibraryBrowser.THUMB_DISPLAY_PX)
|
tr.custom_minimum_size = UIScale.v2(LibraryBrowser.THUMB_DISPLAY_PX, LibraryBrowser.THUMB_DISPLAY_PX)
|
||||||
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
tr.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
tr.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||||
|
|
@ -686,7 +740,7 @@ func _add_library_button(oid: String, parent: Container) -> void:
|
||||||
tile.add_child(tr)
|
tile.add_child(tr)
|
||||||
var lbl := Label.new()
|
var lbl := Label.new()
|
||||||
lbl.text = name_txt
|
lbl.text = name_txt
|
||||||
lbl.add_theme_font_size_override("font_size", 11)
|
lbl.add_theme_font_size_override("font_size", UIScale.f(11))
|
||||||
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
lbl.clip_text = true
|
lbl.clip_text = true
|
||||||
lbl.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
lbl.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
|
|
@ -752,10 +806,20 @@ func _build_center_viewport() -> SubViewportContainer:
|
||||||
help_label = Label.new()
|
help_label = Label.new()
|
||||||
help_label.text = "Linksklick: auswählen & ziehen · Umschalt+Klick: Auswahl erweitern · Rahmen aufziehen: mehrere wählen · Strg+G: gruppieren\nR / Mausrad: drehen · Strg+D: duplizieren · Bild↑/↓: anheben/absenken · Spiegeln rechts · Entf: löschen · Strg+Z: zurück"
|
help_label.text = "Linksklick: auswählen & ziehen · Umschalt+Klick: Auswahl erweitern · Rahmen aufziehen: mehrere wählen · Strg+G: gruppieren\nR / Mausrad: drehen · Strg+D: duplizieren · Bild↑/↓: anheben/absenken · Spiegeln rechts · Entf: löschen · Strg+Z: zurück"
|
||||||
help_label.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
|
help_label.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
|
||||||
help_label.offset_left = 12
|
help_label.offset_left = UIScale.px(12)
|
||||||
help_label.offset_bottom = -8
|
help_label.offset_bottom = UIScale.px(-8)
|
||||||
help_label.offset_top = -46
|
help_label.offset_top = UIScale.px(-46)
|
||||||
help_label.modulate = Color(1, 1, 1, 0.65)
|
help_label.modulate = Color(1, 1, 1, 0.65)
|
||||||
|
# Karte t_297221b5: OHNE Umbruch würde die Mindestbreite des langen Texts
|
||||||
|
# bei Stufe „Groß“ die 3D-Ansicht breiter zwingen als das Fenster ist —
|
||||||
|
# die rechte Spalte rutschte aus dem Bildschirm. Mit Umbruch bleibt die
|
||||||
|
# Mindestbreite klein, der Hinweis legt sich bei Bedarf in mehr Zeilen.
|
||||||
|
help_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
# … und wenn der umgebrochene Text höher wird als der reservierte Streifen,
|
||||||
|
# wächst er nach OBEN (in die 3D-Ansicht) statt nach unten über die
|
||||||
|
# Statuszeile (grow-Richtung = das Godot-Mittel gegen Mindestgrößen-Überlauf
|
||||||
|
# bei Anker-Controls).
|
||||||
|
help_label.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||||
container.add_child(help_label)
|
container.add_child(help_label)
|
||||||
|
|
||||||
# Rahmen-Ausziehen (Karte t_f6f29d86): halbtransparentes Rechteck über
|
# Rahmen-Ausziehen (Karte t_f6f29d86): halbtransparentes Rechteck über
|
||||||
|
|
@ -792,13 +856,13 @@ func _draw_rubberband() -> void:
|
||||||
|
|
||||||
func _build_right_panel() -> PanelContainer:
|
func _build_right_panel() -> PanelContainer:
|
||||||
var panel := PanelContainer.new()
|
var panel := PanelContainer.new()
|
||||||
panel.custom_minimum_size = Vector2(320, 0)
|
panel.custom_minimum_size = UIScale.v2(320, 0)
|
||||||
var scroll := ScrollContainer.new()
|
var scroll := ScrollContainer.new()
|
||||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
panel.add_child(scroll)
|
panel.add_child(scroll)
|
||||||
var vbox := VBoxContainer.new()
|
var vbox := VBoxContainer.new()
|
||||||
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
vbox.add_theme_constant_override("separation", 8)
|
vbox.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
scroll.add_child(vbox)
|
scroll.add_child(vbox)
|
||||||
|
|
||||||
# --- Auswahl (Anfassen/Greifen) ---
|
# --- Auswahl (Anfassen/Greifen) ---
|
||||||
|
|
@ -808,24 +872,24 @@ func _build_right_panel() -> PanelContainer:
|
||||||
sel_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
sel_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
vbox.add_child(sel_label)
|
vbox.add_child(sel_label)
|
||||||
var sel_btns := HBoxContainer.new()
|
var sel_btns := HBoxContainer.new()
|
||||||
sel_btns.add_theme_constant_override("separation", 8)
|
sel_btns.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
vbox.add_child(sel_btns)
|
vbox.add_child(sel_btns)
|
||||||
sel_rotate_btn = Button.new()
|
sel_rotate_btn = Button.new()
|
||||||
sel_rotate_btn.text = "Drehen (R)"
|
sel_rotate_btn.text = "Drehen (R)"
|
||||||
sel_rotate_btn.custom_minimum_size = Vector2(140, 40)
|
sel_rotate_btn.custom_minimum_size = UIScale.v2(140, 40)
|
||||||
sel_rotate_btn.tooltip_text = "Ausgewähltes Objekt 15° drehen (auch: Mausrad)"
|
sel_rotate_btn.tooltip_text = "Ausgewähltes Objekt 15° drehen (auch: Mausrad)"
|
||||||
sel_rotate_btn.pressed.connect(func() -> void: interaction.rotate_selected(15.0))
|
sel_rotate_btn.pressed.connect(func() -> void: interaction.rotate_selected(15.0))
|
||||||
sel_btns.add_child(sel_rotate_btn)
|
sel_btns.add_child(sel_rotate_btn)
|
||||||
sel_delete_btn = Button.new()
|
sel_delete_btn = Button.new()
|
||||||
sel_delete_btn.text = "Löschen (Entf)"
|
sel_delete_btn.text = "Löschen (Entf)"
|
||||||
sel_delete_btn.custom_minimum_size = Vector2(140, 40)
|
sel_delete_btn.custom_minimum_size = UIScale.v2(140, 40)
|
||||||
sel_delete_btn.tooltip_text = "Ausgewähltes Objekt löschen (Strg+Z holt es zurück)"
|
sel_delete_btn.tooltip_text = "Ausgewähltes Objekt löschen (Strg+Z holt es zurück)"
|
||||||
sel_delete_btn.pressed.connect(interaction.delete_selected)
|
sel_delete_btn.pressed.connect(interaction.delete_selected)
|
||||||
sel_btns.add_child(sel_delete_btn)
|
sel_btns.add_child(sel_delete_btn)
|
||||||
|
|
||||||
# Objekt-Ops (Karte t_7567a06f): Duplizieren · Spiegeln · Anheben/Absenken.
|
# Objekt-Ops (Karte t_7567a06f): Duplizieren · Spiegeln · Anheben/Absenken.
|
||||||
var dup_row := HBoxContainer.new()
|
var dup_row := HBoxContainer.new()
|
||||||
dup_row.add_theme_constant_override("separation", 8)
|
dup_row.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
vbox.add_child(dup_row)
|
vbox.add_child(dup_row)
|
||||||
sel_dup_btn = _sel_op_button("⧉ Duplizieren (Strg+D)",
|
sel_dup_btn = _sel_op_button("⧉ Duplizieren (Strg+D)",
|
||||||
"Legt eine Kopie daneben — leicht versetzt, direkt angewählt. Strg+D geht auch.",
|
"Legt eine Kopie daneben — leicht versetzt, direkt angewählt. Strg+D geht auch.",
|
||||||
|
|
@ -833,7 +897,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
dup_row.add_child(sel_dup_btn)
|
dup_row.add_child(sel_dup_btn)
|
||||||
|
|
||||||
var mirror_row := HBoxContainer.new()
|
var mirror_row := HBoxContainer.new()
|
||||||
mirror_row.add_theme_constant_override("separation", 8)
|
mirror_row.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
vbox.add_child(mirror_row)
|
vbox.add_child(mirror_row)
|
||||||
sel_mirror_x_btn = _sel_op_button("⇄ Links↔Rechts",
|
sel_mirror_x_btn = _sel_op_button("⇄ Links↔Rechts",
|
||||||
"Spiegelt das Objekt an seiner Stelle (links↔rechts, X-Achse) — die Blickrichtung wird mitgespiegelt.",
|
"Spiegelt das Objekt an seiner Stelle (links↔rechts, X-Achse) — die Blickrichtung wird mitgespiegelt.",
|
||||||
|
|
@ -845,7 +909,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
mirror_row.add_child(sel_mirror_z_btn)
|
mirror_row.add_child(sel_mirror_z_btn)
|
||||||
|
|
||||||
var lift_row := HBoxContainer.new()
|
var lift_row := HBoxContainer.new()
|
||||||
lift_row.add_theme_constant_override("separation", 8)
|
lift_row.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
vbox.add_child(lift_row)
|
vbox.add_child(lift_row)
|
||||||
sel_raise_btn = _sel_op_button("▲ Anheben (Bild↑)",
|
sel_raise_btn = _sel_op_button("▲ Anheben (Bild↑)",
|
||||||
"Objekt 0,25 m anheben — es schwebt dann, auch wenn der Boden darunter anders ist. Bild↑ geht auch.",
|
"Objekt 0,25 m anheben — es schwebt dann, auch wenn der Boden darunter anders ist. Bild↑ geht auch.",
|
||||||
|
|
@ -858,17 +922,17 @@ func _build_right_panel() -> PanelContainer:
|
||||||
|
|
||||||
# Gruppen-Buttons (Karte t_f6f29d86): Mehrfachauswahl → Gruppe
|
# Gruppen-Buttons (Karte t_f6f29d86): Mehrfachauswahl → Gruppe
|
||||||
var group_btns := HBoxContainer.new()
|
var group_btns := HBoxContainer.new()
|
||||||
group_btns.add_theme_constant_override("separation", 8)
|
group_btns.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
vbox.add_child(group_btns)
|
vbox.add_child(group_btns)
|
||||||
sel_group_btn = Button.new()
|
sel_group_btn = Button.new()
|
||||||
sel_group_btn.text = "🔗 Gruppieren"
|
sel_group_btn.text = "🔗 Gruppieren"
|
||||||
sel_group_btn.custom_minimum_size = Vector2(150, 40)
|
sel_group_btn.custom_minimum_size = UIScale.v2(150, 40)
|
||||||
sel_group_btn.tooltip_text = "Ausgewählte Objekte zu einer Gruppe verbinden (Strg+G) — sie ziehen und drehen dann gemeinsam"
|
sel_group_btn.tooltip_text = "Ausgewählte Objekte zu einer Gruppe verbinden (Strg+G) — sie ziehen und drehen dann gemeinsam"
|
||||||
sel_group_btn.pressed.connect(func() -> void: interaction.group_selection())
|
sel_group_btn.pressed.connect(func() -> void: interaction.group_selection())
|
||||||
group_btns.add_child(sel_group_btn)
|
group_btns.add_child(sel_group_btn)
|
||||||
sel_ungroup_btn = Button.new()
|
sel_ungroup_btn = Button.new()
|
||||||
sel_ungroup_btn.text = "⛓ Gruppe lösen"
|
sel_ungroup_btn.text = "⛓ Gruppe lösen"
|
||||||
sel_ungroup_btn.custom_minimum_size = Vector2(150, 40)
|
sel_ungroup_btn.custom_minimum_size = UIScale.v2(150, 40)
|
||||||
sel_ungroup_btn.tooltip_text = "Die Gruppe der Auswahl wieder auflösen (Strg+Umschalt+G) — Objekte bleiben, nur die Bindung löst sich"
|
sel_ungroup_btn.tooltip_text = "Die Gruppe der Auswahl wieder auflösen (Strg+Umschalt+G) — Objekte bleiben, nur die Bindung löst sich"
|
||||||
sel_ungroup_btn.pressed.connect(func() -> void: interaction.ungroup_selection())
|
sel_ungroup_btn.pressed.connect(func() -> void: interaction.ungroup_selection())
|
||||||
group_btns.add_child(sel_ungroup_btn)
|
group_btns.add_child(sel_ungroup_btn)
|
||||||
|
|
@ -917,20 +981,20 @@ func _build_right_panel() -> PanelContainer:
|
||||||
# --- Terrain-Werkzeug (Karte t_9aba04f1): großer deutscher Button ---
|
# --- Terrain-Werkzeug (Karte t_9aba04f1): großer deutscher Button ---
|
||||||
terrain_edit_btn = Button.new()
|
terrain_edit_btn = Button.new()
|
||||||
terrain_edit_btn.text = "⛰ Terrain formen"
|
terrain_edit_btn.text = "⛰ Terrain formen"
|
||||||
terrain_edit_btn.custom_minimum_size = Vector2(0, 48)
|
terrain_edit_btn.custom_minimum_size = UIScale.v2(0, 48)
|
||||||
terrain_edit_btn.toggle_mode = true
|
terrain_edit_btn.toggle_mode = true
|
||||||
terrain_edit_btn.tooltip_text = "Boden heben, senken und glätten — direkt in der Karte malen"
|
terrain_edit_btn.tooltip_text = "Boden heben, senken und glätten — direkt in der Karte malen"
|
||||||
terrain_edit_btn.pressed.connect(_on_terrain_btn_pressed)
|
terrain_edit_btn.pressed.connect(_on_terrain_btn_pressed)
|
||||||
vbox.add_child(terrain_edit_btn)
|
vbox.add_child(terrain_edit_btn)
|
||||||
|
|
||||||
terrain_panel = VBoxContainer.new()
|
terrain_panel = VBoxContainer.new()
|
||||||
terrain_panel.add_theme_constant_override("separation", 6)
|
terrain_panel.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
terrain_panel.visible = false
|
terrain_panel.visible = false
|
||||||
vbox.add_child(terrain_panel)
|
vbox.add_child(terrain_panel)
|
||||||
|
|
||||||
terrain_panel.add_child(_field_label("Pinsel-Werkzeug:"))
|
terrain_panel.add_child(_field_label("Pinsel-Werkzeug:"))
|
||||||
var mode_row := HBoxContainer.new()
|
var mode_row := HBoxContainer.new()
|
||||||
mode_row.add_theme_constant_override("separation", 6)
|
mode_row.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
terrain_panel.add_child(mode_row)
|
terrain_panel.add_child(mode_row)
|
||||||
var mode_group := ButtonGroup.new()
|
var mode_group := ButtonGroup.new()
|
||||||
terrain_mode_raise = _terrain_mode_button("Heben ⬆", mode_group)
|
terrain_mode_raise = _terrain_mode_button("Heben ⬆", mode_group)
|
||||||
|
|
@ -949,19 +1013,19 @@ func _build_right_panel() -> PanelContainer:
|
||||||
|
|
||||||
# Material-Palette (Karte t_603ec1a8): nur sichtbar im Malen-Modus.
|
# Material-Palette (Karte t_603ec1a8): nur sichtbar im Malen-Modus.
|
||||||
terrain_palette = VBoxContainer.new()
|
terrain_palette = VBoxContainer.new()
|
||||||
terrain_palette.add_theme_constant_override("separation", 4)
|
terrain_palette.add_theme_constant_override("separation", UIScale.px(4))
|
||||||
terrain_palette.visible = false
|
terrain_palette.visible = false
|
||||||
terrain_panel.add_child(terrain_palette)
|
terrain_panel.add_child(terrain_palette)
|
||||||
terrain_palette.add_child(_field_label("Material:"))
|
terrain_palette.add_child(_field_label("Material:"))
|
||||||
var mat_row := HBoxContainer.new()
|
var mat_row := HBoxContainer.new()
|
||||||
mat_row.add_theme_constant_override("separation", 6)
|
mat_row.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
terrain_palette.add_child(mat_row)
|
terrain_palette.add_child(mat_row)
|
||||||
var mat_group := ButtonGroup.new()
|
var mat_group := ButtonGroup.new()
|
||||||
var mat_labels := ["🌿 Gras", "🏖 Sand", "🪨 Fels"]
|
var mat_labels := ["🌿 Gras", "🏖 Sand", "🪨 Fels"]
|
||||||
for mat_id in SvmapIO.MATERIAL_IDS.size():
|
for mat_id in SvmapIO.MATERIAL_IDS.size():
|
||||||
var mat_btn := Button.new()
|
var mat_btn := Button.new()
|
||||||
mat_btn.text = str(mat_labels[mat_id])
|
mat_btn.text = str(mat_labels[mat_id])
|
||||||
mat_btn.custom_minimum_size = Vector2(0, 40)
|
mat_btn.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
mat_btn.toggle_mode = true
|
mat_btn.toggle_mode = true
|
||||||
mat_btn.button_group = mat_group
|
mat_btn.button_group = mat_group
|
||||||
mat_btn.tooltip_text = "Malen: %s" % str(SvmapIO.MATERIAL_IDS[mat_id])
|
mat_btn.tooltip_text = "Malen: %s" % str(SvmapIO.MATERIAL_IDS[mat_id])
|
||||||
|
|
@ -974,7 +1038,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
# Pinselgrößen-Presets (Karte t_603ec1a8): drei große Buttons + freier Slider.
|
# Pinselgrößen-Presets (Karte t_603ec1a8): drei große Buttons + freier Slider.
|
||||||
terrain_panel.add_child(_field_label("Pinselgröße:"))
|
terrain_panel.add_child(_field_label("Pinselgröße:"))
|
||||||
var size_row := HBoxContainer.new()
|
var size_row := HBoxContainer.new()
|
||||||
size_row.add_theme_constant_override("separation", 6)
|
size_row.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
terrain_panel.add_child(size_row)
|
terrain_panel.add_child(size_row)
|
||||||
var size_group := ButtonGroup.new()
|
var size_group := ButtonGroup.new()
|
||||||
terrain_size_btn_small = _terrain_size_button("Klein\n(4 m)", size_group, 4.0)
|
terrain_size_btn_small = _terrain_size_button("Klein\n(4 m)", size_group, 4.0)
|
||||||
|
|
@ -995,7 +1059,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
terrain_radius_slider.max_value = 64.0
|
terrain_radius_slider.max_value = 64.0
|
||||||
terrain_radius_slider.step = 1.0
|
terrain_radius_slider.step = 1.0
|
||||||
terrain_radius_slider.value = 12.0
|
terrain_radius_slider.value = 12.0
|
||||||
terrain_radius_slider.custom_minimum_size = Vector2(0, 32)
|
terrain_radius_slider.custom_minimum_size = UIScale.v2(0, 32)
|
||||||
terrain_radius_slider.value_changed.connect(_on_terrain_radius_changed)
|
terrain_radius_slider.value_changed.connect(_on_terrain_radius_changed)
|
||||||
terrain_panel.add_child(terrain_radius_slider)
|
terrain_panel.add_child(terrain_radius_slider)
|
||||||
|
|
||||||
|
|
@ -1006,7 +1070,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
terrain_strength_slider.max_value = 20.0
|
terrain_strength_slider.max_value = 20.0
|
||||||
terrain_strength_slider.step = 0.5
|
terrain_strength_slider.step = 0.5
|
||||||
terrain_strength_slider.value = 8.0
|
terrain_strength_slider.value = 8.0
|
||||||
terrain_strength_slider.custom_minimum_size = Vector2(0, 32)
|
terrain_strength_slider.custom_minimum_size = UIScale.v2(0, 32)
|
||||||
terrain_strength_slider.value_changed.connect(_on_terrain_strength_changed)
|
terrain_strength_slider.value_changed.connect(_on_terrain_strength_changed)
|
||||||
terrain_panel.add_child(terrain_strength_slider)
|
terrain_panel.add_child(terrain_strength_slider)
|
||||||
|
|
||||||
|
|
@ -1020,27 +1084,27 @@ func _build_right_panel() -> PanelContainer:
|
||||||
spawn_status = Label.new()
|
spawn_status = Label.new()
|
||||||
spawn_status.text = ""
|
spawn_status.text = ""
|
||||||
spawn_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
spawn_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
spawn_status.custom_minimum_size = Vector2(0, 44)
|
spawn_status.custom_minimum_size = UIScale.v2(0, 44)
|
||||||
vbox.add_child(spawn_status)
|
vbox.add_child(spawn_status)
|
||||||
vbox.add_child(_muted_label("Der Check sagt VOR dem Export, was der Server-Loader verlangen wird (mindestens 4 Zonen pro Team, bekannte Objekt-IDs)."))
|
vbox.add_child(_muted_label("Der Check sagt VOR dem Export, was der Server-Loader verlangen wird (mindestens 4 Zonen pro Team, bekannte Objekt-IDs)."))
|
||||||
|
|
||||||
# --- Spawn-Zonen-Werkzeug: großer Button + Bedien-Panel ---
|
# --- Spawn-Zonen-Werkzeug: großer Button + Bedien-Panel ---
|
||||||
spawn_edit_btn = Button.new()
|
spawn_edit_btn = Button.new()
|
||||||
spawn_edit_btn.text = "🚩 Spawn-Zonen malen"
|
spawn_edit_btn.text = "🚩 Spawn-Zonen malen"
|
||||||
spawn_edit_btn.custom_minimum_size = Vector2(0, 48)
|
spawn_edit_btn.custom_minimum_size = UIScale.v2(0, 48)
|
||||||
spawn_edit_btn.toggle_mode = true
|
spawn_edit_btn.toggle_mode = true
|
||||||
spawn_edit_btn.tooltip_text = "Startzonen für Team Blau und Rot auf den Boden malen (TDM)"
|
spawn_edit_btn.tooltip_text = "Startzonen für Team Blau und Rot auf den Boden malen (TDM)"
|
||||||
spawn_edit_btn.pressed.connect(_on_spawn_btn_pressed)
|
spawn_edit_btn.pressed.connect(_on_spawn_btn_pressed)
|
||||||
vbox.add_child(spawn_edit_btn)
|
vbox.add_child(spawn_edit_btn)
|
||||||
|
|
||||||
spawn_panel = VBoxContainer.new()
|
spawn_panel = VBoxContainer.new()
|
||||||
spawn_panel.add_theme_constant_override("separation", 6)
|
spawn_panel.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
spawn_panel.visible = false
|
spawn_panel.visible = false
|
||||||
vbox.add_child(spawn_panel)
|
vbox.add_child(spawn_panel)
|
||||||
|
|
||||||
spawn_panel.add_child(_field_label("Team:"))
|
spawn_panel.add_child(_field_label("Team:"))
|
||||||
var team_row := HBoxContainer.new()
|
var team_row := HBoxContainer.new()
|
||||||
team_row.add_theme_constant_override("separation", 6)
|
team_row.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
spawn_panel.add_child(team_row)
|
spawn_panel.add_child(team_row)
|
||||||
var team_group := ButtonGroup.new()
|
var team_group := ButtonGroup.new()
|
||||||
spawn_team_blue = _spawn_team_button("🔵 Team Blau", team_group, 0)
|
spawn_team_blue = _spawn_team_button("🔵 Team Blau", team_group, 0)
|
||||||
|
|
@ -1056,7 +1120,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
spawn_radius_slider.max_value = SpawnOps.MAX_ZONE_RADIUS
|
spawn_radius_slider.max_value = SpawnOps.MAX_ZONE_RADIUS
|
||||||
spawn_radius_slider.step = 1.0
|
spawn_radius_slider.step = 1.0
|
||||||
spawn_radius_slider.value = SpawnOps.DEFAULT_ZONE_RADIUS
|
spawn_radius_slider.value = SpawnOps.DEFAULT_ZONE_RADIUS
|
||||||
spawn_radius_slider.custom_minimum_size = Vector2(0, 32)
|
spawn_radius_slider.custom_minimum_size = UIScale.v2(0, 32)
|
||||||
spawn_radius_slider.value_changed.connect(_on_spawn_radius_changed)
|
spawn_radius_slider.value_changed.connect(_on_spawn_radius_changed)
|
||||||
spawn_panel.add_child(spawn_radius_slider)
|
spawn_panel.add_child(spawn_radius_slider)
|
||||||
|
|
||||||
|
|
@ -1066,7 +1130,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
spawn_panel.add_child(spawn_sel_label)
|
spawn_panel.add_child(spawn_sel_label)
|
||||||
spawn_delete_btn = Button.new()
|
spawn_delete_btn = Button.new()
|
||||||
spawn_delete_btn.text = "Zone löschen (Entf)"
|
spawn_delete_btn.text = "Zone löschen (Entf)"
|
||||||
spawn_delete_btn.custom_minimum_size = Vector2(0, 40)
|
spawn_delete_btn.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
spawn_delete_btn.tooltip_text = "Die ausgewählte Spawn-Zone löschen (Strg+Z holt sie zurück)"
|
spawn_delete_btn.tooltip_text = "Die ausgewählte Spawn-Zone löschen (Strg+Z holt sie zurück)"
|
||||||
spawn_delete_btn.pressed.connect(_on_spawn_delete_pressed)
|
spawn_delete_btn.pressed.connect(_on_spawn_delete_pressed)
|
||||||
spawn_panel.add_child(spawn_delete_btn)
|
spawn_panel.add_child(spawn_delete_btn)
|
||||||
|
|
@ -1079,22 +1143,22 @@ func _build_right_panel() -> PanelContainer:
|
||||||
vbox.add_child(_separator())
|
vbox.add_child(_separator())
|
||||||
build_edit_btn = Button.new()
|
build_edit_btn = Button.new()
|
||||||
build_edit_btn.text = "🏠 Bauen (Häuser)"
|
build_edit_btn.text = "🏠 Bauen (Häuser)"
|
||||||
build_edit_btn.custom_minimum_size = Vector2(0, 48)
|
build_edit_btn.custom_minimum_size = UIScale.v2(0, 48)
|
||||||
build_edit_btn.toggle_mode = true
|
build_edit_btn.toggle_mode = true
|
||||||
build_edit_btn.tooltip_text = "Fundamente, Wände, Dächer, Treppen und Türen bauen — im 2-Meter-Raster, wie Bauklötze"
|
build_edit_btn.tooltip_text = "Fundamente, Wände, Dächer, Treppen und Türen bauen — im 2-Meter-Raster, wie Bauklötze"
|
||||||
build_edit_btn.pressed.connect(_on_build_btn_pressed)
|
build_edit_btn.pressed.connect(_on_build_btn_pressed)
|
||||||
vbox.add_child(build_edit_btn)
|
vbox.add_child(build_edit_btn)
|
||||||
|
|
||||||
build_panel = VBoxContainer.new()
|
build_panel = VBoxContainer.new()
|
||||||
build_panel.add_theme_constant_override("separation", 6)
|
build_panel.add_theme_constant_override("separation", UIScale.px(6))
|
||||||
build_panel.visible = false
|
build_panel.visible = false
|
||||||
vbox.add_child(build_panel)
|
vbox.add_child(build_panel)
|
||||||
|
|
||||||
build_panel.add_child(_field_label("Bauteil:"))
|
build_panel.add_child(_field_label("Bauteil:"))
|
||||||
var build_grid := GridContainer.new()
|
var build_grid := GridContainer.new()
|
||||||
build_grid.columns = 2
|
build_grid.columns = 2
|
||||||
build_grid.add_theme_constant_override("h_separation", 6)
|
build_grid.add_theme_constant_override("h_separation", UIScale.px(6))
|
||||||
build_grid.add_theme_constant_override("v_separation", 6)
|
build_grid.add_theme_constant_override("v_separation", UIScale.px(6))
|
||||||
build_panel.add_child(build_grid)
|
build_panel.add_child(build_grid)
|
||||||
var build_group := ButtonGroup.new()
|
var build_group := ButtonGroup.new()
|
||||||
for kind_id in BuildOps.KINDS.keys():
|
for kind_id in BuildOps.KINDS.keys():
|
||||||
|
|
@ -1102,7 +1166,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
kind_btn.text = str(BuildOps.KINDS[kind_id]["label"])
|
kind_btn.text = str(BuildOps.KINDS[kind_id]["label"])
|
||||||
kind_btn.toggle_mode = true
|
kind_btn.toggle_mode = true
|
||||||
kind_btn.button_group = build_group
|
kind_btn.button_group = build_group
|
||||||
kind_btn.custom_minimum_size = Vector2(148, 44)
|
kind_btn.custom_minimum_size = UIScale.v2(148, 44)
|
||||||
kind_btn.clip_text = true
|
kind_btn.clip_text = true
|
||||||
kind_btn.tooltip_text = "%s — %s" % [str(BuildOps.KINDS[kind_id]["label"]), str(BuildOps.KINDS[kind_id].get("info", ""))]
|
kind_btn.tooltip_text = "%s — %s" % [str(BuildOps.KINDS[kind_id]["label"]), str(BuildOps.KINDS[kind_id].get("info", ""))]
|
||||||
kind_btn.pressed.connect(_on_build_kind_pressed.bind(str(kind_id)))
|
kind_btn.pressed.connect(_on_build_kind_pressed.bind(str(kind_id)))
|
||||||
|
|
@ -1127,7 +1191,7 @@ func _build_right_panel() -> PanelContainer:
|
||||||
func _sel_op_button(text: String, tip: String, action: Callable) -> Button:
|
func _sel_op_button(text: String, tip: String, action: Callable) -> Button:
|
||||||
var btn := Button.new()
|
var btn := Button.new()
|
||||||
btn.text = text
|
btn.text = text
|
||||||
btn.custom_minimum_size = Vector2(140, 40)
|
btn.custom_minimum_size = UIScale.v2(140, 40)
|
||||||
btn.tooltip_text = tip
|
btn.tooltip_text = tip
|
||||||
btn.pressed.connect(action)
|
btn.pressed.connect(action)
|
||||||
return btn
|
return btn
|
||||||
|
|
@ -1136,7 +1200,7 @@ func _sel_op_button(text: String, tip: String, action: Callable) -> Button:
|
||||||
func _header_label(text: String) -> Label:
|
func _header_label(text: String) -> Label:
|
||||||
var label := Label.new()
|
var label := Label.new()
|
||||||
label.text = text
|
label.text = text
|
||||||
label.add_theme_font_size_override("font_size", 19)
|
label.add_theme_font_size_override("font_size", UIScale.f(19))
|
||||||
label.add_theme_color_override("font_color", Color(0.55, 0.8, 1.0))
|
label.add_theme_color_override("font_color", Color(0.55, 0.8, 1.0))
|
||||||
return label
|
return label
|
||||||
|
|
||||||
|
|
@ -1181,8 +1245,8 @@ func _on_menu_action(id: int) -> void:
|
||||||
_save()
|
_save()
|
||||||
3: # Speichern unter
|
3: # Speichern unter
|
||||||
save_dialog.popup_centered()
|
save_dialog.popup_centered()
|
||||||
4: # Beenden
|
4: # Beenden (Fensterlage vorher merken, Karte t_297221b5)
|
||||||
get_tree().quit()
|
_quit_editor()
|
||||||
5: # Karte testen
|
5: # Karte testen
|
||||||
_on_test_run()
|
_on_test_run()
|
||||||
6: # Kartengröße ändern (R3, t_c863b93b)
|
6: # Kartengröße ändern (R3, t_c863b93b)
|
||||||
|
|
@ -1191,6 +1255,116 @@ func _on_menu_action(id: int) -> void:
|
||||||
_on_update_check_menu()
|
_on_update_check_menu()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------- UI-Größe + Fenster (t_297221b5) --
|
||||||
|
|
||||||
|
## Popup-Häkchen auf die aktuelle Stufe setzen.
|
||||||
|
func _sync_size_menu() -> void:
|
||||||
|
if size_menu == null or not is_instance_valid(size_menu):
|
||||||
|
return
|
||||||
|
var popup := size_menu.get_popup()
|
||||||
|
var cur := UIScale.step_index()
|
||||||
|
for i in popup.item_count:
|
||||||
|
popup.set_item_checked(i, i == cur)
|
||||||
|
|
||||||
|
|
||||||
|
## Neue Stufe gewählt: speichern, kompletten UI-Neuaufbau mit dem neuen
|
||||||
|
## Faktor, Statusmeldung. Wirkt SOFORT — kein Neustart nötig.
|
||||||
|
func _on_ui_size_selected(index: int) -> void:
|
||||||
|
if index < 0 or index >= UIScale.step_count():
|
||||||
|
return
|
||||||
|
if index == UIScale.step_index():
|
||||||
|
return
|
||||||
|
if _update_running:
|
||||||
|
_set_status("Während eines Updates kann die UI-Größe nicht umgestellt werden — gleich danach nochmal.", true)
|
||||||
|
_sync_size_menu()
|
||||||
|
return
|
||||||
|
UIScale.set_step_index(index)
|
||||||
|
_rebuild_ui_for_scale()
|
||||||
|
_set_status("UI-Größe: %s — sofort wirksam und gespeichert (bleibt auch nach Neustart)." % UIScale.step_label(index))
|
||||||
|
|
||||||
|
|
||||||
|
## Kompletter UI-Neuaufbau mit dem aktuellen UIScale-Faktor. Die Map (doc)
|
||||||
|
## bleibt als Objekt UNBERÜHRT (Undo-History bleibt gültig); nur die Controls
|
||||||
|
## werden neu gebaut und der Zustand (Werkzeug, Gitter, Höhenlinien, Auswahl)
|
||||||
|
## wiederhergestellt.
|
||||||
|
func _rebuild_ui_for_scale() -> void:
|
||||||
|
var prev_mode := tool_mode
|
||||||
|
for child in get_children():
|
||||||
|
remove_child(child)
|
||||||
|
child.queue_free()
|
||||||
|
_build_ui()
|
||||||
|
_update_tool_label()
|
||||||
|
_clear_edit_state()
|
||||||
|
_refresh_all()
|
||||||
|
_refresh_history_buttons()
|
||||||
|
# Nutzer-Wahl Gitter/Höhenlinien wiederherstellen (Neubau setzt Defaults).
|
||||||
|
grid_btn.set_pressed_no_signal(_grid_on)
|
||||||
|
_sync_grid_to_builder()
|
||||||
|
contours_btn.set_pressed_no_signal(_contours_on)
|
||||||
|
_sync_contours_to_builder()
|
||||||
|
# Werkzeug-Modus wiederherstellen (Neubau startet bei „objects“).
|
||||||
|
if prev_mode != "objects":
|
||||||
|
tool_mode = "objects"
|
||||||
|
_set_tool_mode(prev_mode)
|
||||||
|
# Update-Banner-Zustand wiederherstellen (Start-Check kannte das Ergebnis).
|
||||||
|
if str(_update_result.get("status")) == "update_available":
|
||||||
|
_show_update_banner(str(_update_result.get("local", "")), str(_update_result.get("remote", "")))
|
||||||
|
_sync_size_menu()
|
||||||
|
|
||||||
|
|
||||||
|
## Fenster beim Start: maximiert (Default, kein Vollbild-Zwang) oder die
|
||||||
|
## gemerkte Fensterlage wiederherstellen. Headless-Läufe (Tests) haben kein
|
||||||
|
## Fenster und springen raus.
|
||||||
|
func _apply_window_state() -> void:
|
||||||
|
if DisplayServer.get_name() == "headless":
|
||||||
|
return
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
var maximiert := true
|
||||||
|
var groesse := Vector2i(1600, 900)
|
||||||
|
var position := Vector2i()
|
||||||
|
var hat_geom := false
|
||||||
|
if cfg.load(UIScale.cfg_path()) == OK:
|
||||||
|
maximiert = bool(cfg.get_value(UIScale.WIN_SECTION, "maximiert", true))
|
||||||
|
var g: Variant = cfg.get_value(UIScale.WIN_SECTION, "groesse", null)
|
||||||
|
if g is Array and (g as Array).size() == 2:
|
||||||
|
groesse = Vector2i(int((g as Array)[0]), int((g as Array)[1]))
|
||||||
|
hat_geom = true
|
||||||
|
var p: Variant = cfg.get_value(UIScale.WIN_SECTION, "position", null)
|
||||||
|
if p is Array and (p as Array).size() == 2:
|
||||||
|
position = Vector2i(int((p as Array)[0]), int((p as Array)[1]))
|
||||||
|
if maximiert:
|
||||||
|
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MAXIMIZED)
|
||||||
|
else:
|
||||||
|
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||||
|
if hat_geom:
|
||||||
|
DisplayServer.window_set_size(groesse)
|
||||||
|
DisplayServer.window_set_position(position)
|
||||||
|
|
||||||
|
|
||||||
|
## Fensterlage merken (beim Schließen): Modus + Größe + Position.
|
||||||
|
func _save_window_state() -> void:
|
||||||
|
if DisplayServer.get_name() == "headless":
|
||||||
|
return
|
||||||
|
var win := get_window()
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
cfg.load(UIScale.cfg_path())
|
||||||
|
cfg.set_value(UIScale.WIN_SECTION, "maximiert", win.mode == Window.MODE_MAXIMIZED)
|
||||||
|
cfg.set_value(UIScale.WIN_SECTION, "groesse", [win.size.x, win.size.y])
|
||||||
|
cfg.set_value(UIScale.WIN_SECTION, "position", [win.position.x, win.position.y])
|
||||||
|
cfg.save(UIScale.cfg_path())
|
||||||
|
|
||||||
|
|
||||||
|
## Ein Ausgang für beide Beenden-Wege (Menü + Fenster-X).
|
||||||
|
func _quit_editor() -> void:
|
||||||
|
_save_window_state()
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||||||
|
_quit_editor()
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------- Kartengröße ändern (R3) --
|
# ------------------------------------------------- Kartengröße ändern (R3) --
|
||||||
|
|
||||||
## Dialog öffnen: aktueller Stand + Live-Preview (Verlust-Liste/Warnband) füllen.
|
## Dialog öffnen: aktueller Stand + Live-Preview (Verlust-Liste/Warnband) füllen.
|
||||||
|
|
@ -1265,21 +1439,21 @@ func _update_banner_build(parent: Container) -> void:
|
||||||
style.bg_color = Color(0.36, 0.25, 0.06, 0.97) # gedämpftes Bernstein
|
style.bg_color = Color(0.36, 0.25, 0.06, 0.97) # gedämpftes Bernstein
|
||||||
style.border_color = Color(0.95, 0.72, 0.15)
|
style.border_color = Color(0.95, 0.72, 0.15)
|
||||||
style.set_border_width_all(2)
|
style.set_border_width_all(2)
|
||||||
style.set_content_margin_all(8)
|
style.set_content_margin_all(UIScale.px(8))
|
||||||
style.set_corner_radius_all(6)
|
style.set_corner_radius_all(6)
|
||||||
update_banner.add_theme_stylebox_override("panel", style)
|
update_banner.add_theme_stylebox_override("panel", style)
|
||||||
var h := HBoxContainer.new()
|
var h := HBoxContainer.new()
|
||||||
h.add_theme_constant_override("separation", 14)
|
h.add_theme_constant_override("separation", UIScale.px(14))
|
||||||
update_banner.add_child(h)
|
update_banner.add_child(h)
|
||||||
update_label = Label.new()
|
update_label = Label.new()
|
||||||
update_label.add_theme_font_size_override("font_size", 17)
|
update_label.add_theme_font_size_override("font_size", UIScale.f(17))
|
||||||
h.add_child(update_label)
|
h.add_child(update_label)
|
||||||
var spacer := Control.new()
|
var spacer := Control.new()
|
||||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
h.add_child(spacer)
|
h.add_child(spacer)
|
||||||
update_btn = Button.new()
|
update_btn = Button.new()
|
||||||
update_btn.text = "🔄 Jetzt updaten"
|
update_btn.text = "🔄 Jetzt updaten"
|
||||||
update_btn.custom_minimum_size = Vector2(190, 40)
|
update_btn.custom_minimum_size = UIScale.v2(190, 40)
|
||||||
update_btn.tooltip_text = "Lädt die neue Version (git pull), importiert sie und startet den Editor neu. Bei ungespeicherten Änderungen fragt der Editor vorher."
|
update_btn.tooltip_text = "Lädt die neue Version (git pull), importiert sie und startet den Editor neu. Bei ungespeicherten Änderungen fragt der Editor vorher."
|
||||||
update_btn.pressed.connect(_on_update_now)
|
update_btn.pressed.connect(_on_update_now)
|
||||||
h.add_child(update_btn)
|
h.add_child(update_btn)
|
||||||
|
|
@ -1343,6 +1517,8 @@ func _updater_check_done(result: Dictionary, report: bool) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _show_update_banner(local_sha: String, remote_sha: String) -> void:
|
func _show_update_banner(local_sha: String, remote_sha: String) -> void:
|
||||||
|
if update_banner == null or not is_instance_valid(update_banner):
|
||||||
|
return # UI wurde gerade neu aufgebaut (UI-Größe) — nichts anfassen
|
||||||
update_label.text = EditorUpdater.banner_text(local_sha, remote_sha)
|
update_label.text = EditorUpdater.banner_text(local_sha, remote_sha)
|
||||||
update_banner.visible = true
|
update_banner.visible = true
|
||||||
|
|
||||||
|
|
@ -1405,6 +1581,8 @@ func _updater_run_thread() -> void:
|
||||||
|
|
||||||
func _update_finished(ok: bool, title: String, details: Array) -> void:
|
func _update_finished(ok: bool, title: String, details: Array) -> void:
|
||||||
_update_running = false
|
_update_running = false
|
||||||
|
if not is_instance_valid(update_btn) or not is_instance_valid(update_banner):
|
||||||
|
return # UI wurde zwischendurch neu aufgebaut (UI-Größe) — nichts anfassen
|
||||||
update_btn.disabled = false
|
update_btn.disabled = false
|
||||||
_join(update_thread)
|
_join(update_thread)
|
||||||
update_thread = null
|
update_thread = null
|
||||||
|
|
@ -1956,7 +2134,7 @@ func _terrain_mode_button(text: String, group: ButtonGroup) -> Button:
|
||||||
btn.text = text
|
btn.text = text
|
||||||
btn.toggle_mode = true
|
btn.toggle_mode = true
|
||||||
btn.button_group = group
|
btn.button_group = group
|
||||||
btn.custom_minimum_size = Vector2(0, 40)
|
btn.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
return btn
|
return btn
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2130,7 +2308,7 @@ func _terrain_size_button(text: String, group: ButtonGroup, radius_m: float) ->
|
||||||
btn.text = text
|
btn.text = text
|
||||||
btn.toggle_mode = true
|
btn.toggle_mode = true
|
||||||
btn.button_group = group
|
btn.button_group = group
|
||||||
btn.custom_minimum_size = Vector2(0, 48)
|
btn.custom_minimum_size = UIScale.v2(0, 48)
|
||||||
btn.tooltip_text = "Pinselradius %d m" % int(radius_m)
|
btn.tooltip_text = "Pinselradius %d m" % int(radius_m)
|
||||||
btn.toggled.connect(_on_terrain_size_preset.bind(radius_m))
|
btn.toggled.connect(_on_terrain_size_preset.bind(radius_m))
|
||||||
return btn
|
return btn
|
||||||
|
|
@ -2241,7 +2419,7 @@ func _spawn_team_button(text: String, group: ButtonGroup, team: int) -> Button:
|
||||||
btn.text = text
|
btn.text = text
|
||||||
btn.toggle_mode = true
|
btn.toggle_mode = true
|
||||||
btn.button_group = group
|
btn.button_group = group
|
||||||
btn.custom_minimum_size = Vector2(0, 40)
|
btn.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
btn.pressed.connect(_on_spawn_team_changed.bind(team))
|
btn.pressed.connect(_on_spawn_team_changed.bind(team))
|
||||||
return btn
|
return btn
|
||||||
|
|
||||||
|
|
@ -2719,10 +2897,15 @@ func _refresh_file_label() -> void:
|
||||||
file_label.text = "„%s“ (noch nicht gespeichert)%s" % [map_name, " •" if dirty else ""]
|
file_label.text = "„%s“ (noch nicht gespeichert)%s" % [map_name, " •" if dirty else ""]
|
||||||
else:
|
else:
|
||||||
file_label.text = "„%s“ — %s%s" % [map_name, current_path.get_file(), " •" if dirty else ""]
|
file_label.text = "„%s“ — %s%s" % [map_name, current_path.get_file(), " •" if dirty else ""]
|
||||||
|
# Karte t_297221b5: bei egalisiertem Label (große Stufe, schmales Fenster)
|
||||||
|
# steht der volle Name im Tooltip.
|
||||||
|
file_label.tooltip_text = file_label.text
|
||||||
get_window().title = "%s — %s" % [APP_TITLE, map_name]
|
get_window().title = "%s — %s" % [APP_TITLE, map_name]
|
||||||
|
|
||||||
|
|
||||||
func _set_status(text: String, is_error := false) -> void:
|
func _set_status(text: String, is_error := false) -> void:
|
||||||
|
if status_label == null or not is_instance_valid(status_label):
|
||||||
|
return # UI wurde gerade neu aufgebaut (UI-Größe) — nichts anfassen
|
||||||
status_label.text = text
|
status_label.text = text
|
||||||
status_label.add_theme_color_override("font_color",
|
status_label.add_theme_color_override("font_color",
|
||||||
Color(1.0, 0.6, 0.5) if is_error else Color(0.75, 0.95, 0.75))
|
Color(1.0, 0.6, 0.5) if is_error else Color(0.75, 0.95, 0.75))
|
||||||
|
|
@ -2759,6 +2942,9 @@ func _maybe_start_demo_mode() -> void:
|
||||||
if args.has("--bauen-demo"):
|
if args.has("--bauen-demo"):
|
||||||
_start_bauen_demo(args)
|
_start_bauen_demo(args)
|
||||||
return
|
return
|
||||||
|
if args.has("--ui-demo"):
|
||||||
|
_start_ui_demo(args)
|
||||||
|
return
|
||||||
if not args.has("--ghost-demo"):
|
if not args.has("--ghost-demo"):
|
||||||
return
|
return
|
||||||
var oid := "crate_wood"
|
var oid := "crate_wood"
|
||||||
|
|
@ -2794,6 +2980,43 @@ func _maybe_start_demo_mode() -> void:
|
||||||
camera.call("_apply")
|
camera.call("_apply")
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------- UI-Größen-Demo-Screens --
|
||||||
|
|
||||||
|
## `godot ... -- --ui-demo [ordner] [--ui-demo-res BxH] [--ui-demo-quit]`
|
||||||
|
## (Karte t_297221b5): fährt ALLE UI-Größen-Stufen programmatisch durch —
|
||||||
|
## pro Stufe kompletter UI-Neuaufbau (derselbe Pfad wie der Menü-Klick) und
|
||||||
|
## ein Fenster-Screenshot. Beweist: umschaltbar im Lauf + nichts abgeschnitten.
|
||||||
|
func _start_ui_demo(args: PackedStringArray) -> void:
|
||||||
|
var out_dir := "/tmp/scopedvr_ui"
|
||||||
|
var idx := args.find("--ui-demo")
|
||||||
|
if idx >= 0 and idx + 1 < args.size() and not args[idx + 1].begins_with("--"):
|
||||||
|
out_dir = args[idx + 1]
|
||||||
|
DirAccess.make_dir_recursive_absolute(out_dir)
|
||||||
|
var res_idx := args.find("--ui-demo-res")
|
||||||
|
if res_idx >= 0 and res_idx + 1 < args.size():
|
||||||
|
var parts := str(args[res_idx + 1]).split("x")
|
||||||
|
if parts.size() == 2:
|
||||||
|
# Fenstergröße NICHT zu früh setzen — direkt bei _ready wird der
|
||||||
|
# Wert von der Projekt-Einstellung überschrieben (xvfb-Befund);
|
||||||
|
# erst nach dem ersten Frame ist der Resize stabil.
|
||||||
|
await get_tree().process_frame
|
||||||
|
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||||
|
DisplayServer.window_set_size(Vector2i(int(parts[0]), int(parts[1])))
|
||||||
|
get_window().size = Vector2i(int(parts[0]), int(parts[1])) # root-Window setzt durch (xvfb-Befund)
|
||||||
|
for i in UIScale.step_count():
|
||||||
|
UIScale.set_step_index(i)
|
||||||
|
_rebuild_ui_for_scale()
|
||||||
|
await get_tree().process_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
_set_status("UI-Größe: %s (%.2f-fach) — Theme-Schrift %d px, Beweis-Screenshot." % [
|
||||||
|
UIScale.step_label(i), UIScale.factor(), UIScale.font_size()])
|
||||||
|
await _demo_shot(out_dir + "/ui_%s.png" % UIScale.step_key(i))
|
||||||
|
print("UI_DEMO: Stufe %s Faktor %.2f Schrift %d" % [UIScale.step_key(i), UIScale.factor(), UIScale.font_size()])
|
||||||
|
print("UI_DEMO_OK: %d Stufen, PNGs in %s" % [UIScale.step_count(), out_dir])
|
||||||
|
if args.has("--ui-demo-quit"):
|
||||||
|
get_tree().quit() # Batch-Smoke (xvfb): automatisch beenden nach den Shots
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------ Terrain-Demo-Screenshots --
|
# ------------------------------------------------ Terrain-Demo-Screenshots --
|
||||||
|
|
||||||
## `godot ... -- --terrain-demo [ordner]` — Desktop-Smoke (Karte t_9aba04f1):
|
## `godot ... -- --terrain-demo [ordner]` — Desktop-Smoke (Karte t_9aba04f1):
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ extends ConfirmationDialog
|
||||||
## „Neue Map“-Dialog: Name, Größe, Wetter-Preset — bewusst nur 3 Fragen,
|
## „Neue Map“-Dialog: Name, Größe, Wetter-Preset — bewusst nur 3 Fragen,
|
||||||
## alles Weitere setzt vernünftige Defaults (Chef-Grundsatz: easy).
|
## alles Weitere setzt vernünftige Defaults (Chef-Grundsatz: easy).
|
||||||
|
|
||||||
|
const UIScale := preload("res://src/ui/ui_scale.gd") # Karte t_297221b5
|
||||||
|
|
||||||
var name_edit: LineEdit
|
var name_edit: LineEdit
|
||||||
var size_option: OptionButton
|
var size_option: OptionButton
|
||||||
var weather_option: OptionButton
|
var weather_option: OptionButton
|
||||||
|
|
@ -16,19 +18,19 @@ func _init() -> void:
|
||||||
title = "Neue Map"
|
title = "Neue Map"
|
||||||
ok_button_text = "Map erstellen"
|
ok_button_text = "Map erstellen"
|
||||||
cancel_button_text = "Abbrechen"
|
cancel_button_text = "Abbrechen"
|
||||||
min_size = Vector2i(460, 0)
|
min_size = UIScale.v2i(460, 0)
|
||||||
|
|
||||||
var vbox := VBoxContainer.new()
|
var vbox := VBoxContainer.new()
|
||||||
vbox.anchors_preset = Control.PRESET_FULL_RECT
|
vbox.anchors_preset = Control.PRESET_FULL_RECT
|
||||||
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
vbox.add_theme_constant_override("separation", 12)
|
vbox.add_theme_constant_override("separation", UIScale.px(12))
|
||||||
add_child(vbox)
|
add_child(vbox)
|
||||||
|
|
||||||
vbox.add_child(_make_label("Wie soll die Map heißen?"))
|
vbox.add_child(_make_label("Wie soll die Map heißen?"))
|
||||||
name_edit = LineEdit.new()
|
name_edit = LineEdit.new()
|
||||||
name_edit.text = "Neue Map"
|
name_edit.text = "Neue Map"
|
||||||
name_edit.max_length = 64
|
name_edit.max_length = 64
|
||||||
name_edit.custom_minimum_size = Vector2(0, 40)
|
name_edit.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
vbox.add_child(name_edit)
|
vbox.add_child(name_edit)
|
||||||
|
|
||||||
vbox.add_child(_make_label("Wie groß soll das Gelände sein?"))
|
vbox.add_child(_make_label("Wie groß soll das Gelände sein?"))
|
||||||
|
|
@ -36,14 +38,14 @@ func _init() -> void:
|
||||||
for label in _size_labels:
|
for label in _size_labels:
|
||||||
size_option.add_item(label)
|
size_option.add_item(label)
|
||||||
size_option.select(1) # Mittel als Default
|
size_option.select(1) # Mittel als Default
|
||||||
size_option.custom_minimum_size = Vector2(0, 40)
|
size_option.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
vbox.add_child(size_option)
|
vbox.add_child(size_option)
|
||||||
|
|
||||||
vbox.add_child(_make_label("Welches Wetter soll die Map haben?"))
|
vbox.add_child(_make_label("Welches Wetter soll die Map haben?"))
|
||||||
weather_option = OptionButton.new()
|
weather_option = OptionButton.new()
|
||||||
for label in _weather_labels:
|
for label in _weather_labels:
|
||||||
weather_option.add_item(label)
|
weather_option.add_item(label)
|
||||||
weather_option.custom_minimum_size = Vector2(0, 40)
|
weather_option.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
vbox.add_child(weather_option)
|
vbox.add_child(weather_option)
|
||||||
|
|
||||||
var hint := _make_label("Die Map startet leer mit 4 Spawn-Punkten pro Team\n(Blau im Westen, Rot im Osten) — beides lässt sich später ändern.")
|
var hint := _make_label("Die Map startet leer mit 4 Spawn-Punkten pro Team\n(Blau im Westen, Rot im Osten) — beides lässt sich später ändern.")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ extends ConfirmationDialog
|
||||||
## „Kartengröße ändern“-Dialog (R3, Karte t_c863b93b — PLAN.md „Map-Größe
|
## „Kartengröße ändern“-Dialog (R3, Karte t_c863b93b — PLAN.md „Map-Größe
|
||||||
## ändern“): aktuelle Größe anzeigen, neue X/Z in Metern eingeben (Preset-
|
## ändern“): aktuelle Größe anzeigen, neue X/Z in Metern eingeben (Preset-
|
||||||
## Buttons + Freifeld), Live-Vorschau aus SvmapDocument.resize_preview (R2).
|
## Buttons + Freifeld), Live-Vorschau aus SvmapDocument.resize_preview (R2).
|
||||||
|
|
||||||
|
const UIScale := preload("res://src/ui/ui_scale.gd") # Karte t_297221b5
|
||||||
##
|
##
|
||||||
## Stil wie new_map_dialog.gd: alles per Code, Deutsch, Klartext-Warnungen.
|
## Stil wie new_map_dialog.gd: alles per Code, Deutsch, Klartext-Warnungen.
|
||||||
## Der Dialog mutiert NICHTS — das Anwenden macht main.gd (_on_resize_confirmed)
|
## Der Dialog mutiert NICHTS — das Anwenden macht main.gd (_on_resize_confirmed)
|
||||||
|
|
@ -25,22 +27,22 @@ func _init() -> void:
|
||||||
title = "Kartengröße ändern"
|
title = "Kartengröße ändern"
|
||||||
ok_button_text = "Größe ändern"
|
ok_button_text = "Größe ändern"
|
||||||
cancel_button_text = "Abbrechen"
|
cancel_button_text = "Abbrechen"
|
||||||
min_size = Vector2i(520, 0)
|
min_size = UIScale.v2i(520, 0)
|
||||||
|
|
||||||
var vbox := VBoxContainer.new()
|
var vbox := VBoxContainer.new()
|
||||||
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
vbox.add_theme_constant_override("separation", 12)
|
vbox.add_theme_constant_override("separation", UIScale.px(12))
|
||||||
add_child(vbox)
|
add_child(vbox)
|
||||||
|
|
||||||
info_label = _make_label("")
|
info_label = _make_label("")
|
||||||
vbox.add_child(info_label)
|
vbox.add_child(info_label)
|
||||||
|
|
||||||
var preset_h := HBoxContainer.new()
|
var preset_h := HBoxContainer.new()
|
||||||
preset_h.add_theme_constant_override("separation", 8)
|
preset_h.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
for size in _PRESET_SIZES:
|
for size in _PRESET_SIZES:
|
||||||
var btn := Button.new()
|
var btn := Button.new()
|
||||||
btn.text = "%d m" % size
|
btn.text = "%d m" % size
|
||||||
btn.custom_minimum_size = Vector2(0, 40)
|
btn.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
btn.pressed.connect(_on_preset.bind(float(size)))
|
btn.pressed.connect(_on_preset.bind(float(size)))
|
||||||
preset_h.add_child(btn)
|
preset_h.add_child(btn)
|
||||||
vbox.add_child(preset_h)
|
vbox.add_child(preset_h)
|
||||||
|
|
@ -61,7 +63,7 @@ func _init() -> void:
|
||||||
|
|
||||||
preview_label = _make_label("")
|
preview_label = _make_label("")
|
||||||
preview_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
preview_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
preview_label.custom_minimum_size = Vector2(0, 60)
|
preview_label.custom_minimum_size = UIScale.v2(0, 60)
|
||||||
vbox.add_child(preview_label)
|
vbox.add_child(preview_label)
|
||||||
|
|
||||||
drop_check = CheckBox.new()
|
drop_check = CheckBox.new()
|
||||||
|
|
@ -87,7 +89,7 @@ func _fill_spin(spin: SpinBox) -> void:
|
||||||
spin.step = 1.0
|
spin.step = 1.0
|
||||||
spin.suffix = " m"
|
spin.suffix = " m"
|
||||||
spin.value = 1024.0
|
spin.value = 1024.0
|
||||||
spin.custom_minimum_size = Vector2(0, 40)
|
spin.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
|
|
||||||
|
|
||||||
## Vom Editor vor dem Öffnen füllen: aktueller Stand + Live-Vorschau.
|
## Vom Editor vor dem Öffnen füllen: aktueller Stand + Live-Vorschau.
|
||||||
|
|
|
||||||
120
src/ui/ui_scale.gd
Normal file
120
src/ui/ui_scale.gd
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
class_name UIScale
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Zentrale UI-Skalierung (Karte t_297221b5): EIN Faktor für den ganzen Editor.
|
||||||
|
##
|
||||||
|
## Stufen: Klein (1.0) / Mittel (1.25) / Groß (1.5). Der Faktor multipliziert
|
||||||
|
## alle Layout-Werte beim UI-Bau (Schrift, Zeilen-/Knopfhöhen, Panelbreiten,
|
||||||
|
## Thumbnail-Größe, Abstände) — bewusst als Faktor-Multiplikation und NICHT als
|
||||||
|
## stretch/canvas_items, damit (a) die 3D-Map-Ansicht pixelgenau unberührt bleibt,
|
||||||
|
## (b) nichts doppelt skaliert und (c) die Stufe vom Nutzer gewählt wird statt
|
||||||
|
## von der Fenstergröße abzuhängen.
|
||||||
|
##
|
||||||
|
## Die gewählte Stufe liegt in user://scopedvr_editor.cfg, Abschnitt [ui]
|
||||||
|
## (Key "groesse"). ConfigFile lädt+speichert die GANZE Datei — der Abschnitt
|
||||||
|
## [library] (Klapp-Zustand, Karte t_59fc0f21) bleibt dabei erhalten.
|
||||||
|
##
|
||||||
|
## Für Tests/Probes kann per Umgebungsvariable SCOPEDVR_UI_CFG eine andere
|
||||||
|
## Config-Datei erzwungen werden (hermetische Läufe ohne Nutzer-Config).
|
||||||
|
|
||||||
|
const CFG_PATH := "user://scopedvr_editor.cfg"
|
||||||
|
const CFG_SECTION := "ui"
|
||||||
|
const CFG_KEY := "groesse" # "klein" | "mittel" | "gross"
|
||||||
|
const WIN_SECTION := "fenster" # Fenster-Zustand (maximiert/Größe/Position)
|
||||||
|
|
||||||
|
## [key, Anzeige-Name, Faktor]
|
||||||
|
const STEPS: Array = [
|
||||||
|
["klein", "Klein", 1.0],
|
||||||
|
["mittel", "Mittel", 1.25],
|
||||||
|
["gross", "Groß", 1.5],
|
||||||
|
]
|
||||||
|
const BASE_FONT_SIZE := 16 # bisherige feste Schriftgröße (main.gd, Vorher)
|
||||||
|
|
||||||
|
static var _factor: float = -1.0 # Cache; < 0 = noch nicht aus der Config gelesen
|
||||||
|
|
||||||
|
|
||||||
|
static func cfg_path() -> String:
|
||||||
|
var env := OS.get_environment("SCOPEDVR_UI_CFG")
|
||||||
|
if not env.is_empty():
|
||||||
|
return env
|
||||||
|
return CFG_PATH
|
||||||
|
|
||||||
|
|
||||||
|
static func step_count() -> int:
|
||||||
|
return STEPS.size()
|
||||||
|
|
||||||
|
|
||||||
|
static func step_label(i: int) -> String:
|
||||||
|
return str(STEPS[clampi(i, 0, STEPS.size() - 1)][1])
|
||||||
|
|
||||||
|
|
||||||
|
static func step_key(i: int) -> String:
|
||||||
|
return str(STEPS[clampi(i, 0, STEPS.size() - 1)][0])
|
||||||
|
|
||||||
|
|
||||||
|
static func step_index_from_key(key: String) -> int:
|
||||||
|
for i in STEPS.size():
|
||||||
|
if str(STEPS[i][0]) == key:
|
||||||
|
return i
|
||||||
|
return 0 # unbekannt/leer → Klein (Vorher-Verhalten)
|
||||||
|
|
||||||
|
|
||||||
|
## Aktuelle Stufe aus der Config (ohne Cache).
|
||||||
|
static func step_index() -> int:
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
if cfg.load(cfg_path()) != OK:
|
||||||
|
return 0 # keine Config = Klein (altes Verhalten)
|
||||||
|
return step_index_from_key(str(cfg.get_value(CFG_SECTION, CFG_KEY, "")))
|
||||||
|
|
||||||
|
|
||||||
|
## Faktor der aktuellen Stufe (cached — UI-Bau ruft das sehr oft).
|
||||||
|
static func factor() -> float:
|
||||||
|
if _factor < 0.0:
|
||||||
|
_factor = float(STEPS[step_index()][2])
|
||||||
|
return _factor
|
||||||
|
|
||||||
|
|
||||||
|
## Stufe setzen UND sofort in die Config schreiben (Abschnitte wie [library]
|
||||||
|
## bleiben erhalten — ConfigFile liest/schreibt die ganze Datei).
|
||||||
|
static func set_step_index(i: int) -> void:
|
||||||
|
i = clampi(i, 0, STEPS.size() - 1)
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
cfg.load(cfg_path()) # fehlt die Datei: leer anfangen, Abschnitte bleiben
|
||||||
|
cfg.set_value(CFG_SECTION, CFG_KEY, str(STEPS[i][0]))
|
||||||
|
var err := cfg.save(cfg_path())
|
||||||
|
if err != OK:
|
||||||
|
push_warning("UIScale: Config nicht schreibbar (%s), Stufe nur für diese Sitzung." % err)
|
||||||
|
_factor = float(STEPS[i][2])
|
||||||
|
|
||||||
|
|
||||||
|
## Cache verwerfen und neu lesen — simuliert den Neustart (für Tests/Probes).
|
||||||
|
static func reload() -> void:
|
||||||
|
_factor = -1.0
|
||||||
|
factor()
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------- Rechenhelfer (Bau) --
|
||||||
|
|
||||||
|
## Ganzzahliges Pixel-Maß (Höhen, Breiten, Abstände, Schriftgrößen).
|
||||||
|
static func px(v: float) -> int:
|
||||||
|
return int(round(v * factor()))
|
||||||
|
|
||||||
|
|
||||||
|
## Fließkommamaß (z. B. split_offset).
|
||||||
|
static func f(v: float) -> float:
|
||||||
|
return v * factor()
|
||||||
|
|
||||||
|
|
||||||
|
## custom_minimum_size = UIScale.v2(b, h) statt Vector2(b, h).
|
||||||
|
static func v2(x: float, y: float) -> Vector2:
|
||||||
|
return Vector2(px(x), px(y))
|
||||||
|
|
||||||
|
|
||||||
|
## Dialog-/Fenstergrößen.
|
||||||
|
static func v2i(x: float, y: float) -> Vector2i:
|
||||||
|
return Vector2i(px(x), px(y))
|
||||||
|
|
||||||
|
|
||||||
|
## Standardschriftgröße (bisher fest 16).
|
||||||
|
static func font_size() -> int:
|
||||||
|
return px(BASE_FONT_SIZE)
|
||||||
1
src/ui/ui_scale.gd.uid
Normal file
1
src/ui/ui_scale.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://d4d60533njpla
|
||||||
219
tests/test_ui_scale.gd
Normal file
219
tests/test_ui_scale.gd
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
extends SceneTree
|
||||||
|
## Editor-Test (headless): UI-Skalierung — Karte t_297221b5.
|
||||||
|
## Ausführen: godot --headless --path . -s res://tests/test_ui_scale.gd
|
||||||
|
## Exit 0 = alles grün.
|
||||||
|
##
|
||||||
|
## Geprüft wird:
|
||||||
|
## 1. Stufen-API: 3 Stufen (Klein 1.0 / Mittel 1.25 / Groß 1.5), Labels/Keys.
|
||||||
|
## 2. Rechenhelfer: px/f/v2/font_size multiplizieren korrekt (inkl. Runden).
|
||||||
|
## 3. Config-Roundtrip: Stufe übersteht reload() (= Neustart) UND der
|
||||||
|
## [library]-Abschnitt (Klapp-Zustand, Karte t_59fc0f21) bleibt erhalten.
|
||||||
|
## 4. ECHTE Main-UI (scenes/main.tscn): Theme-Schrift, Knopf-/Kachel-/
|
||||||
|
## Thumbnail-Mindestmaße skalieren bei jeder Stufe — sofort nach dem
|
||||||
|
## Rebuild (derselbe Pfad wie der Menü-Klick „UI-Größe“).
|
||||||
|
## 5. Rebuild erhält den Map-Zustand (doc-Objekt bleibt, Map-Eigenschaften
|
||||||
|
## werden wieder eingefüllt, Objektzahl unverändert).
|
||||||
|
## 6. Ungültige Menü-Indices und headless-Fenster-Aufrufe sind harmlos.
|
||||||
|
##
|
||||||
|
## Die Suite ist hermetisch: SCOPEDVR_UI_CFG zeigt auf eine Temp-Config,
|
||||||
|
## damit weder die Nutzer-Config noch andere Suite-Läufe gestört werden.
|
||||||
|
|
||||||
|
const UIScale := preload("res://src/ui/ui_scale.gd")
|
||||||
|
|
||||||
|
var failures := 0
|
||||||
|
var checks := 0
|
||||||
|
var main: Control
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
# Hermetische Config (Temp-Pfad im user://-Bereich):
|
||||||
|
OS.set_environment("SCOPEDVR_UI_CFG", "user://ui_scale_test.cfg")
|
||||||
|
_run_all.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _check(cond: bool, label: String) -> void:
|
||||||
|
checks += 1
|
||||||
|
if cond:
|
||||||
|
print(" ok: %s" % label)
|
||||||
|
else:
|
||||||
|
failures += 1
|
||||||
|
print(" FAIL: %s" % label)
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
# Sauber hinterlassen: Test-Config löschen, Env-Umleitung aufheben.
|
||||||
|
var dir := DirAccess.open("user://")
|
||||||
|
if dir != null and dir.file_exists("ui_scale_test.cfg"):
|
||||||
|
dir.remove("ui_scale_test.cfg")
|
||||||
|
OS.set_environment("SCOPEDVR_UI_CFG", "")
|
||||||
|
print("CHECKS: %d FAILURES: %d" % [checks, failures])
|
||||||
|
print("UI_SCALE_TEST_OK" if failures == 0 else "UI_SCALE_TEST_FAIL")
|
||||||
|
quit(1 if failures > 0 else 0)
|
||||||
|
|
||||||
|
|
||||||
|
func _run_all() -> void:
|
||||||
|
UIScale.set_step_index(0)
|
||||||
|
_test_steps_api()
|
||||||
|
_test_math()
|
||||||
|
_test_cfg_roundtrip()
|
||||||
|
await _test_real_ui_scaling()
|
||||||
|
await _test_state_survives_rebuild()
|
||||||
|
_test_headless_safety()
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _test_steps_api() -> void:
|
||||||
|
print("Testgruppe: Stufen-API")
|
||||||
|
_check(UIScale.step_count() == 3, "Genau 3 Stufen")
|
||||||
|
_check(UIScale.step_label(0) == "Klein" and UIScale.step_label(1) == "Mittel" and UIScale.step_label(2) == "Groß",
|
||||||
|
"Labels Klein/Mittel/Groß")
|
||||||
|
UIScale.set_step_index(0)
|
||||||
|
_check(is_equal_approx(UIScale.factor(), 1.0), "Klein = Faktor 1.0")
|
||||||
|
UIScale.set_step_index(1)
|
||||||
|
_check(is_equal_approx(UIScale.factor(), 1.25), "Mittel = Faktor 1.25")
|
||||||
|
UIScale.set_step_index(2)
|
||||||
|
_check(is_equal_approx(UIScale.factor(), 1.5), "Groß = Faktor 1.5")
|
||||||
|
_check(UIScale.step_index_from_key("mittel") == 1, "Key 'mittel' → Index 1")
|
||||||
|
_check(UIScale.step_index_from_key("quatsch") == 0, "Unbekannter Key → Klein (0)")
|
||||||
|
_check(UIScale.step_index_from_key("") == 0, "Leerer Key → Klein (0)")
|
||||||
|
|
||||||
|
|
||||||
|
func _test_math() -> void:
|
||||||
|
print("Testgruppe: Rechenhelfer")
|
||||||
|
UIScale.set_step_index(0)
|
||||||
|
_check(UIScale.px(40) == 40 and UIScale.px(16) == 16, "Klein: px unverändert (1.0)")
|
||||||
|
_check(UIScale.font_size() == 16, "Klein: Standardschrift 16 (wie Vorher)")
|
||||||
|
UIScale.set_step_index(1)
|
||||||
|
_check(UIScale.px(40) == 50, "Mittel: 40 px → 50 px")
|
||||||
|
_check(UIScale.px(126) == 158, "Mittel: 126 px → 158 px (gerundet)")
|
||||||
|
_check(UIScale.px(280) == 350, "Mittel: Panel 280 px → 350 px")
|
||||||
|
_check(is_equal_approx(UIScale.f(-320.0), -400.0), "Mittel: split_offset -320 → -400")
|
||||||
|
_check(UIScale.font_size() == 20, "Mittel: Standardschrift 20")
|
||||||
|
var v := UIScale.v2(140, 40)
|
||||||
|
_check(v.x == 175 and v.y == 50, "Mittel: v2(140,40) → (175,50)")
|
||||||
|
UIScale.set_step_index(2)
|
||||||
|
_check(UIScale.px(40) == 60 and UIScale.font_size() == 24, "Groß: 40 px → 60 px, Schrift 24")
|
||||||
|
_check(UIScale.px(96) == 144, "Groß: Thumbnail 96 → 144")
|
||||||
|
|
||||||
|
|
||||||
|
func _test_cfg_roundtrip() -> void:
|
||||||
|
print("Testgruppe: Config-Roundtrip (Neustart-Festigkeit + [library]-Schutz)")
|
||||||
|
UIScale.set_step_index(1) # schreibt [ui] groesse=mittel
|
||||||
|
# [library]-Abschnitt anlegen (wie Karte t_59fc0f21 es tut):
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
cfg.load(UIScale.cfg_path())
|
||||||
|
cfg.set_value("library", "gebaeude", true)
|
||||||
|
cfg.save(UIScale.cfg_path())
|
||||||
|
# „Neustart“: Cache verwerfen, frisch aus der Datei lesen.
|
||||||
|
UIScale.reload()
|
||||||
|
_check(UIScale.step_index() == 1, "Stufe Mittel übersteht reload() (= Neustart)")
|
||||||
|
var cfg2 := ConfigFile.new()
|
||||||
|
cfg2.load(UIScale.cfg_path())
|
||||||
|
_check(bool(cfg2.get_value("library", "gebaeude", false)), "[library]-Abschnitt bleibt beim Schreiben erhalten")
|
||||||
|
_check(str(cfg2.get_value("ui", "groesse", "")) == "mittel", "[ui] groesse=mittel steht in der Datei")
|
||||||
|
|
||||||
|
|
||||||
|
func _test_real_ui_scaling() -> void:
|
||||||
|
print("Testgruppe: Echte Main-UI skaliert (Build + Rebuild)")
|
||||||
|
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||||
|
_check(scene != null, "scenes/main.tscn lädt")
|
||||||
|
if scene == null:
|
||||||
|
return
|
||||||
|
UIScale.set_step_index(0)
|
||||||
|
main = scene.instantiate()
|
||||||
|
root.add_child(main)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
_check(main != null and main.undo_btn != null, "Main-UI gebaut")
|
||||||
|
_check(int(main.theme.default_font_size) == 16, "Klein: Theme-Schrift 16")
|
||||||
|
_check(main.undo_btn.custom_minimum_size == Vector2(140, 40), "Klein: Undo-Knopf 140×40")
|
||||||
|
_check(main.redo_btn.custom_minimum_size == Vector2(150, 40), "Klein: Redo-Knopf 150×40")
|
||||||
|
_check(main.size_menu != null and main.size_menu.text == "UI-Größe", "UI-Größe-Menü in der Top-Bar")
|
||||||
|
var sp: PopupMenu = main.size_menu.get_popup()
|
||||||
|
_check(sp.item_count == 3, "Menü hat 3 Einträge")
|
||||||
|
_check(sp.is_item_checked(0), "Menü-Häkchen auf aktueller Stufe (Klein)")
|
||||||
|
|
||||||
|
# Rebuild-Pfad (derselbe wie beim Menü-Klick) → Mittel:
|
||||||
|
UIScale.set_step_index(1)
|
||||||
|
main._rebuild_ui_for_scale()
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
_check(is_instance_valid(main.undo_btn), "Rebuild: Knöpfe neu gebaut")
|
||||||
|
_check(int(main.theme.default_font_size) == 20, "Mittel: Theme-Schrift 20 (sofort wirksam)")
|
||||||
|
_check(main.undo_btn.custom_minimum_size == Vector2(175, 50), "Mittel: Undo-Knopf 175×50")
|
||||||
|
_check(main.library_search.custom_minimum_size.y == 45, "Mittel: Suchfeld 36 → 45")
|
||||||
|
var tile := _find_tile_button(158)
|
||||||
|
_check(tile != null and tile.custom_minimum_size.y == 158, "Mittel: Bibliotheks-Kachel 126 → 158")
|
||||||
|
var thumb := _find_thumb_rect(120)
|
||||||
|
_check(thumb != null and thumb.custom_minimum_size.x == 120, "Mittel: Thumbnail 96 → 120")
|
||||||
|
_check(main.size_menu.get_popup().is_item_checked(1), "Menü-Häkchen wandert auf Mittel (neues Popup)")
|
||||||
|
|
||||||
|
# Groß:
|
||||||
|
main._on_ui_size_selected(2)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
_check(int(main.theme.default_font_size) == 24, "Groß: Theme-Schrift 24 (per Menü-Handler)")
|
||||||
|
_check(main.undo_btn.custom_minimum_size == Vector2(210, 60), "Groß: Undo-Knopf 210×60")
|
||||||
|
_check(main.status_label != null and is_instance_valid(main.status_label), "Statuszeile existiert (Groß)")
|
||||||
|
_check(main.content_tree != null and is_instance_valid(main.content_tree), "Inhalts-Baum existiert (Groß)")
|
||||||
|
|
||||||
|
# Zurück auf Klein: wieder exakt die Vorher-Maße.
|
||||||
|
main._on_ui_size_selected(0)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
_check(main.size_menu.get_popup().is_item_checked(0), "Menü-Häkchen wieder auf Klein")
|
||||||
|
_check(int(main.theme.default_font_size) == 16, "Zurück auf Klein: Schrift wieder 16")
|
||||||
|
_check(main.undo_btn.custom_minimum_size == Vector2(140, 40), "Zurück auf Klein: Undo-Knopf 140×40")
|
||||||
|
|
||||||
|
|
||||||
|
func _find_tile_button(y: int) -> Button:
|
||||||
|
# Bibliotheks-Kacheln: Buttons mit custom_minimum_size.y == Stufenhöhe
|
||||||
|
# unterhalb der Haupt-VBox suchen — der Rebuild baut sie jedes Mal neu.
|
||||||
|
var stack: Array = [main]
|
||||||
|
while not stack.is_empty():
|
||||||
|
var node: Node = stack.pop_back()
|
||||||
|
if node is Button and node.custom_minimum_size.y == y and node.custom_minimum_size.x == 0:
|
||||||
|
return node
|
||||||
|
for c in node.get_children():
|
||||||
|
stack.push_back(c)
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _find_thumb_rect(x: int) -> TextureRect:
|
||||||
|
var stack: Array = [main]
|
||||||
|
while not stack.is_empty():
|
||||||
|
var node: Node = stack.pop_back()
|
||||||
|
if node is TextureRect and node.custom_minimum_size.x == x:
|
||||||
|
return node
|
||||||
|
for c in node.get_children():
|
||||||
|
stack.push_back(c)
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _test_state_survives_rebuild() -> void:
|
||||||
|
print("Testgruppe: Rebuild erhält Map-Zustand")
|
||||||
|
if main == null:
|
||||||
|
return
|
||||||
|
var count_before := SvmapDocument.object_count(main.doc)
|
||||||
|
main.doc["info"]["name"] = "Skalierungs-Test"
|
||||||
|
main._rebuild_ui_for_scale()
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
_check(SvmapDocument.object_count(main.doc) == count_before, "Map-Dokument unverändert (Objektzahl)")
|
||||||
|
_check(str(main.prop_name.text) == "Skalierungs-Test", "Map-Eigenschaften nach Rebuild wieder eingefüllt")
|
||||||
|
_check(main.doc == main.history.doc, "Undo-History zeigt weiter auf dasselbe doc")
|
||||||
|
_check(main.camera != null and is_instance_valid(main.camera), "3D-Kamera nach Rebuild vorhanden")
|
||||||
|
_check(main.view_builder != null and is_instance_valid(main.view_builder), "Map-Ansicht nach Rebuild vorhanden")
|
||||||
|
|
||||||
|
|
||||||
|
func _test_headless_safety() -> void:
|
||||||
|
print("Testgruppe: Headless-Sicherheit")
|
||||||
|
# Headless: Fenster-Aufrufe dürfen nicht krachen (Guards springen an).
|
||||||
|
main._apply_window_state()
|
||||||
|
main._save_window_state()
|
||||||
|
_check(true, "Fenster-Aufrufe headless ohne Crash")
|
||||||
|
# Ungültige Menü-Indices harmlos:
|
||||||
|
main._on_ui_size_selected(-1)
|
||||||
|
main._on_ui_size_selected(99)
|
||||||
|
_check(true, "Ungültige Menü-Indices ohne Crash")
|
||||||
|
_check(UIScale.step_index() == 0, "Stufe bleibt Klein nach ungültigen Aufrufen")
|
||||||
1
tests/test_ui_scale.gd.uid
Normal file
1
tests/test_ui_scale.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cxpf27vu88wq1
|
||||||
Loading…
Reference in a new issue