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:
parent
bcbd416960
commit
902181500a
4 changed files with 104 additions and 4 deletions
|
|
@ -28,6 +28,7 @@ Ein Bauteil ist ein **ganz normales Bibliotheks-Objekt** (`bau_*`, Kategorie
|
||||||
| Dach & Giebel (klein 2 m) | Giebelwand, Satteldach, Pyramidendach |
|
| 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 (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 & 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) |
|
| Treppe | Treppe 4 × 2 m, 3 m hoch (15 Stufen) |
|
||||||
|
|
||||||
Fertige Pack-Objekte (Container, Hangar-Tore, Betonsperren) stehen NICHT im Bauen-Tab,
|
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
|
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
|
liegt auf der Wandoberkante: jedes Geschoss liegt auf Vielfachen von 3 m, mit oder
|
||||||
ohne Decke darunter stehen die oberen Wände gleich hoch.
|
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
|
* **Ersetzen:** gleiche Art auf demselben Platz wird ersetzt (Tür in vorhandene
|
||||||
Wand). Dasselbe Teil nochmal wird mit Erklärung abgelehnt.
|
Wand). Dasselbe Teil nochmal wird mit Erklärung abgelehnt.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ var toolbar_mod # src/ui/panels/toolbar.gd
|
||||||
var status_mod # src/ui/panels/status_bar.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 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 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 settings_dialog: AcceptDialog # Fenster „Einstellungen“ (Knopf in der Kopfleiste)
|
||||||
var sym_mod # src/ui/panels/symmetry_panel.gd (im Map-Tab)
|
var sym_mod # src/ui/panels/symmetry_panel.gd (im Map-Tab)
|
||||||
var map_mod # src/ui/panels/map_panel.gd
|
var map_mod # src/ui/panels/map_panel.gd
|
||||||
|
|
@ -700,6 +701,18 @@ func _build_center_viewport() -> SubViewportContainer:
|
||||||
container.add_child(viewport_toolbar)
|
container.add_child(viewport_toolbar)
|
||||||
viewport_toolbar.setup(self)
|
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)
|
# Bedien-Hinweis im Viewport (Overlay)
|
||||||
help_label = Label.new()
|
help_label = Label.new()
|
||||||
help_label.text = "Linksklick: auswählen · Rahmen: mehrere · R: drehen · Entf: löschen · H: Hilfe"
|
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:
|
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:
|
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||||||
_quit_editor()
|
_quit_editor()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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, {}))
|
lbl.text = LibraryBrowser.entry_label(oid, (main.manifest.get("objects", {}) as Dictionary).get(oid, {}))
|
||||||
box.add_child(lbl)
|
box.add_child(lbl)
|
||||||
prev.modulate.a = 0.85
|
prev.modulate.a = 0.85
|
||||||
if btn.get_viewport().gui_is_dragging():
|
btn.set_drag_preview(prev) # gehört in den Drag-Start: gui_is_dragging() ist hier noch false
|
||||||
btn.set_drag_preview(prev)
|
|
||||||
else:
|
|
||||||
prev.free()
|
|
||||||
return {"svr_oid": oid}
|
return {"svr_oid": oid}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
79
tests/probe_drag_echt.gd
Normal file
79
tests/probe_drag_echt.gd
Normal 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)
|
||||||
Loading…
Reference in a new issue