Drag&Drop aus der Bibliothek: Ablege-Fläche über der 3D-Ansicht (echter Maus-Drag getestet), Drag-Vorschau, Doku

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
slyphax 2026-10-25 22:50:37 +01:00
parent bcbd416960
commit 902181500a
4 changed files with 104 additions and 4 deletions

View file

@ -28,6 +28,7 @@ Ein Bauteil ist ein **ganz normales Bibliotheks-Objekt** (`bau_*`, Kategorie
| Dach & Giebel (klein 2 m) | Giebelwand, Satteldach, Pyramidendach |
| Dach & Giebel (mittel 4 m) | Giebelwand 4 m, Satteldach 4×4, Pyramidendach 4×4 (Spitze 2 m) |
| Dach & Giebel (groß 6 m) | Giebelwand 6 m, Satteldach 6×6, Pyramidendach 6×6 (Spitze 3 m) |
| Dach aus Einzelteilen (45°) | Dachschräge 2×2 m (2 m hoch), Dachecke außen (Grat), Dachecke innen (Kehle), Dachplateau, Dachstirn — massiv, rundum geschlossen; tiefe Kante/Ecke zeigt Richtung Vorschau, R dreht |
| Treppe | Treppe 4 × 2 m, 3 m hoch (15 Stufen) |
Fertige Pack-Objekte (Container, Hangar-Tore, Betonsperren) stehen NICHT im Bauen-Tab,
@ -47,6 +48,11 @@ sondern wie alle Objekte in der Objekt-Bibliothek unten.
Pfeiler zählen von der Unterkante einer Decke/eines Fundaments**, die Decke selbst
liegt auf der Wandoberkante: jedes Geschoss liegt auf Vielfachen von 3 m, mit oder
ohne Decke darunter stehen die oberen Wände gleich hoch.
* **Geschoss fest:** Bild↑/Bild↓ (oder Knöpfe im Tab) wählen ein festes Geschoss
(Basis = tiefstes Teil in der Nähe, je 3 m). Der Mausstrahl trifft dann die
Geschoss-Ebene statt Wand/Boden darunter — Decken und Böden lassen sich mitten im
großen Raum setzen. Ganz unten = Automatik.
* **Kein Z-Fighting:** Wand-Außenseite steht 1 cm vor der Plattenkante.
* **Ersetzen:** gleiche Art auf demselben Platz wird ersetzt (Tür in vorhandene
Wand). Dasselbe Teil nochmal wird mit Erklärung abgelehnt.

View file

@ -78,6 +78,7 @@ var toolbar_mod # src/ui/panels/toolbar.gd
var status_mod # src/ui/panels/status_bar.gd
var selection_mod # src/ui/panels/selection_panel.gd (nur Logik, kein Tab mehr)
var settings_mod # src/ui/panels/settings_panel.gd (im Einstellungen-Fenster)
var drop_overlay: Control # Ablege-Fläche über der 3D-Ansicht (nur während Drag aktiv)
var settings_dialog: AcceptDialog # Fenster „Einstellungen“ (Knopf in der Kopfleiste)
var sym_mod # src/ui/panels/symmetry_panel.gd (im Map-Tab)
var map_mod # src/ui/panels/map_panel.gd
@ -700,6 +701,18 @@ func _build_center_viewport() -> SubViewportContainer:
container.add_child(viewport_toolbar)
viewport_toolbar.setup(self)
# Ablege-Fläche für Bibliotheks-Kacheln: Godot ruft can_drop/drop NICHT auf einem
# SubViewportContainer auf (Test-Befund 25.10.: Ziehen startete, Loslassen setzte
# nichts — echter Maus-Drag, tests/probe_drag_echt.gd). Eine durchsichtige Fläche
# darüber nimmt den Drop an; sie ist nur WÄHREND eines Ziehens aktiv (sonst
# bleibt die Maus für Kamera/Auswahl frei).
drop_overlay = Control.new()
drop_overlay.name = "AblegeFlaeche"
drop_overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
drop_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
drop_overlay.set_drag_forwarding(Callable(), _viewport_can_drop, _viewport_drop)
container.add_child(drop_overlay)
# Bedien-Hinweis im Viewport (Overlay)
help_label = Label.new()
help_label.text = "Linksklick: auswählen · Rahmen: mehrere · R: drehen · Entf: löschen · H: Hilfe"
@ -890,6 +903,11 @@ func _quit_editor() -> void:
func _notification(what: int) -> void:
if drop_overlay != null and is_instance_valid(drop_overlay):
if what == NOTIFICATION_DRAG_BEGIN:
drop_overlay.mouse_filter = Control.MOUSE_FILTER_STOP
elif what == NOTIFICATION_DRAG_END:
drop_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
if what == NOTIFICATION_WM_CLOSE_REQUEST:
_quit_editor()

View file

@ -636,10 +636,7 @@ func _tile_drag_data(_at_position: Vector2, oid: String, btn: Button) -> Variant
lbl.text = LibraryBrowser.entry_label(oid, (main.manifest.get("objects", {}) as Dictionary).get(oid, {}))
box.add_child(lbl)
prev.modulate.a = 0.85
if btn.get_viewport().gui_is_dragging():
btn.set_drag_preview(prev)
else:
prev.free()
btn.set_drag_preview(prev) # gehört in den Drag-Start: gui_is_dragging() ist hier noch false
return {"svr_oid": oid}

79
tests/probe_drag_echt.gd Normal file
View file

@ -0,0 +1,79 @@
extends SceneTree
## Echter Maus-Drag im sichtbaren Fenster (nicht headless): Kachel drücken, über die
## Szene ziehen, loslassen → genau ein Objekt gesetzt. Ausführen:
## godot4 --rendering-driver opengl3 --path . --script res://tests/probe_drag_echt.gd
func _mouse(pos: Vector2, pressed: bool, motion := false) -> void:
var ev: InputEvent
if motion:
var mm := InputEventMouseMotion.new()
mm.position = pos
mm.global_position = pos
mm.relative = Vector2(4, 4)
mm.button_mask = MOUSE_BUTTON_MASK_LEFT
ev = mm
else:
var mb := InputEventMouseButton.new()
mb.button_index = MOUSE_BUTTON_LEFT
mb.position = pos
mb.global_position = pos
mb.pressed = pressed
mb.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0
ev = mb
root.push_input(ev)
func _init() -> void:
root.size = Vector2i(1600, 900)
var main: Control = (load("res://scenes/main.tscn") as PackedScene).instantiate()
root.add_child(main)
for i in 10:
await process_frame
main.sym_mode = SymmetryCore.Mode.OFF
var vc0: Control = main.interaction.camera.get_viewport().get_parent()
var n0: int = (main.doc["objects"] as Array).size()
var oid := "barrel_001"
main.assets_mod._on_library_expand_all()
for i in 5:
await process_frame
var btn: Button = main.library_buttons.get(oid)
print("window=", root.size, " btn=", btn)
btn.get_parent().get_parent()
# Kachel in Sicht scrollen
var p := btn.get_parent()
while p != null and not (p is ScrollContainer):
p = p.get_parent()
if p != null:
(p as ScrollContainer).ensure_control_visible(btn)
for i in 5:
await process_frame
var r := btn.get_global_rect()
print("Kachel rect=", r)
var start := r.get_center()
var vc: Control = main.interaction.camera.get_viewport().get_parent()
var vr := vc.get_global_rect()
print("Szene rect=", vr)
var ziel := vr.get_center()
_mouse(start, false, true)
await process_frame
_mouse(start, true)
await process_frame
var steps := 25
for i in steps + 1:
var pos := start.lerp(ziel, float(i) / steps)
_mouse(pos, false, true)
await process_frame
for j in 6:
_mouse(ziel + Vector2(j * 3, j * 2), false, true)
await process_frame
var hov := root.gui_get_hovered_control()
print("Hover-Control: ", hov, " pfad=", hov.get_path() if hov != null else "-")
print("DragData=", root.gui_get_drag_data())
print("während Drag: dragging=", root.gui_is_dragging(), " placing=", main.interaction.placing)
_mouse(ziel, false)
for i in 4:
await process_frame
var n1: int = (main.doc["objects"] as Array).size()
print("Objekte: %d → %d placing=%s" % [n0, n1, main.interaction.placing])
print("ERGEBNIS: ", "OK" if n1 == n0 + 1 else "FEHLER")
quit(0 if n1 == n0 + 1 else 1)