Shortcuts 3/3 (t_11b5c25b): Strg+O/N/Umschalt+S + Redo-Wege dokumentiert, F1-F4 Werkzeugwechsel, H-Kurzhilfe-Overlay, F5 Karte testen, Dirty-Schutz vor Neu/Oeffnen, permanente Werkzeug-Anzeige in der Statuszeile.
This commit is contained in:
parent
08a75ac430
commit
afcbda8dd0
9 changed files with 527 additions and 14 deletions
|
|
@ -1,9 +1,11 @@
|
||||||
# Tastatur & Maus — Map-Editor
|
# Tastatur & Maus — Map-Editor
|
||||||
|
|
||||||
Deutsche Tastenbelegung des ScopedVR Map-Editors. **Diese Datei ist die Source
|
Deutsche Tastenbelegung des ScopedVR Map-Editors. **Diese Datei ist die Source
|
||||||
of truth** für alle Shortcut-Karten (Karte 1/3: t_c46d31c2 — Navigation,
|
of truth** für alle Shortcut-Karten — FINALE Version nach allen drei Karten:
|
||||||
Auswahl, Löschen; Karte 2/3: t_55257792 — Objektmanipulation + Terrain;
|
Karte 1/3 (t_c46d31c2): Navigation, Auswahl, Löschen · Karte 2/3 (t_55257792):
|
||||||
Karte 3/3 ergänzt hier).
|
Objektmanipulation + Terrain · Karte 3/3 (t_11b5c25b): Datei & Undo,
|
||||||
|
Werkzeugwechsel, Hilfe, Test. Das Kurzhilfe-Overlay im Editor (Taste **H**)
|
||||||
|
zeigt dieselbe Tabelle (Quelle: `src/ui/help_text.gd`).
|
||||||
|
|
||||||
Konventionen (alle Karten):
|
Konventionen (alle Karten):
|
||||||
|
|
||||||
|
|
@ -62,10 +64,23 @@ alle, Strg+G gruppieren / Strg+Umschalt+G lösen.
|
||||||
| **G** | Auswahl **greifen** (Blender-Semantik): sie hängt an der Maus, Linksklick setzt die neue Position (mit Raster-Snap bzw. Strg-Zwang), **Esc** bricht ab und setzt die exakten alten Positionen zurück. Greifen der ganzen Gruppe/mehreren Objekte wandert gemeinsam |
|
| **G** | Auswahl **greifen** (Blender-Semantik): sie hängt an der Maus, Linksklick setzt die neue Position (mit Raster-Snap bzw. Strg-Zwang), **Esc** bricht ab und setzt die exakten alten Positionen zurück. Greifen der ganzen Gruppe/mehreren Objekte wandert gemeinsam |
|
||||||
| **Entf (DELETE)** | Auswahl SOFORT löschen, ohne Rückfrage — Strg+Z holt alles zurück. Wirkt auf Szene-Auswahl UND Objektbaum-Markierung |
|
| **Entf (DELETE)** | Auswahl SOFORT löschen, ohne Rückfrage — Strg+Z holt alles zurück. Wirkt auf Szene-Auswahl UND Objektbaum-Markierung |
|
||||||
| **Strg+D** | Auswahl duplizieren: Kopie +2 m in X versetzt (Rasterversatz), Füßpunkt unverändert; bei Mehrfachauswahl ALLE Objekte mit EINEM Undo-Schritt |
|
| **Strg+D** | Auswahl duplizieren: Kopie +2 m in X versetzt (Rasterversatz), Füßpunkt unverändert; bei Mehrfachauswahl ALLE Objekte mit EINEM Undo-Schritt |
|
||||||
| Strg+Z / Strg+Y | Rückgängig / Wiederherstellen |
|
|
||||||
| Strg+S | Karte speichern |
|
|
||||||
| Strg+G / Strg+Umschalt+G | Auswahl gruppieren / Gruppe lösen |
|
| Strg+G / Strg+Umschalt+G | Auswahl gruppieren / Gruppe lösen |
|
||||||
|
|
||||||
|
## Datei & Undo (Karte 3/3)
|
||||||
|
|
||||||
|
Vor „Neue Map“ und „Öffnen“ fragt der Editor NACH, wenn die Map noch
|
||||||
|
ungespeicherte Änderungen hat (deutscher Dialog „Verwerfen / Abbrechen“) —
|
||||||
|
nichts geht versehentlich verloren.
|
||||||
|
|
||||||
|
| Eingabe | Wirkung |
|
||||||
|
| --- | --- |
|
||||||
|
| **Strg+Z** | Rückgängig (EIN Eintrag pro Operation) |
|
||||||
|
| **Strg+Y** UND **Strg+Umschalt+Z** | Wiederherstellen (Redo) — beide Wege sind belegt |
|
||||||
|
| **Strg+S** | Map speichern (.svmap, bekannter Pfad; noch nie gespeichert → „Speichern unter“) |
|
||||||
|
| **Strg+Umschalt+S** | Map speichern unter … |
|
||||||
|
| **Strg+O** | Map öffnen |
|
||||||
|
| **Strg+N** | Neue Map |
|
||||||
|
|
||||||
## Terrain-Werkzeug (Karte 2/3) — nur aktiv, wenn das Terrain-Werkzeug an ist
|
## Terrain-Werkzeug (Karte 2/3) — nur aktiv, wenn das Terrain-Werkzeug an ist
|
||||||
|
|
||||||
| Eingabe | Wirkung |
|
| Eingabe | Wirkung |
|
||||||
|
|
@ -82,12 +97,31 @@ Konfliktfreiheit: Die Ziffern 1–4 sind NUR im Terrain-Werkzeug belegt — im
|
||||||
Objekt-/Bauen-Modus sind sie unbenutzt, im Flugmodus (rechte Maustaste
|
Objekt-/Bauen-Modus sind sie unbenutzt, im Flugmodus (rechte Maustaste
|
||||||
gehalten) bleiben E/Q die Kamera.
|
gehalten) bleiben E/Q die Kamera.
|
||||||
|
|
||||||
## Werkzeuge
|
## Werkzeuge (Karte 3/3: F-Tasten wechseln das Werkzeug)
|
||||||
|
|
||||||
| Eingabe | Wirkung |
|
| Eingabe | Wirkung |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| **F1** | Werkzeug: Terrain formen |
|
||||||
|
| **F2** | Werkzeug: Objekte platzieren/auswählen (Standard) |
|
||||||
|
| **F3** | Werkzeug: Bauen (Häuser) |
|
||||||
|
| **F4** | Werkzeug: Spawn-Zonen malen |
|
||||||
| Esc (im Terrain-/Spawn-/Bauen-Werkzeug) | Werkzeug beenden — zurück zum Objekt-Werkzeug |
|
| Esc (im Terrain-/Spawn-/Bauen-Werkzeug) | Werkzeug beenden — zurück zum Objekt-Werkzeug |
|
||||||
| Esc (beim Greifen) | Greifen abbrechen, alte Positionen zurück |
|
| Esc (beim Greifen) | Greifen abbrechen, alte Positionen zurück |
|
||||||
| Esc (beim Platzieren) | Ghost-Preview abbrechen |
|
| Esc (beim Platzieren) | Ghost-Preview abbrechen |
|
||||||
|
| Esc (Kurzhilfe offen) | Kurzhilfe schließen |
|
||||||
|
|
||||||
*(Karte 3/3 ergänzt hier ihre Belegungen.)*
|
F1–F4 und F5/H wirken NUR, wenn kein Textfeld/SpinBox fokussiert ist und kein
|
||||||
|
Dialog offen ist (ShortcutPolicy) — Tippen geht immer vor.
|
||||||
|
|
||||||
|
Die Terrain-Ziffern 1–4 und [ / ] bleiben (Karte 2/3) NUR im Terrain-Werkzeug
|
||||||
|
belegt — der Werkzeugwechsel läuft jetzt über die F-Tasten, nicht über Ziffern.
|
||||||
|
|
||||||
|
## Hilfe & Test (Karte 3/3)
|
||||||
|
|
||||||
|
| Eingabe | Wirkung |
|
||||||
|
| --- | --- |
|
||||||
|
| **H** | Kurzhilfe-Overlay an/aus — alle Tasten dieser Datei als Panel über der Karte; Esc schließt ebenfalls. Nicht-blockierend: der Editor bleibt bedienbar |
|
||||||
|
| **F5** | ▶ Karte testen (aktuelle Map sofort als Spiel durchlaufen; der Editor bleibt offen) |
|
||||||
|
|
||||||
|
Die Statuszeile unten zeigt PERMANENT das aktive Werkzeug („Werkzeug: …“) plus
|
||||||
|
den Hinweis „Umschalt+F = Flugmodus“ — man sieht immer, wo man ist.
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,25 @@ const ACTIONS := {
|
||||||
"editor_brush_lower": [KEY_2],
|
"editor_brush_lower": [KEY_2],
|
||||||
"editor_brush_smooth": [KEY_3],
|
"editor_brush_smooth": [KEY_3],
|
||||||
"editor_brush_paint": [KEY_4],
|
"editor_brush_paint": [KEY_4],
|
||||||
|
# --- Datei & Undo (Karte t_11b5c25b, Karte 3/3) --------------------------
|
||||||
|
# Strg+Z/Strg+Y laufen seit dem M2-UX-Fundament; hier NACHGEBUGGEN als
|
||||||
|
# dokumentierte Actions (alle 3 Shortcut-Karten: ACTIONS-Dict = Source of
|
||||||
|
# truth). Strg+Umschalt+Z ist der zweite Redo-Weg (beide belegt).
|
||||||
|
"editor_undo": [{"key": KEY_Z, "ctrl": true}],
|
||||||
|
"editor_redo_y": [{"key": KEY_Y, "ctrl": true}],
|
||||||
|
"editor_redo_shift_z": [{"key": KEY_Z, "ctrl": true, "shift": true}],
|
||||||
|
"editor_save": [{"key": KEY_S, "ctrl": true}],
|
||||||
|
"editor_save_as": [{"key": KEY_S, "ctrl": true, "shift": true}],
|
||||||
|
"editor_open_map": [{"key": KEY_O, "ctrl": true}],
|
||||||
|
"editor_new_map": [{"key": KEY_N, "ctrl": true}],
|
||||||
|
# --- Werkzeugwechsel (F-Tasten statt Ziffern, Slys Weiche) ---------------
|
||||||
|
"editor_tool_terrain": [KEY_F1],
|
||||||
|
"editor_tool_objects": [KEY_F2],
|
||||||
|
"editor_tool_bauen": [KEY_F3],
|
||||||
|
"editor_tool_spawns": [KEY_F4],
|
||||||
|
# --- Kurzhilfe-Overlay + Test-Platzhalter --------------------------------
|
||||||
|
"editor_help_overlay": [KEY_H],
|
||||||
|
"editor_test_map": [KEY_F5],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,18 @@ static func esc_action(tool_mode: String, placing: bool, has_selection: bool,
|
||||||
if has_selection:
|
if has_selection:
|
||||||
return "clear_selection"
|
return "clear_selection"
|
||||||
return "none"
|
return "none"
|
||||||
|
|
||||||
|
|
||||||
|
## Karte 3/3 (t_11b5c25b): Dürfen die globalen Tasten (F1–F5, H) gerade wirken?
|
||||||
|
## Nein, wenn ein Textfeld/SpinBox fokussiert ist (Tippen geht vor) oder ein
|
||||||
|
## Dialog/Fenster offen ist (Taste soll nicht HINTER dem Dialog wirken).
|
||||||
|
## Pure Funktion — main.gd liefert nur die beiden Fakten (testbar, wie esc_action).
|
||||||
|
static func tool_keys_allowed(text_focused: bool, dialog_open: bool) -> bool:
|
||||||
|
return not text_focused and not dialog_open
|
||||||
|
|
||||||
|
|
||||||
|
## Karte 3/3: Verlust-Operation (Neue Map / Öffnen) bei ungespeicherten
|
||||||
|
## Änderungen → erst NACHFRAGEN (deutscher Dialog), sonst direkt ausführen.
|
||||||
|
## Pure Entscheidung — der Dialog selbst lebt in main.gd.
|
||||||
|
static func needs_dirty_confirm(dirty: bool) -> bool:
|
||||||
|
return dirty
|
||||||
86
src/ui/help_text.gd
Normal file
86
src/ui/help_text.gd
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
extends RefCounted
|
||||||
|
## Kurzhilfe-Overlay (Karte t_11b5c25b, Karte 3/3) — H schaltet das Panel an,
|
||||||
|
## H/Esc aus. Der Text spiegelt docs/tasten.md (Source of truth der Shortcut-
|
||||||
|
## Karten) als Kompakt-Tabelle für den Editor-Bildschirm. Bei neuen Tasten:
|
||||||
|
## docs/tasten.md UND hier pflegen.
|
||||||
|
##
|
||||||
|
## BBCode-Tabelle ([table=2]): Taste | Wirkung. Bei neuen Tasten die Zeile in
|
||||||
|
## der passenden Sektion ergänzen (text() aufbauen, _rows() macht die Zellen).
|
||||||
|
|
||||||
|
static func text() -> String:
|
||||||
|
var out := ""
|
||||||
|
out += _header("Navigation (Kamera)")
|
||||||
|
out += _table(_rows([
|
||||||
|
["Mittlere Maustaste ziehen", "Orbit: um die Karte drehen"],
|
||||||
|
["Mittlere Maustaste + Umschalt ziehen", "Pan: Karte verschieben"],
|
||||||
|
["Mausrad", "Zoom (aus, während ein Objekt gedreht wird)"],
|
||||||
|
["Rechte Maustaste GEDACHT halten", "Freelook-Flug: Umsehen + W vor / S zurück / A links / D rechts; E hoch / Q runter"],
|
||||||
|
["Umschalt (im Flug)", "schneller fliegen (×3)"],
|
||||||
|
["Alt (im Flug)", "langsamer fliegen (×0,3)"],
|
||||||
|
["Mausrad (im Flug)", "Grundgeschwindigkeit dauerhaft ändern (2–300 m/s)"],
|
||||||
|
["Umschalt+F", "Flugmodus dauerhaft an/aus"],
|
||||||
|
["F", "Auswahl ins Bild zentrieren"],
|
||||||
|
]))
|
||||||
|
out += _header("Auswahl")
|
||||||
|
out += _table(_rows([
|
||||||
|
["Linksklick", "Objekt auswählen (ersetzt die Auswahl)"],
|
||||||
|
["Strg+Klick (Viewport + Baum)", "Objekt zur Auswahl hinzufügen/entfernen (Mehrfachauswahl)"],
|
||||||
|
["Umschalt+Klick", "wie Strg+Klick (Mehrfachauswahl umschalten)"],
|
||||||
|
["Ziehen über leeren Boden", "Rahmen aufziehen: alle Objekte im Rahmen wählen"],
|
||||||
|
["Umschalt + Rahmen", "Rahmen-Auswahl zur bestehenden Auswahl hinzufügen"],
|
||||||
|
["Esc", "Werkzeug abbrechen → Greifen zurückrollen → Ghost abbrechen → Auswahl aufheben"],
|
||||||
|
["G", "Auswahl greifen (hängt an der Maus; Klick setzt, Esc rollt zurück)"],
|
||||||
|
]))
|
||||||
|
out += _header("Objekt-Manipulation")
|
||||||
|
out += _table(_rows([
|
||||||
|
["R", "90° drehen (Umschalt+R = −90°, ganze Gruppe dreht mit; Mausrad: 15° fein)"],
|
||||||
|
["Pfeiltasten", "Auswahl im 2-m-Raster verschieben (Kamera-nächste Weltachse)"],
|
||||||
|
["Bild↑ / Bild↓", "+0,25 m / −0,25 m anheben/absenken"],
|
||||||
|
["Strg+Bild↑", "Auswahl aufs Terrain/Boden absetzen"],
|
||||||
|
["Strg halten", "temporärer Snap-Zwang (rastet auf ganze Meter)"],
|
||||||
|
["Strg+D", "Auswahl duplizieren (+2 m versetzt)"],
|
||||||
|
["Entf", "Auswahl sofort löschen (Strg+Z holt zurück)"],
|
||||||
|
["Strg+G / Strg+Umschalt+G", "Gruppieren / Gruppe lösen"],
|
||||||
|
]))
|
||||||
|
out += _header("Terrain-Werkzeug (F1)")
|
||||||
|
out += _table(_rows([
|
||||||
|
["1 / 2 / 3 / 4", "Pinselmodus: Heben / Senken / Glätten / Malen"],
|
||||||
|
["[ / ]", "Pinselgröße: Klein (4 m) / Mittel (12 m) / Groß (32 m) zyklisch"],
|
||||||
|
["Linksklick-Ziehen", "Boden formen bzw. malen"],
|
||||||
|
["Rechtsklick-Ziehen", "Gegenteil formen / Gras zurückmalen"],
|
||||||
|
]))
|
||||||
|
out += _header("Datei & Undo")
|
||||||
|
out += _table(_rows([
|
||||||
|
["Strg+Z", "Rückgängig"],
|
||||||
|
["Strg+Y oder Strg+Umschalt+Z", "Wiederherstellen (Redo)"],
|
||||||
|
["Strg+S", "Map speichern (.svmap, bekannter Pfad)"],
|
||||||
|
["Strg+Umschalt+S", "Map speichern unter …"],
|
||||||
|
["Strg+O", "Map öffnen"],
|
||||||
|
["Strg+N", "Neue Map"],
|
||||||
|
]))
|
||||||
|
out += _header("Werkzeuge & Tests")
|
||||||
|
out += _table(_rows([
|
||||||
|
["F1", "Werkzeug: Terrain formen"],
|
||||||
|
["F2", "Werkzeug: Objekte platzieren/auswählen"],
|
||||||
|
["F3", "Werkzeug: Bauen (Häuser)"],
|
||||||
|
["F4", "Werkzeug: Spawn-Zonen malen"],
|
||||||
|
["F5", "▶ Karte testen"],
|
||||||
|
["H", "Diese Kurzhilfe an/aus (Esc schließt auch)"],
|
||||||
|
]))
|
||||||
|
out += "[color=#a8b6ad]Vor „Neue Map“ und „Öffnen“ fragt der Editor nach, wenn noch ungespeicherte Änderungen da sind. Die Statuszeile unten zeigt immer das aktive Werkzeug.[/color]\n"
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
static func _header(title: String) -> String:
|
||||||
|
return "\n[color=#8cc9f2][b]%s[/b][/color]\n\n" % title
|
||||||
|
|
||||||
|
|
||||||
|
static func _table(rows: String) -> String:
|
||||||
|
return "[table=2]%s[/table]\n" % rows
|
||||||
|
|
||||||
|
|
||||||
|
static func _rows(pairs: Array) -> String:
|
||||||
|
var out := ""
|
||||||
|
for pair in pairs:
|
||||||
|
out += "[cell][b]%s[/b][/cell][cell]%s[/cell]" % [str(pair[0]), str(pair[1])]
|
||||||
|
return out
|
||||||
1
src/ui/help_text.gd.uid
Normal file
1
src/ui/help_text.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cmq500ivk0ro2
|
||||||
177
src/ui/main.gd
177
src/ui/main.gd
|
|
@ -21,6 +21,8 @@ const ShortcutKeys := preload("res://src/editor/shortcut_keys.gd")
|
||||||
const ShortcutPolicy := preload("res://src/editor/shortcut_policy.gd")
|
const ShortcutPolicy := preload("res://src/editor/shortcut_policy.gd")
|
||||||
# Shortcuts-Mathe Karte 2/3 (t_55257792): Pfeil-Raster + Pinsel-Presets.
|
# Shortcuts-Mathe Karte 2/3 (t_55257792): Pfeil-Raster + Pinsel-Presets.
|
||||||
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.
|
||||||
|
const HelpText := preload("res://src/ui/help_text.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)
|
||||||
|
|
@ -116,6 +118,14 @@ var check_text: Label
|
||||||
# Karte testen (Karte t_b84af40f)
|
# Karte testen (Karte t_b84af40f)
|
||||||
var test_btn: Button
|
var test_btn: Button
|
||||||
|
|
||||||
|
# Karte 3/3 (t_11b5c25b): Dirty-Schutz + Kurzhilfe + Werkzeug-Statuszeile.
|
||||||
|
var dirty_dialog: ConfirmationDialog # „Ungespeicherte Änderungen verwerfen?“
|
||||||
|
var _pending_after_dirty := Callable() # was nach „Verwerfen“ ausgeführt wird
|
||||||
|
var help_overlay: PanelContainer # H-Kurzhilfe (nicht-blockierend)
|
||||||
|
var help_overlay_title: Label
|
||||||
|
var help_overlay_text: RichTextLabel
|
||||||
|
var tool_label: Label # permanenter Werkzeugmodus + Flug-Hint
|
||||||
|
|
||||||
# Dialoge
|
# Dialoge
|
||||||
var new_dialog: AcceptDialog
|
var new_dialog: AcceptDialog
|
||||||
var open_dialog: FileDialog
|
var open_dialog: FileDialog
|
||||||
|
|
@ -133,6 +143,7 @@ func _ready() -> void:
|
||||||
ShortcutKeys.register()
|
ShortcutKeys.register()
|
||||||
manifest = _load_manifest()
|
manifest = _load_manifest()
|
||||||
_build_ui()
|
_build_ui()
|
||||||
|
_update_tool_label()
|
||||||
_start_with_new_map()
|
_start_with_new_map()
|
||||||
_maybe_start_demo_mode()
|
_maybe_start_demo_mode()
|
||||||
|
|
||||||
|
|
@ -260,6 +271,12 @@ func _build_ui() -> void:
|
||||||
status_label.clip_text = true # langer Text darf das Layout NICHT sprengen
|
status_label.clip_text = true # langer Text darf das Layout NICHT sprengen
|
||||||
status_h.add_child(status_label)
|
status_h.add_child(status_label)
|
||||||
|
|
||||||
|
# Karte 3/3 (t_11b5c25b): PERMANENTE Werkzeug-Anzeige — Sly sieht immer,
|
||||||
|
# in welchem Werkzeug er ist, plus Hint für den Flugmodus (Umschalt+F).
|
||||||
|
tool_label = Label.new()
|
||||||
|
tool_label.modulate = Color(0.55, 0.8, 1.0)
|
||||||
|
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)
|
||||||
status_h.add_child(stats_objects)
|
status_h.add_child(stats_objects)
|
||||||
|
|
@ -316,6 +333,52 @@ func _build_ui() -> void:
|
||||||
check_dialog.add_child(check_text)
|
check_dialog.add_child(check_text)
|
||||||
add_child(check_dialog)
|
add_child(check_dialog)
|
||||||
|
|
||||||
|
# Karte 3/3 (t_11b5c25b): Dirty-Schutz vor „Neue Map“ / „Öffnen“ — deutsche
|
||||||
|
# Klartext-Frage, „Verwerfen“ führt die wartende Aktion aus, „Abbrechen“ tut
|
||||||
|
# nichts (Map bleibt, wie sie ist).
|
||||||
|
dirty_dialog = ConfirmationDialog.new()
|
||||||
|
dirty_dialog.title = "Ungespeicherte Änderungen"
|
||||||
|
dirty_dialog.dialog_text = "Die Map hat ungespeicherte Änderungen — beim Fortfahren gehen sie verloren.\nJetzt verwerfen und fortfahren?"
|
||||||
|
dirty_dialog.ok_button_text = "Verwerfen"
|
||||||
|
dirty_dialog.cancel_button_text = "Abbrechen"
|
||||||
|
dirty_dialog.confirmed.connect(_on_dirty_confirmed)
|
||||||
|
add_child(dirty_dialog)
|
||||||
|
|
||||||
|
# Kurzhilfe-Overlay (Karte 3/3): H schaltet es an, H/Esc aus. Kein modal
|
||||||
|
# Dialog — der Editor bleibt bedienbar, das Panel liegt nur obenauf.
|
||||||
|
help_overlay = PanelContainer.new()
|
||||||
|
var overlay_style := StyleBoxFlat.new()
|
||||||
|
overlay_style.bg_color = Color(0.09, 0.11, 0.13, 0.97) # Slate, wie Statuszeile
|
||||||
|
overlay_style.border_color = Color(0.35, 0.55, 0.7)
|
||||||
|
overlay_style.set_border_width_all(2)
|
||||||
|
overlay_style.set_corner_radius_all(8)
|
||||||
|
overlay_style.set_content_margin_all(14)
|
||||||
|
help_overlay.add_theme_stylebox_override("panel", overlay_style)
|
||||||
|
# Zentriertes Panel statt Vollbild: der Editor bleibt rundherum bedienbar
|
||||||
|
# (nicht-blockierend), das Panel ist groß genug für die ganze Tabelle.
|
||||||
|
# Grow BOTH: das Panel wächst SYMMETRISCH um den Mittelpunkt (sonst hängt
|
||||||
|
# seine linke obere Ecke am Zentrum und es läuft rechts/unten aus dem Bild).
|
||||||
|
help_overlay.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
|
||||||
|
help_overlay.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||||
|
help_overlay.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||||
|
help_overlay.custom_minimum_size = Vector2(880, 640)
|
||||||
|
help_overlay.visible = false
|
||||||
|
var overlay_box := VBoxContainer.new()
|
||||||
|
overlay_box.add_theme_constant_override("separation", 6)
|
||||||
|
help_overlay.add_child(overlay_box)
|
||||||
|
help_overlay_title = Label.new()
|
||||||
|
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_color_override("font_color", Color(0.55, 0.8, 1.0))
|
||||||
|
overlay_box.add_child(help_overlay_title)
|
||||||
|
help_overlay_text = RichTextLabel.new()
|
||||||
|
help_overlay_text.bbcode_enabled = true
|
||||||
|
help_overlay_text.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
help_overlay_text.custom_minimum_size = Vector2(0, 0)
|
||||||
|
help_overlay_text.text = HelpText.text()
|
||||||
|
overlay_box.add_child(help_overlay_text)
|
||||||
|
add_child(help_overlay)
|
||||||
|
|
||||||
|
|
||||||
func _build_left_panel() -> PanelContainer:
|
func _build_left_panel() -> PanelContainer:
|
||||||
var panel := PanelContainer.new()
|
var panel := PanelContainer.new()
|
||||||
|
|
@ -888,10 +951,10 @@ func _start_with_new_map() -> void:
|
||||||
|
|
||||||
func _on_menu_action(id: int) -> void:
|
func _on_menu_action(id: int) -> void:
|
||||||
match id:
|
match id:
|
||||||
0: # Neue Map
|
0: # Neue Map — bei ungespeicherten Änderungen erst NACHFRAGEN (Karte 3/3)
|
||||||
new_dialog.popup_centered()
|
_request_lossy(func() -> void: new_dialog.popup_centered())
|
||||||
1: # Öffnen
|
1: # Öffnen — ditto Dirty-Schutz
|
||||||
open_dialog.popup_centered()
|
_request_lossy(func() -> void: open_dialog.popup_centered())
|
||||||
2: # Speichern
|
2: # Speichern
|
||||||
_save()
|
_save()
|
||||||
3: # Speichern unter
|
3: # Speichern unter
|
||||||
|
|
@ -1022,6 +1085,66 @@ func _show_errors(title: String, errors: Array) -> void:
|
||||||
error_dialog.popup_centered()
|
error_dialog.popup_centered()
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------- Karte 3/3: Globale Shortcuts ---
|
||||||
|
|
||||||
|
## Verlust-Operation (Neue Map / Öffnen): bei ungespeicherten Änderungen erst
|
||||||
|
## NACHFRAGEN (deutscher Dialog), sonst direkt ausführen. Die wartende Aktion
|
||||||
|
## liegt in _pending_after_dirty und läuft bei „Verwerfen“ an.
|
||||||
|
func _request_lossy(next: Callable) -> void:
|
||||||
|
if ShortcutPolicy.needs_dirty_confirm(dirty):
|
||||||
|
_pending_after_dirty = next
|
||||||
|
dirty_dialog.popup_centered()
|
||||||
|
return
|
||||||
|
next.call()
|
||||||
|
|
||||||
|
|
||||||
|
## „Verwerfen“ im Dirty-Dialog: die wartende Aktion (Neu/Öffnen) jetzt ausführen.
|
||||||
|
func _on_dirty_confirmed() -> void:
|
||||||
|
var next := _pending_after_dirty
|
||||||
|
_pending_after_dirty = Callable()
|
||||||
|
if next.is_valid():
|
||||||
|
next.call()
|
||||||
|
|
||||||
|
|
||||||
|
## Karte 3/3: Dürfen die globalen Tasten (F1–F5, H) gerade wirken? Pure Policy
|
||||||
|
## in ShortcutPolicy — hier nur die Fakten: Textfeld/SpinBox fokussiert? Ein
|
||||||
|
## Dialog/Popup offen? (Eigene Fenster des Editors; das Hilfe-Panel ist
|
||||||
|
## absichtlich KEIN Fenster und zählt nicht.)
|
||||||
|
func _tool_key_allowed() -> bool:
|
||||||
|
var focus := get_viewport().gui_get_focus_owner()
|
||||||
|
var text_focused: bool = focus != null \
|
||||||
|
and (focus is LineEdit or focus is TextEdit or focus is SpinBox)
|
||||||
|
var dialog_open := false
|
||||||
|
for child in get_children():
|
||||||
|
if child is Window and child.visible:
|
||||||
|
dialog_open = true
|
||||||
|
break
|
||||||
|
return ShortcutPolicy.tool_keys_allowed(text_focused, dialog_open)
|
||||||
|
|
||||||
|
|
||||||
|
## Kurzhilfe-Overlay umschalten; Statuszeile meldet den Zustand.
|
||||||
|
func _toggle_help_overlay() -> void:
|
||||||
|
help_overlay.visible = not help_overlay.visible
|
||||||
|
if help_overlay.visible:
|
||||||
|
_set_status("Kurzhilfe an — H oder Esc schließt das Overlay wieder.")
|
||||||
|
else:
|
||||||
|
_base_tool_status()
|
||||||
|
|
||||||
|
|
||||||
|
## Permanente Werkzeug-Anzeige (Karte 3/3): aktives Werkzeug + Flug-Hint —
|
||||||
|
## Sly sieht in der Statuszeile immer, wo er ist.
|
||||||
|
func _update_tool_label() -> void:
|
||||||
|
if tool_label == null:
|
||||||
|
return
|
||||||
|
var names := {
|
||||||
|
"objects": "Objekte (F2)",
|
||||||
|
"terrain": "Terrain (F1)",
|
||||||
|
"bauen": "Bauen (F3)",
|
||||||
|
"spawns": "Spawn-Zonen (F4)",
|
||||||
|
}
|
||||||
|
tool_label.text = "Werkzeug: %s · Umschalt+F = Flugmodus" % str(names.get(tool_mode, tool_mode))
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------- History ----
|
# --------------------------------------------------------------- History ----
|
||||||
|
|
||||||
func _do_undo() -> void:
|
func _do_undo() -> void:
|
||||||
|
|
@ -1090,8 +1213,45 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
KEY_S:
|
KEY_S:
|
||||||
if ctrl:
|
if ctrl:
|
||||||
|
if event.shift_pressed:
|
||||||
|
# Strg+Umschalt+S (Karte 3/3): Speichern unter …
|
||||||
|
save_dialog.popup_centered()
|
||||||
|
else:
|
||||||
_save()
|
_save()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
KEY_O:
|
||||||
|
if ctrl:
|
||||||
|
# Strg+O (Karte 3/3): Map öffnen — bei dirty erst NACHFRAGEN.
|
||||||
|
_request_lossy(func() -> void: open_dialog.popup_centered())
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
KEY_N:
|
||||||
|
if ctrl:
|
||||||
|
# Strg+N (Karte 3/3): Neue Map — bei dirty erst NACHFRAGEN.
|
||||||
|
_request_lossy(func() -> void: new_dialog.popup_centered())
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5:
|
||||||
|
# Karte 3/3: Werkzeugwechsel + Test-Platzhalter. NUR wirken, wenn
|
||||||
|
# kein Textfeld tippt und kein Dialog offen ist (ShortcutPolicy).
|
||||||
|
if _tool_key_allowed():
|
||||||
|
match key:
|
||||||
|
KEY_F1:
|
||||||
|
_set_tool_mode("terrain")
|
||||||
|
KEY_F2:
|
||||||
|
_set_tool_mode("objects")
|
||||||
|
KEY_F3:
|
||||||
|
_set_tool_mode("bauen")
|
||||||
|
KEY_F4:
|
||||||
|
_set_tool_mode("spawns")
|
||||||
|
KEY_F5:
|
||||||
|
# ▶ Karte testen: der echte Freigabe-/Test-Flow existiert
|
||||||
|
# (Karte t_b84af40f) — F5 ruft ihn direkt auf.
|
||||||
|
_on_test_run()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
KEY_H:
|
||||||
|
# Karte 3/3: Kurzhilfe-Overlay an/aus (Esc schließt ebenfalls).
|
||||||
|
if _tool_key_allowed():
|
||||||
|
_toggle_help_overlay()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
KEY_G:
|
KEY_G:
|
||||||
if ctrl:
|
if ctrl:
|
||||||
# Gruppieren/Lösen (Karte t_f6f29d86)
|
# Gruppieren/Lösen (Karte t_f6f29d86)
|
||||||
|
|
@ -1132,6 +1292,12 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||||
_focus_selection()
|
_focus_selection()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
KEY_ESCAPE:
|
KEY_ESCAPE:
|
||||||
|
# Karte 3/3: Steht das Hilfe-Overlay offen, gehört Esc IHM (schließen)
|
||||||
|
# — VOR der normalen Esc-Prioritätskette.
|
||||||
|
if help_overlay.visible:
|
||||||
|
_toggle_help_overlay()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
# Esc-Priorität über pure Funktion (Karte t_c46d31c2, testbar):
|
# Esc-Priorität über pure Funktion (Karte t_c46d31c2, testbar):
|
||||||
# Werkzeug verlassen → Greifen zurückrollen → Ghost-Preview
|
# Werkzeug verlassen → Greifen zurückrollen → Ghost-Preview
|
||||||
# abbrechen → Auswahl aufheben (Greifen: Karte t_55257792).
|
# abbrechen → Auswahl aufheben (Greifen: Karte t_55257792).
|
||||||
|
|
@ -1326,6 +1492,7 @@ func _set_tool_mode(mode: String) -> void:
|
||||||
_sync_terrain_button()
|
_sync_terrain_button()
|
||||||
_sync_spawn_button()
|
_sync_spawn_button()
|
||||||
_sync_build_button()
|
_sync_build_button()
|
||||||
|
_update_tool_label()
|
||||||
return
|
return
|
||||||
if mode == "terrain":
|
if mode == "terrain":
|
||||||
_leave_spawn_mode()
|
_leave_spawn_mode()
|
||||||
|
|
@ -1344,6 +1511,7 @@ func _set_tool_mode(mode: String) -> void:
|
||||||
_leave_spawn_mode()
|
_leave_spawn_mode()
|
||||||
_leave_build_mode()
|
_leave_build_mode()
|
||||||
_enter_objects_mode()
|
_enter_objects_mode()
|
||||||
|
_update_tool_label()
|
||||||
|
|
||||||
|
|
||||||
func _enter_terrain_mode() -> void:
|
func _enter_terrain_mode() -> void:
|
||||||
|
|
@ -1570,6 +1738,7 @@ func _recheck_terrain_mode() -> void:
|
||||||
tool_mode = "objects"
|
tool_mode = "objects"
|
||||||
terrain_panel.visible = false
|
terrain_panel.visible = false
|
||||||
_sync_terrain_button()
|
_sync_terrain_button()
|
||||||
|
_update_tool_label()
|
||||||
help_label.text = "Linksklick: auswählen & ziehen · Umschalt+Klick: Auswahl erweitern · Rahmen aufziehen: mehrere wählen · Strg+G: gruppieren\nR: 90° drehen (Mausrad: 15° fein) · Pfeiltasten: 2-m-Raster · G: greifen (Esc: zurück) · Strg+D: duplizieren · Bild↑/↓: anheben/absenken\nStrg+Bild↑: auf Boden setzen · Strg halten: Snap-Zwang · 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: 90° drehen (Mausrad: 15° fein) · Pfeiltasten: 2-m-Raster · G: greifen (Esc: zurück) · Strg+D: duplizieren · Bild↑/↓: anheben/absenken\nStrg+Bild↑: auf Boden setzen · Strg halten: Snap-Zwang · Spiegeln rechts · Entf: löschen · Strg+Z: zurück"
|
||||||
_set_status("Terrain-Modus aus: Diese Map passt nicht ins Editor-MVP (Terrain-Auflösung über %d) — die Objekte bleiben normal bearbeitbar." % TerrainField.EDITOR_MAX_RESOLUTION, true)
|
_set_status("Terrain-Modus aus: Diese Map passt nicht ins Editor-MVP (Terrain-Auflösung über %d) — die Objekte bleiben normal bearbeitbar." % TerrainField.EDITOR_MAX_RESOLUTION, true)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ func _run_all() -> void:
|
||||||
_test_terrain_presets()
|
_test_terrain_presets()
|
||||||
_test_shortcut_undo()
|
_test_shortcut_undo()
|
||||||
_test_terrain_hotkeys()
|
_test_terrain_hotkeys()
|
||||||
|
_test_shortcuts_karte3()
|
||||||
|
|
||||||
_finish()
|
_finish()
|
||||||
|
|
||||||
|
|
@ -101,12 +102,13 @@ func _test_terrain_presets() -> void:
|
||||||
"Klick auf Klein setzt Slider auf 4 m")
|
"Klick auf Klein setzt Slider auf 4 m")
|
||||||
|
|
||||||
|
|
||||||
func _press_key(keycode: Key, ctrl: bool) -> InputEventKey:
|
func _press_key(keycode: Key, ctrl: bool, shift := false) -> InputEventKey:
|
||||||
var ev := InputEventKey.new()
|
var ev := InputEventKey.new()
|
||||||
ev.keycode = keycode
|
ev.keycode = keycode
|
||||||
ev.physical_keycode = keycode
|
ev.physical_keycode = keycode
|
||||||
ev.pressed = true
|
ev.pressed = true
|
||||||
ev.ctrl_pressed = ctrl
|
ev.ctrl_pressed = ctrl
|
||||||
|
ev.shift_pressed = shift
|
||||||
return ev
|
return ev
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -205,3 +207,112 @@ func _test_terrain_hotkeys() -> void:
|
||||||
"Ziffern/[ im Objekt-Modus: Pinsel bleibt unangetastet")
|
"Ziffern/[ im Objekt-Modus: Pinsel bleibt unangetastet")
|
||||||
_check(int(main.terrain_tool.get("brush_mode")) == TerrainField.Mode.RAISE,
|
_check(int(main.terrain_tool.get("brush_mode")) == TerrainField.Mode.RAISE,
|
||||||
"Ziffern im Objekt-Modus: Pinsel-Modus bleibt unangetastet")
|
"Ziffern im Objekt-Modus: Pinsel-Modus bleibt unangetastet")
|
||||||
|
|
||||||
|
|
||||||
|
## Karte 3/3 (t_11b5c25b): F1–F4 Werkzeugwechsel, F5 Test, H-Hilfe,
|
||||||
|
## Strg+O/N/S + Dirty-Dialog am ECHTEN main (Dialoge als --mock: sichtbar
|
||||||
|
## prüfen statt bedienen — headless gibt es keine echte Maus).
|
||||||
|
func _test_shortcuts_karte3() -> void:
|
||||||
|
print("Testgruppe: Karte 3/3 — F-Tasten, Hilfe, Datei, Dirty-Dialog")
|
||||||
|
|
||||||
|
# --- F1–F4: Werkzeugwechsel (Slys Weiche: F-Tasten statt Ziffern) --------
|
||||||
|
main._set_tool_mode("objects")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F1, false))
|
||||||
|
_check(str(main.get("tool_mode")) == "terrain", "F1: Terrain-Werkzeug aktiv")
|
||||||
|
_check(str(main.tool_label.text).contains("Terrain"), "Statuszeile: Werkzeug-Label zeigt Terrain")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F2, false))
|
||||||
|
_check(str(main.get("tool_mode")) == "objects", "F2: zurück zum Objekt-Werkzeug")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F3, false))
|
||||||
|
_check(str(main.get("tool_mode")) == "bauen", "F3: Bauen-Werkzeug aktiv")
|
||||||
|
_check(str(main.tool_label.text).contains("Bauen"), "Statuszeile: Werkzeug-Label zeigt Bauen")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F4, false))
|
||||||
|
_check(str(main.get("tool_mode")) == "spawns", "F4: Spawn-Werkzeug aktiv")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F2, false))
|
||||||
|
_check(str(main.get("tool_mode")) == "objects", "F2 aus dem Spawn-Werkzeug: sauber zurück")
|
||||||
|
|
||||||
|
# --- Fokus-Regel: Textfeld fokussiert → F-Tasten wirken NICHT ------------
|
||||||
|
main.prop_name.grab_focus()
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F1, false))
|
||||||
|
_check(str(main.get("tool_mode")) == "objects",
|
||||||
|
"F1 bei fokussiertem Textfeld: Werkzeug bleibt (Tippen geht vor)")
|
||||||
|
main.prop_name.release_focus()
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F1, false))
|
||||||
|
_check(str(main.get("tool_mode")) == "terrain", "F1 nach Release-Focus: wirkt wieder")
|
||||||
|
main._set_tool_mode("objects")
|
||||||
|
|
||||||
|
# --- Dialog-Regel: offener Dialog → F-Tasten wirken NICHT ----------------
|
||||||
|
main.error_dialog.popup_centered()
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F1, false))
|
||||||
|
_check(str(main.get("tool_mode")) == "objects",
|
||||||
|
"F1 bei offenem Dialog: Werkzeug bleibt")
|
||||||
|
main.error_dialog.hide()
|
||||||
|
|
||||||
|
# --- H: Kurzhilfe-Overlay an/aus (Esc schließt ebenfalls) ----------------
|
||||||
|
main._unhandled_key_input(_press_key(KEY_H, false))
|
||||||
|
_check(main.help_overlay.visible, "H: Kurzhilfe-Overlay sichtbar")
|
||||||
|
var help_txt: String = main.help_overlay_text.text
|
||||||
|
_check(help_txt.contains("Strg+Z") and help_txt.contains("F1") and help_txt.contains("Terrain"),
|
||||||
|
"Overlay enthält die Tastenliste aus docs/tasten.md")
|
||||||
|
_check(str(main.status_label.text).contains("Kurzhilfe an"), "Statuszeile meldet Overlay an")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_ESCAPE, false))
|
||||||
|
_check(not main.help_overlay.visible, "Esc schließt das Overlay")
|
||||||
|
_check(str(main.get("tool_mode")) == "objects", "Esc am Overlay: Objekt-Werkzeug unangetastet")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_H, false))
|
||||||
|
main._unhandled_key_input(_press_key(KEY_H, false))
|
||||||
|
_check(not main.help_overlay.visible, "H zweimal: Overlay wieder zu (Toggle)")
|
||||||
|
|
||||||
|
# --- F5: Karte testen (echter Flow existiert — hier läuft er in den
|
||||||
|
# „Client nicht gefunden“-Zweig, Hauptsache die Route ist verdrahtet) -----
|
||||||
|
main._unhandled_key_input(_press_key(KEY_F5, false))
|
||||||
|
_check(str(main.status_label.text).contains("Karte testen"),
|
||||||
|
"F5: Karte-testen-Flow angesprungen (Statuszeile meldet)")
|
||||||
|
_check(main.error_dialog.visible, "F5: Fehler-Dialog im nicht-gefunden-Zweig geöffnet")
|
||||||
|
main.error_dialog.hide()
|
||||||
|
|
||||||
|
# --- Dirty-Schutz: Strg+N bei ungespeicherten Änderungen FRAGT ZUERST ----
|
||||||
|
main.dirty = true
|
||||||
|
main._unhandled_key_input(_press_key(KEY_N, true))
|
||||||
|
_check(main.dirty_dialog.visible, "Strg+N bei dirty: Bestätigungs-Dialog ZUERST")
|
||||||
|
_check(not main.new_dialog.visible, "Strg+N bei dirty: Neue-Map-Dialog wartet hinterher")
|
||||||
|
_check(str(main.dirty_dialog.dialog_text).contains("ungespeicherte Änderungen"),
|
||||||
|
"Dirty-Dialog: deutsche Klartext-Meldung")
|
||||||
|
# „Verwerfen“ (confirmed) → jetzt erst geht der Neue-Map-Dialog auf.
|
||||||
|
main.dirty_dialog.confirmed.emit()
|
||||||
|
_check(main.new_dialog.visible, "Verwerfen bestätigt: Neue-Map-Dialog öffnet jetzt")
|
||||||
|
main.new_dialog.hide()
|
||||||
|
main.dirty = false
|
||||||
|
|
||||||
|
# --- Strg+O ohne dirty: Öffnen-Dialog DIREKT -----------------------------
|
||||||
|
main._unhandled_key_input(_press_key(KEY_O, true))
|
||||||
|
_check(main.open_dialog.visible, "Strg+N/Strg+O ohne dirty: Öffnen-Dialog direkt")
|
||||||
|
main.open_dialog.hide()
|
||||||
|
|
||||||
|
# --- Strg+S: speichert an bekannten Pfad, dirty wird aufgeräumt ----------
|
||||||
|
main.current_path = "/tmp/svmap_karte3_test.svmap"
|
||||||
|
main.dirty = true
|
||||||
|
main._unhandled_key_input(_press_key(KEY_S, true))
|
||||||
|
_check(FileAccess.file_exists("/tmp/svmap_karte3_test.svmap"), "Strg+S: .svmap geschrieben")
|
||||||
|
_check(not main.dirty, "Strg+S: dirty zurückgesetzt")
|
||||||
|
_check(str(main.status_label.text).contains("gespeichert"), "Strg+S: Statuszeile meldet Speichern")
|
||||||
|
|
||||||
|
# --- Strg+Umschalt+S: Speichern-unter-Dialog ------------------------------
|
||||||
|
main._unhandled_key_input(_press_key(KEY_S, true, true))
|
||||||
|
_check(main.save_dialog.visible, "Strg+Umschalt+S: Speichern-unter-Dialog")
|
||||||
|
main.save_dialog.hide()
|
||||||
|
|
||||||
|
# --- Strg+Umschalt+Z: der zweite Redo-Weg --------------------------------
|
||||||
|
var before: Dictionary = main.history.snapshot(["objects"])
|
||||||
|
(main.doc["objects"] as Array).append(
|
||||||
|
{"id": "test_redo_objekt", "pos": [4.0, 0.0, 4.0], "scale": 1.0,
|
||||||
|
"rot_y_deg": 0.0})
|
||||||
|
main.history.commit("Test: Redo-Objekt", before, main.history.snapshot(["objects"]))
|
||||||
|
main._unhandled_key_input(_press_key(KEY_Z, true))
|
||||||
|
_check((main.doc["objects"] as Array).size() == 0, "Strg+Z: Objekt weg (Undo)")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_Z, true, true))
|
||||||
|
_check((main.doc["objects"] as Array).size() == 1, "Strg+Umschalt+Z: Redo-Weg 2 stellt her")
|
||||||
|
main._unhandled_key_input(_press_key(KEY_Z, true))
|
||||||
|
main._unhandled_key_input(_press_key(KEY_Y, true))
|
||||||
|
_check((main.doc["objects"] as Array).size() == 1, "Strg+Y: Redo-Weg 1 stellt auch her")
|
||||||
|
# Aufräumen: Undo, damit spätere Gruppen (falls Testreihenfolge sich ändert)
|
||||||
|
# nicht von diesem Objekt ausgehen.
|
||||||
|
main._unhandled_key_input(_press_key(KEY_Z, true))
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ var checks := 0
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
ShortcutKeys.register()
|
ShortcutKeys.register()
|
||||||
_test_inputmap_actions()
|
_test_inputmap_actions()
|
||||||
|
_test_inputmap_actions_3()
|
||||||
|
_test_policy_3()
|
||||||
_test_fly_math()
|
_test_fly_math()
|
||||||
_test_esc_policy()
|
_test_esc_policy()
|
||||||
_test_esc_grab_policy()
|
_test_esc_grab_policy()
|
||||||
|
|
@ -194,6 +196,64 @@ func _test_inputmap_actions() -> void:
|
||||||
_check(lift_floor, "editor_lift_to_floor → Strg+Bild↑")
|
_check(lift_floor, "editor_lift_to_floor → Strg+Bild↑")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------- Karte 3/3 (t_11b5c25b) ----
|
||||||
|
|
||||||
|
## Karte 3/3: Datei & Undo, Werkzeugwechsel (F-Tasten), Hilfe + Test als
|
||||||
|
## physische KEY_*-Actions — gleiche Konvention wie Karte 1/2.
|
||||||
|
func _test_inputmap_actions_3() -> void:
|
||||||
|
print("Testgruppe: InputMap-Actions Karte 3/3 (Datei, F-Tasten, Hilfe)")
|
||||||
|
var mod_combos := {
|
||||||
|
"editor_undo": [KEY_Z, false, true], # Strg+Z
|
||||||
|
"editor_redo_y": [KEY_Y, false, true], # Strg+Y
|
||||||
|
"editor_redo_shift_z": [KEY_Z, true, true], # Strg+Umschalt+Z (2. Redo-Weg)
|
||||||
|
"editor_save": [KEY_S, false, true], # Strg+S
|
||||||
|
"editor_save_as": [KEY_S, true, true], # Strg+Umschalt+S
|
||||||
|
"editor_open_map": [KEY_O, false, true], # Strg+O
|
||||||
|
"editor_new_map": [KEY_N, false, true], # Strg+N
|
||||||
|
}
|
||||||
|
for action in mod_combos:
|
||||||
|
var combo: Array = mod_combos[action]
|
||||||
|
if not InputMap.has_action(action):
|
||||||
|
_check(false, "Action existiert: %s" % action)
|
||||||
|
continue
|
||||||
|
var ok := false
|
||||||
|
for ev in InputMap.action_get_events(action):
|
||||||
|
var kev := ev as InputEventKey
|
||||||
|
if ev is InputEventKey and kev.physical_keycode == int(combo[0]) \
|
||||||
|
and kev.shift_pressed == bool(combo[1]) and kev.ctrl_pressed == bool(combo[2]):
|
||||||
|
ok = true
|
||||||
|
_check(ok, "%s → korrekte Tastenkombi" % action)
|
||||||
|
var f_keys := {
|
||||||
|
"editor_tool_terrain": KEY_F1,
|
||||||
|
"editor_tool_objects": KEY_F2,
|
||||||
|
"editor_tool_bauen": KEY_F3,
|
||||||
|
"editor_tool_spawns": KEY_F4,
|
||||||
|
"editor_test_map": KEY_F5,
|
||||||
|
"editor_help_overlay": KEY_H,
|
||||||
|
}
|
||||||
|
for action in f_keys:
|
||||||
|
if not InputMap.has_action(action):
|
||||||
|
_check(false, "Action existiert: %s" % action)
|
||||||
|
continue
|
||||||
|
var ok := false
|
||||||
|
for ev in InputMap.action_get_events(action):
|
||||||
|
var kev := ev as InputEventKey
|
||||||
|
if ev is InputEventKey and kev.physical_keycode == int(f_keys[action]):
|
||||||
|
ok = true
|
||||||
|
_check(ok, "%s → physische Taste %s" % [action, OS.get_keycode_string(int(f_keys[action]))])
|
||||||
|
|
||||||
|
|
||||||
|
## Karte 3/3: Pure Policy — F-Tasten-Gate (Textfeld/Dialog) + Dirty-Confirm.
|
||||||
|
func _test_policy_3() -> void:
|
||||||
|
print("Testgruppe: ShortcutPolicy Karte 3/3 (F-Tasten-Gate, Dirty-Confirm)")
|
||||||
|
_check(ShortcutPolicy.tool_keys_allowed(false, false), "F-Tasten: frei ohne Textfeld/Dialog")
|
||||||
|
_check(not ShortcutPolicy.tool_keys_allowed(true, false), "F-Tasten: GESPERRT bei fokussiertem Textfeld")
|
||||||
|
_check(not ShortcutPolicy.tool_keys_allowed(false, true), "F-Tasten: GESPERRT bei offenem Dialog")
|
||||||
|
_check(not ShortcutPolicy.tool_keys_allowed(true, true), "F-Tasten: GESPERRT bei beidem")
|
||||||
|
_check(ShortcutPolicy.needs_dirty_confirm(true), "Dirty: Neu/Öffnen NACHFRAGEN (Dialog)")
|
||||||
|
_check(not ShortcutPolicy.needs_dirty_confirm(false), "Nicht-dirty: Neu/Öffnen direkt ausführen")
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ FlyMath --------
|
# ------------------------------------------------------------ FlyMath --------
|
||||||
|
|
||||||
func _test_fly_math() -> void:
|
func _test_fly_math() -> void:
|
||||||
|
|
|
||||||
18
tools/parse_check_shortcuts3.sh
Executable file
18
tools/parse_check_shortcuts3.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Parse-Check der Shortcuts-3-Karte (t_11b5c25b): alle geänderten/neuen Scripts.
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
GODOT=/opt/data/home/bin/godot4
|
||||||
|
fail=0
|
||||||
|
for f in src/ui/main.gd src/ui/help_text.gd src/editor/shortcut_keys.gd \
|
||||||
|
src/editor/shortcut_policy.gd \
|
||||||
|
tests/test_shortcuts.gd tests/test_gui_smoke.gd; do
|
||||||
|
out=$("$GODOT" --headless --path . --check-only --script "res://$f" 2>&1 | grep -iv "godot engine\|^$" || true)
|
||||||
|
if [ -n "$out" ]; then
|
||||||
|
echo "FAIL $f:"
|
||||||
|
echo "$out"
|
||||||
|
fail=1
|
||||||
|
else
|
||||||
|
echo "OK $f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
exit $fail
|
||||||
Loading…
Reference in a new issue