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 (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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
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