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
|
||||
|
||||
Deutsche Tastenbelegung des ScopedVR Map-Editors. **Diese Datei ist die Source
|
||||
of truth** für alle Shortcut-Karten (Karte 1/3: t_c46d31c2 — Navigation,
|
||||
Auswahl, Löschen; Karte 2/3: t_55257792 — Objektmanipulation + Terrain;
|
||||
Karte 3/3 ergänzt hier).
|
||||
of truth** für alle Shortcut-Karten — FINALE Version nach allen drei Karten:
|
||||
Karte 1/3 (t_c46d31c2): Navigation, Auswahl, Löschen · Karte 2/3 (t_55257792):
|
||||
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):
|
||||
|
||||
|
|
@ -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 |
|
||||
| **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+Z / Strg+Y | Rückgängig / Wiederherstellen |
|
||||
| Strg+S | Karte speichern |
|
||||
| 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
|
||||
|
||||
| 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
|
||||
gehalten) bleiben E/Q die Kamera.
|
||||
|
||||
## Werkzeuge
|
||||
## Werkzeuge (Karte 3/3: F-Tasten wechseln das Werkzeug)
|
||||
|
||||
| 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 (beim Greifen) | Greifen abbrechen, alte Positionen zurück |
|
||||
| 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_smooth": [KEY_3],
|
||||
"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],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,4 +28,19 @@ static func esc_action(tool_mode: String, placing: bool, has_selection: bool,
|
|||
return "cancel_placement"
|
||||
if has_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
|
||||
179
src/ui/main.gd
179
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")
|
||||
# Shortcuts-Mathe Karte 2/3 (t_55257792): Pfeil-Raster + Pinsel-Presets.
|
||||
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 doc: Dictionary = {} # das Map-Dictionary (1:1 svmap-Format)
|
||||
|
|
@ -116,6 +118,14 @@ var check_text: Label
|
|||
# Karte testen (Karte t_b84af40f)
|
||||
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
|
||||
var new_dialog: AcceptDialog
|
||||
var open_dialog: FileDialog
|
||||
|
|
@ -133,6 +143,7 @@ func _ready() -> void:
|
|||
ShortcutKeys.register()
|
||||
manifest = _load_manifest()
|
||||
_build_ui()
|
||||
_update_tool_label()
|
||||
_start_with_new_map()
|
||||
_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_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.modulate = Color(1, 1, 1, 0.75)
|
||||
status_h.add_child(stats_objects)
|
||||
|
|
@ -316,6 +333,52 @@ func _build_ui() -> void:
|
|||
check_dialog.add_child(check_text)
|
||||
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:
|
||||
var panel := PanelContainer.new()
|
||||
|
|
@ -888,10 +951,10 @@ func _start_with_new_map() -> void:
|
|||
|
||||
func _on_menu_action(id: int) -> void:
|
||||
match id:
|
||||
0: # Neue Map
|
||||
new_dialog.popup_centered()
|
||||
1: # Öffnen
|
||||
open_dialog.popup_centered()
|
||||
0: # Neue Map — bei ungespeicherten Änderungen erst NACHFRAGEN (Karte 3/3)
|
||||
_request_lossy(func() -> void: new_dialog.popup_centered())
|
||||
1: # Öffnen — ditto Dirty-Schutz
|
||||
_request_lossy(func() -> void: open_dialog.popup_centered())
|
||||
2: # Speichern
|
||||
_save()
|
||||
3: # Speichern unter
|
||||
|
|
@ -1022,6 +1085,66 @@ func _show_errors(title: String, errors: Array) -> void:
|
|||
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 ----
|
||||
|
||||
func _do_undo() -> void:
|
||||
|
|
@ -1090,7 +1213,44 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
KEY_S:
|
||||
if ctrl:
|
||||
_save()
|
||||
if event.shift_pressed:
|
||||
# Strg+Umschalt+S (Karte 3/3): Speichern unter …
|
||||
save_dialog.popup_centered()
|
||||
else:
|
||||
_save()
|
||||
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:
|
||||
if ctrl:
|
||||
|
|
@ -1132,6 +1292,12 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
|||
_focus_selection()
|
||||
get_viewport().set_input_as_handled()
|
||||
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):
|
||||
# Werkzeug verlassen → Greifen zurückrollen → Ghost-Preview
|
||||
# abbrechen → Auswahl aufheben (Greifen: Karte t_55257792).
|
||||
|
|
@ -1326,6 +1492,7 @@ func _set_tool_mode(mode: String) -> void:
|
|||
_sync_terrain_button()
|
||||
_sync_spawn_button()
|
||||
_sync_build_button()
|
||||
_update_tool_label()
|
||||
return
|
||||
if mode == "terrain":
|
||||
_leave_spawn_mode()
|
||||
|
|
@ -1344,6 +1511,7 @@ func _set_tool_mode(mode: String) -> void:
|
|||
_leave_spawn_mode()
|
||||
_leave_build_mode()
|
||||
_enter_objects_mode()
|
||||
_update_tool_label()
|
||||
|
||||
|
||||
func _enter_terrain_mode() -> void:
|
||||
|
|
@ -1570,6 +1738,7 @@ func _recheck_terrain_mode() -> void:
|
|||
tool_mode = "objects"
|
||||
terrain_panel.visible = false
|
||||
_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"
|
||||
_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_shortcut_undo()
|
||||
_test_terrain_hotkeys()
|
||||
_test_shortcuts_karte3()
|
||||
|
||||
_finish()
|
||||
|
||||
|
|
@ -101,12 +102,13 @@ func _test_terrain_presets() -> void:
|
|||
"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()
|
||||
ev.keycode = keycode
|
||||
ev.physical_keycode = keycode
|
||||
ev.pressed = true
|
||||
ev.ctrl_pressed = ctrl
|
||||
ev.shift_pressed = shift
|
||||
return ev
|
||||
|
||||
|
||||
|
|
@ -205,3 +207,112 @@ func _test_terrain_hotkeys() -> void:
|
|||
"Ziffern/[ im Objekt-Modus: Pinsel bleibt unangetastet")
|
||||
_check(int(main.terrain_tool.get("brush_mode")) == TerrainField.Mode.RAISE,
|
||||
"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:
|
||||
ShortcutKeys.register()
|
||||
_test_inputmap_actions()
|
||||
_test_inputmap_actions_3()
|
||||
_test_policy_3()
|
||||
_test_fly_math()
|
||||
_test_esc_policy()
|
||||
_test_esc_grab_policy()
|
||||
|
|
@ -194,6 +196,64 @@ func _test_inputmap_actions() -> void:
|
|||
_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 --------
|
||||
|
||||
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