Werkzeuge hängen nicht mehr an der Maus: PointerEvent, MousePointer, PointerRouter; Test Maus gegen Zeiger

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
slyphax 2026-11-01 19:26:17 +01:00
parent 949d73dfdd
commit 295f79011f
12 changed files with 420 additions and 195 deletions

2
core

@ -1 +1 @@
Subproject commit 92ee3fbdd1102d24bd34ad9302a494973a525fc3
Subproject commit 3ee60fb9297a3a850f0a504a7032f44dc8ad2dea

View file

@ -1401,111 +1401,106 @@ func mirror_selected(axis: String) -> void:
func _unhandled_input(event: InputEvent) -> void:
# Strg-Zwang (Karte t_55257792) aus JEDEM durchgelassenen Event verfolgen —
# Maus-Events tragen den Modifier, Tasten-Events ebenso (nicht konsumieren!).
if event is InputEventMouseMotion:
_ctrl_held = (event as InputEventMouseMotion).ctrl_pressed
elif event is InputEventMouseButton:
_ctrl_held = (event as InputEventMouseButton).ctrl_pressed
elif event is InputEventKey:
if event is InputEventKey:
_ctrl_held = (event as InputEventKey).ctrl_pressed
var p := MousePointer.from_input(event)
if p != null and handle_pointer(p):
get_viewport().set_input_as_handled()
## Rechte Maustaste kommt hier nie an (Karte t_2470de89): sie gehört mit
## absolutem Vorrang dem Freelook-Flug (orbit_camera.gd). Beim Platzieren
## bleibt das Objekt IN der Hand, Abwurf nur über Esc.
func handle_pointer(p: PointerEvent) -> bool:
if p.kind != PointerEvent.Kind.CANCEL:
_ctrl_held = p.ctrl
var mode: String = main.get("tool_mode")
if mode == "terrain" or mode == "spawns":
return # Terrain-Pinsel / Spawn-Zonen haben das Zepter (eigene Werkzeuge)
if event is InputEventMouseMotion:
var mm := event as InputEventMouseMotion
if placing:
_ghost_update(mm.position)
if _grabbing:
var gh: Variant = _ground_point(mm.position)
if gh != null:
grab_move(gh as Vector3)
return
if _rubber_pending or _rubber_active:
var dist := Vector2(mm.position.x - _press_pos.x, mm.position.y - _press_pos.y).length()
if dist > DRAG_THRESHOLD_PX:
_rubber_active = true
_rubber_pending = false
if _rubber_active:
main.call("_rubberband_update", _rubber_start, mm.position)
return
if _dragging and not placing and not _drag_members.is_empty():
_drag_members_move(mm.position)
elif _dragging and not placing and selected_index >= 0:
# Einzel-Greifen (alter Pfad, ohne Gruppe/Mehrfachauswahl)
var p: Variant = _ground_point(mm.position)
if p == null:
return
var new_pos: Vector3 = _apply_grid((p as Vector3) + _grab_offset)
var half := _terrain_half()
new_pos.x = clampf(new_pos.x, -half.x, half.x)
new_pos.z = clampf(new_pos.z, -half.y, half.y)
# Höhe: der Terrain-Abstand bleibt beim Ziehen erhalten (angehoben
# schwebt weiter oben) — kein harter Terrain-Clamp mehr, t_7567a06f.
new_pos.y = SvmapDocument.terrain_height_at(main.doc, new_pos.x, new_pos.z) + _drag_lift
var dist := Vector2(mm.position.x - _press_pos.x, mm.position.y - _press_pos.y).length()
if dist > DRAG_THRESHOLD_PX:
_drag_moved = true
if _drag_moved:
var marker: Node3D = view_builder.object_markers[selected_index]
if marker != null and is_instance_valid(marker):
marker.position = new_pos
return
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
return false # Terrain-Pinsel / Spawn-Zonen haben das Zepter (eigene Werkzeuge)
match p.kind:
PointerEvent.Kind.MOVE:
_on_pointer_move(p.position)
PointerEvent.Kind.PRESS:
if _grabbing:
grab_confirm()
get_viewport().set_input_as_handled()
return
_on_left_press(mb)
get_viewport().set_input_as_handled()
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
_on_left_release(mb)
# Rechte Maustaste: absichtlich NICHT behandelt (Karte t_2470de89) —
# sie gehört mit absolutem Vorrang dem Freelook-Flug (orbit_camera.gd).
# Beim Platzieren bleibt das Objekt IN der Hand, Abwurf nur über Esc.
elif mb.button_index == MOUSE_BUTTON_WHEEL_UP and mb.pressed:
return true
_on_left_press(p)
return true
PointerEvent.Kind.RELEASE:
_on_left_release(p)
PointerEvent.Kind.SCROLL:
if _camera_flying():
return # Mausrad = Flug-Tempo (orbit_camera), kein Objekt-Drehen
return false # Mausrad = Flug-Tempo (orbit_camera), kein Objekt-Drehen
if placing:
place_rot_deg = fmod(place_rot_deg + (90.0 if BauKatalog.ist_bau(place_oid) else ROT_STEP_DEG) + 3600.0, 360.0)
place_rot_deg = fmod(place_rot_deg + p.scroll * (90.0 if BauKatalog.ist_bau(place_oid) else ROT_STEP_DEG) + 3600.0, 360.0)
_ghost_rotation_anwenden()
get_viewport().set_input_as_handled()
elif selected_index >= 0 and not _grabbing:
rotate_selected(ROT_STEP_DEG)
get_viewport().set_input_as_handled()
elif mb.button_index == MOUSE_BUTTON_WHEEL_DOWN and mb.pressed:
if _camera_flying():
return # Mausrad = Flug-Tempo (orbit_camera), kein Objekt-Drehen
if placing:
place_rot_deg = fmod(place_rot_deg - (90.0 if BauKatalog.ist_bau(place_oid) else ROT_STEP_DEG) + 3600.0, 360.0)
_ghost_rotation_anwenden()
get_viewport().set_input_as_handled()
elif selected_index >= 0 and not _grabbing:
rotate_selected(-ROT_STEP_DEG)
get_viewport().set_input_as_handled()
return true
if selected_index >= 0 and not _grabbing:
rotate_selected(p.scroll * ROT_STEP_DEG)
return true
return false
func _on_left_press(mb: InputEventMouseButton) -> void:
func _on_pointer_move(pos: Vector2) -> void:
if placing:
_place_object(mb.position, mb.shift_pressed) # U5: Shift = Serienmodus
_ghost_update(pos)
if _grabbing:
var gh: Variant = _ground_point(pos)
if gh != null:
grab_move(gh as Vector3)
return
_press_pos = mb.position
var hit := _pick_object(mb.position)
if _rubber_pending or _rubber_active:
var dist := Vector2(pos.x - _press_pos.x, pos.y - _press_pos.y).length()
if dist > DRAG_THRESHOLD_PX:
_rubber_active = true
_rubber_pending = false
if _rubber_active:
main.call("_rubberband_update", _rubber_start, pos)
return
if _dragging and not placing and not _drag_members.is_empty():
_drag_members_move(pos)
elif _dragging and not placing and selected_index >= 0:
# Einzel-Greifen (alter Pfad, ohne Gruppe/Mehrfachauswahl)
var p: Variant = _ground_point(pos)
if p == null:
return
var new_pos: Vector3 = _apply_grid((p as Vector3) + _grab_offset)
var half := _terrain_half()
new_pos.x = clampf(new_pos.x, -half.x, half.x)
new_pos.z = clampf(new_pos.z, -half.y, half.y)
# Höhe: der Terrain-Abstand bleibt beim Ziehen erhalten (angehoben
# schwebt weiter oben) — kein harter Terrain-Clamp mehr, t_7567a06f.
new_pos.y = SvmapDocument.terrain_height_at(main.doc, new_pos.x, new_pos.z) + _drag_lift
var dist := Vector2(pos.x - _press_pos.x, pos.y - _press_pos.y).length()
if dist > DRAG_THRESHOLD_PX:
_drag_moved = true
if _drag_moved:
var marker: Node3D = view_builder.object_markers[selected_index]
if marker != null and is_instance_valid(marker):
marker.position = new_pos
func _on_left_press(p: PointerEvent) -> void:
if placing:
_place_object(p.position, p.shift) # U5: Shift = Serienmodus
return
_press_pos = p.position
var hit := _pick_object(p.position)
if hit >= 0:
if mb.shift_pressed or mb.is_command_or_control_pressed():
if p.shift or p.command:
# Umschalt+Klick / Strg+Klick (Karte t_c46d31c2): zur Auswahl
# hinzufügen/entfernen — kein Ziehen.
select_toggle(hit)
return
if not selected_indices.has(hit):
select_object(hit)
_begin_drag(hit, mb.position)
_begin_drag(hit, p.position)
else:
# Leere Stelle: Rahmen-Ausziehen vorbereiten; loser Klick = Auswahl weg.
_rubber_pending = true
_rubber_active = false
_rubber_start = mb.position
_rubber_shift = mb.shift_pressed
_rubber_start = p.position
_rubber_shift = p.shift
## Zieh-Menge festlegen: Mehrfachauswahl oder komplette Gruppe ziehen GEMEINSAM.
@ -1606,14 +1601,14 @@ func _drag_members_move(viewport_pos: Vector2) -> void:
_sym_drag_sync_live()
func _on_left_release(mb: InputEventMouseButton) -> void:
func _on_left_release(p: PointerEvent) -> void:
if _rubber_pending or _rubber_active:
var was_active := _rubber_active
_rubber_pending = false
_rubber_active = false
main.call("_rubberband_clear")
if was_active:
_finish_rubberband(mb.position)
_finish_rubberband(p.position)
elif not _rubber_shift:
# Loser Klick auf leere Fläche: Auswahl aufheben (Klick statt Auswendiglernen).
if not selected_indices.is_empty():

View file

@ -0,0 +1,39 @@
class_name MousePointer
extends RefCounted
## Übersetzt Maus und Esc-Taste in PointerEvent. Die rechte Maustaste fehlt
## absichtlich: sie gehört dem Freelook-Flug (orbit_camera.gd).
static func from_input(event: InputEvent) -> PointerEvent:
if event is InputEventMouseMotion:
var mm := event as InputEventMouseMotion
var p := PointerEvent.make(PointerEvent.Kind.MOVE, mm.position)
p.shift = mm.shift_pressed
p.ctrl = mm.ctrl_pressed
p.command = mm.is_command_or_control_pressed()
p.pressure = mm.pressure
return p
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
var kind := PointerEvent.Kind.MOVE
var scroll := 0
match mb.button_index:
MOUSE_BUTTON_LEFT:
kind = PointerEvent.Kind.PRESS if mb.pressed else PointerEvent.Kind.RELEASE
MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN:
if not mb.pressed:
return null
kind = PointerEvent.Kind.SCROLL
scroll = 1 if mb.button_index == MOUSE_BUTTON_WHEEL_UP else -1
_:
return null
var p := PointerEvent.make(kind, mb.position)
p.shift = mb.shift_pressed
p.ctrl = mb.ctrl_pressed
p.command = mb.is_command_or_control_pressed()
p.scroll = scroll
return p
if event is InputEventKey and (event as InputEventKey).pressed \
and (event as InputEventKey).keycode == KEY_ESCAPE:
return PointerEvent.make(PointerEvent.Kind.CANCEL)
return null

View file

@ -0,0 +1 @@
uid://1nyr0qwwdvnv

View file

@ -83,16 +83,20 @@ func refresh_from_doc() -> void:
# --------------------------------------------------------------- Eingaben ---
func _unhandled_input(event: InputEvent) -> void:
var p := MousePointer.from_input(event)
if p != null and handle_pointer(p):
get_viewport().set_input_as_handled()
func handle_pointer(p: PointerEvent) -> bool:
if not active:
return
if event is InputEventMouseMotion:
_update_cursor((event as InputEventMouseMotion).position)
return
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
get_viewport().set_input_as_handled()
_click(mb.position)
return false
if p.kind == PointerEvent.Kind.MOVE:
_update_cursor(p.position)
elif p.kind == PointerEvent.Kind.PRESS:
_click(p.position)
return true
return false
func _click(viewport_pos: Vector2) -> void:

View file

@ -0,0 +1,13 @@
class_name PointerRouter
extends RefCounted
## Gibt eine Zeige-Eingabe in derselben Reihenfolge an die Werkzeuge wie Godot
## die Maus: tiefer und später im Baum zuerst, das erste Werkzeug nimmt sie.
## Für Eingaben, die nicht über den Viewport kommen, also den VR-Controller.
static func dispatch(main: Node, p: PointerEvent) -> bool:
for key in ["test_start_tool", "spawn_tool", "terrain_tool", "gizmo", "interaction"]:
var werkzeug: Variant = main.get(key)
if werkzeug != null and bool(werkzeug.call("handle_pointer", p)):
return true
return false

View file

@ -0,0 +1 @@
uid://bem4edb2s282

View file

@ -158,38 +158,42 @@ func set_radius(m: float) -> void:
# --------------------------------------------------------------- Eingaben ---
func _unhandled_input(event: InputEvent) -> void:
var p := MousePointer.from_input(event)
if p != null and handle_pointer(p):
get_viewport().set_input_as_handled()
## Rechte Maustaste kommt hier nie an (Karte t_2470de89): sie gehört mit
## absolutem Vorrang dem Freelook-Flug. Deselect/Werkzeug beenden läuft über
## ESC (main.gd), die Auswahl bleibt im Flug erhalten.
func handle_pointer(p: PointerEvent) -> bool:
if not active:
return
if event is InputEventMouseMotion:
var mm := event as InputEventMouseMotion
_update_cursor(mm.position)
if not _arrow_grab.is_empty():
_drag_arrow(mm.position)
return
if _dragging and selected_team >= 0:
var hit: Variant = _ray_hit(mm.position)
if hit == null:
return
var pos: Vector3 = _clamped(hit as Vector3) + _grab_offset
var half := _terrain_half()
pos.x = clampf(pos.x, -half.x + zone_radius, half.x - zone_radius)
pos.z = clampf(pos.z, -half.y + zone_radius, half.y - zone_radius)
pos.y = SvmapDocument.terrain_height_at(main.doc, pos.x, pos.z)
_drag_moved = true
var marker: Node3D = _marker_of(selected_team, selected_id)
if marker != null and is_instance_valid(marker):
marker.position = pos
return
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
_on_left_press(mb.position)
get_viewport().set_input_as_handled()
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
return false
match p.kind:
PointerEvent.Kind.MOVE:
_update_cursor(p.position)
if not _arrow_grab.is_empty():
_drag_arrow(p.position)
return false
if _dragging and selected_team >= 0:
var hit: Variant = _ray_hit(p.position)
if hit == null:
return false
var pos: Vector3 = _clamped(hit as Vector3) + _grab_offset
var half := _terrain_half()
pos.x = clampf(pos.x, -half.x + zone_radius, half.x - zone_radius)
pos.z = clampf(pos.z, -half.y + zone_radius, half.y - zone_radius)
pos.y = SvmapDocument.terrain_height_at(main.doc, pos.x, pos.z)
_drag_moved = true
var marker: Node3D = _marker_of(selected_team, selected_id)
if marker != null and is_instance_valid(marker):
marker.position = pos
PointerEvent.Kind.PRESS:
_on_left_press(p.position)
return true
PointerEvent.Kind.RELEASE:
_on_left_release()
# Rechte Maustaste: absichtlich NICHT behandelt (Karte t_2470de89) —
# sie gehört mit absolutem Vorrang dem Freelook-Flug. Deselect/Werkzeug
# beenden läuft über ESC (main.gd), die Auswahl bleibt im Flug erhalten.
return false
func _update_cursor(viewport_pos: Vector2) -> void:

View file

@ -220,38 +220,45 @@ func set_paint_material(id: int) -> void:
# --------------------------------------------------------------- Eingaben ---
func _unhandled_input(event: InputEvent) -> void:
var p := MousePointer.from_input(event)
if p == null:
return
# Stift: pressure > 0; Maus meldet 0 (oder 1) → bleibt voll.
if p.kind == PointerEvent.Kind.MOVE:
p.pressure = TerrainField.pressure_of(p.pressure, _trigger_axis())
if handle_pointer(p):
get_viewport().set_input_as_handled()
## Rechte Maustaste kommt hier nie an: sie gehört mit absolutem Vorrang dem
## Freelook-Flug (orbit_camera.gd, Karte t_2470de89), in jedem Werkzeugzustand.
func handle_pointer(p: PointerEvent) -> bool:
if not active:
return
if event is InputEventMouseMotion:
var mm := event as InputEventMouseMotion
_update_cursor(mm.position)
# Stift: pressure > 0; Maus meldet 0 (oder 1) → bleibt voll.
pressure = TerrainField.pressure_of(mm.pressure, _trigger_axis())
return
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
if mb.button_index == MOUSE_BUTTON_LEFT:
if mb.pressed:
# Karte t_2470de89: Strg = Invert-Modifier. Heben↔Senken
# tauschen (Blender/Photoshop-Konvention), im Malen-Modus
# malt Strg+Klick Gras zurück (alter Radierer-Weg).
var mode := brush_mode
var mat := -1
if mb.ctrl_pressed:
match brush_mode:
TerrainField.Mode.RAISE:
mode = TerrainField.Mode.LOWER
TerrainField.Mode.LOWER:
mode = TerrainField.Mode.RAISE
TerrainField.Mode.PAINT:
mat = 0 # Gras zurückmalen
_begin_stroke(mode, mat)
else:
_end_stroke()
get_viewport().set_input_as_handled()
# Rechte Maustaste: absichtlich NICHT behandelt — sie gehört mit
# absolutem Vorrang dem Freelook-Flug (orbit_camera.gd, Karte
# t_2470de89), in jedem Werkzeugzustand.
return false
match p.kind:
PointerEvent.Kind.MOVE:
_update_cursor(p.position)
pressure = p.pressure
PointerEvent.Kind.PRESS:
# Karte t_2470de89: Strg = Invert-Modifier. Heben↔Senken
# tauschen (Blender/Photoshop-Konvention), im Malen-Modus
# malt Strg+Klick Gras zurück (alter Radierer-Weg).
var mode := brush_mode
var mat := -1
if p.ctrl:
match brush_mode:
TerrainField.Mode.RAISE:
mode = TerrainField.Mode.LOWER
TerrainField.Mode.LOWER:
mode = TerrainField.Mode.RAISE
TerrainField.Mode.PAINT:
mat = 0 # Gras zurückmalen
_begin_stroke(mode, mat)
return true
PointerEvent.Kind.RELEASE:
_end_stroke()
return true
return false
## Gamepad-Trigger (rechts) des ersten Pads; 0 ohne Pad. Nur Eingabe-Lesen,

View file

@ -220,61 +220,65 @@ func _process(_delta: float) -> void:
func _unhandled_input(event: InputEvent) -> void:
var p := MousePointer.from_input(event)
if p != null and handle_pointer(p):
get_viewport().set_input_as_handled()
func handle_pointer(p: PointerEvent) -> bool:
if main == null or camera == null:
return
return false
var doc: Dictionary = main.get("doc")
if doc == null or doc.is_empty():
return
return false
var tool_mode: String = str(main.get("tool_mode"))
if tool_mode == "terrain" or tool_mode == "spawns":
return # Terrain-Pinsel / Spawn-Zonen haben das Zepter
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
return false # Terrain-Pinsel / Spawn-Zonen haben das Zepter
match p.kind:
PointerEvent.Kind.PRESS:
if _dragging:
return
return false
var inter = main.get("interaction")
if inter == null or bool(inter.get("placing")):
return
return false
if mode == "rotate":
var ring_axis := pick_ring(mb.position)
var ring_axis := pick_ring(p.position)
if ring_axis != "":
_begin_rotate(ring_axis, mb.position)
get_viewport().set_input_as_handled()
return
var axis := pick(mb.position)
_begin_rotate(ring_axis, p.position)
return true
return false
var axis := pick(p.position)
if axis != "":
_begin_drag(axis, mb.position)
get_viewport().set_input_as_handled()
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
_begin_drag(axis, p.position)
return true
PointerEvent.Kind.RELEASE:
if _rot_dragging:
_finish_rotate(true)
get_viewport().set_input_as_handled()
elif _dragging:
return true
if _dragging:
_finish_drag(true)
get_viewport().set_input_as_handled()
elif (mb.button_index == MOUSE_BUTTON_WHEEL_UP \
or mb.button_index == MOUSE_BUTTON_WHEEL_DOWN) and mb.pressed:
return true
PointerEvent.Kind.SCROLL:
if _rot_dragging:
get_viewport().set_input_as_handled() # Mausrad zoomt nicht mitten im Dreh-Zug
elif _dragging:
var dir := 1 if mb.button_index == MOUSE_BUTTON_WHEEL_UP else -1
cycle_step(dir)
get_viewport().set_input_as_handled()
elif event is InputEventMouseMotion and _rot_dragging:
_rotate_motion((event as InputEventMouseMotion).position)
get_viewport().set_input_as_handled()
elif event is InputEventMouseMotion and _dragging:
_drag_motion((event as InputEventMouseMotion).position)
get_viewport().set_input_as_handled()
elif event is InputEventKey and (event as InputEventKey).pressed \
and (event as InputEventKey).keycode == KEY_ESCAPE:
if _rot_dragging:
_finish_rotate(false)
get_viewport().set_input_as_handled()
elif _dragging:
_finish_drag(false)
get_viewport().set_input_as_handled()
return true # Mausrad zoomt nicht mitten im Dreh-Zug
if _dragging:
cycle_step(p.scroll)
return true
PointerEvent.Kind.MOVE:
if _rot_dragging:
_rotate_motion(p.position)
return true
if _dragging:
_drag_motion(p.position)
return true
PointerEvent.Kind.CANCEL:
if _rot_dragging:
_finish_rotate(false)
return true
if _dragging:
_finish_drag(false)
return true
return false
# ------------------------------------------------------------- Treffer -----

View file

@ -0,0 +1,156 @@
extends SceneTree
## Editor-Test (headless): Werkzeuge laufen ohne Maus. Dieselben Züge einmal als
## echte Maus-Ereignisse und einmal als PointerEvent müssen dieselbe Karte ergeben.
## Ausführen: godot --headless --path . --script res://tests/test_pointer_router.gd
const OID := "box_001"
var failures := 0
var checks := 0
func _initialize() -> void:
_run.call_deferred()
func _check(cond: bool, label: String) -> void:
checks += 1
if cond:
print(" ok: ", label)
else:
failures += 1
print(" FAIL: ", label)
func _run() -> void:
print("== Objekte: Maus gegen PointerEvent ==")
var maus := await _objekt_zuege(true)
var zeiger := await _objekt_zuege(false)
# Neue Karten spiegeln: jedes gesetzte Objekt bekommt einen Zwilling.
_check(int(zeiger["anzahl"]) == 4, "Zeiger: zwei Objekte mit Zwillingen gesetzt (%d)" % int(zeiger["anzahl"]))
_check(int(zeiger["verschoben"]) == 1, "Zeiger: Ziehen hat ein Objekt bewegt")
_check(int(zeiger["undo"]) == 3, "Zeiger: drei Undo-Schritte")
_check(str(maus["objekte"]) == str(zeiger["objekte"]), "Maus und Zeiger ergeben dieselben Objekte")
_check(int(maus["undo"]) == int(zeiger["undo"]), "Maus und Zeiger ergeben gleich viele Undo-Schritte")
print("== Terrain und Abbruch ==")
await _terrain_und_abbruch()
if failures == 0:
print("ALL OK: %d checks" % checks)
quit(0)
else:
print("FAILED: %d/%d checks" % [failures, checks])
quit(1)
func _neu() -> Control:
var main: Control = (load("res://scenes/main.tscn") as PackedScene).instantiate()
root.add_child(main)
await process_frame
await process_frame
main.camera.global_position = Vector3(0.0, 200.0, 0.0)
main.camera.look_at(Vector3.ZERO, Vector3.FORWARD)
main.call("_set_tool_mode", "objects")
await process_frame
return main
func _bild(main: Control, welt: Vector3) -> Vector2:
return main.camera.unproject_position(welt)
## Ein Zug: als Mausereignis durch den echten Viewport oder direkt über den Router.
func _zug(main: Control, ueber_maus: bool, kind: PointerEvent.Kind, pos: Vector2, shift := false) -> void:
if ueber_maus:
var ev: InputEvent
if kind == PointerEvent.Kind.MOVE:
var mm := InputEventMouseMotion.new()
mm.position = pos
ev = mm
else:
var mb := InputEventMouseButton.new()
mb.position = pos
mb.button_index = MOUSE_BUTTON_LEFT
mb.pressed = kind == PointerEvent.Kind.PRESS
mb.shift_pressed = shift
ev = mb
main.camera.get_viewport().push_input(ev)
else:
var p := PointerEvent.make(kind, pos)
p.shift = shift
PointerRouter.dispatch(main, p)
await process_frame
func _objekt_zuege(ueber_maus: bool) -> Dictionary:
var main := await _neu()
var davor: int = (main.doc.get("objects", []) as Array).size()
var a := Vector3(10.0, 0.0, 10.0)
var b := Vector3(-20.0, 0.0, 5.0)
var ziel := Vector3(30.0, 0.0, -15.0)
# Serienmodus: Umschalt setzt und behält das Objekt in der Hand.
main.interaction.start_placement(OID)
await _zug(main, ueber_maus, PointerEvent.Kind.MOVE, _bild(main, a))
await _zug(main, ueber_maus, PointerEvent.Kind.PRESS, _bild(main, a), true)
await _zug(main, ueber_maus, PointerEvent.Kind.RELEASE, _bild(main, a), true)
await _zug(main, ueber_maus, PointerEvent.Kind.MOVE, _bild(main, b))
await _zug(main, ueber_maus, PointerEvent.Kind.PRESS, _bild(main, b))
await _zug(main, ueber_maus, PointerEvent.Kind.RELEASE, _bild(main, b))
main.interaction.cancel_placement()
main.interaction.clear_selection()
await process_frame
var objekte: Array = main.doc.get("objects", [])
var vorher: Array = (objekte[0]["pos"] as Array).duplicate()
# Greifen, ziehen, loslassen. Die Mitte des Objekts statt des Gizmos treffen:
# ohne Auswahl ist das Gizmo unsichtbar.
var start := Vector3(float(vorher[0]), float(vorher[1]), float(vorher[2]))
await _zug(main, ueber_maus, PointerEvent.Kind.PRESS, _bild(main, start))
main.gizmo.visible = false
await _zug(main, ueber_maus, PointerEvent.Kind.MOVE, _bild(main, ziel))
await _zug(main, ueber_maus, PointerEvent.Kind.RELEASE, _bild(main, ziel))
var ergebnis := {
"anzahl": objekte.size() - davor,
"objekte": JSON.stringify(objekte),
"verschoben": 1 if str(objekte[0]["pos"]) != str(vorher) else 0,
"undo": (main.history.index + 1),
}
main.queue_free()
await process_frame
return ergebnis
func _terrain_und_abbruch() -> void:
var main := await _neu()
main.call("_set_tool_mode", "terrain")
await process_frame
var hoehen_vorher := str(main.doc["terrain"].get("heightmap_r16_b64", ""))
var undo_vorher: int = main.history.index + 1
var mitte := _bild(main, Vector3.ZERO)
_check(not PointerRouter.dispatch(main, PointerEvent.make(PointerEvent.Kind.MOVE, mitte)),
"Terrain: Bewegen wird nicht verschluckt")
_check(PointerRouter.dispatch(main, PointerEvent.make(PointerEvent.Kind.PRESS, mitte)),
"Terrain: Drücken nimmt der Pinsel")
for i in 6:
await process_frame
_check(PointerRouter.dispatch(main, PointerEvent.make(PointerEvent.Kind.RELEASE, mitte)),
"Terrain: Loslassen nimmt der Pinsel")
await process_frame
_check(str(main.doc["terrain"].get("heightmap_r16_b64", "")) != hoehen_vorher, "Terrain: Strich ändert die Höhen")
_check((main.history.index + 1) == undo_vorher + 1, "Terrain: ein Strich, ein Undo-Schritt")
main.call("_set_tool_mode", "objects")
await process_frame
_check(not PointerRouter.dispatch(main, PointerEvent.make(PointerEvent.Kind.CANCEL)),
"Esc ohne laufenden Zug bleibt frei fürs Hauptfenster")
var ohne := MousePointer.from_input(_rechts())
_check(ohne == null, "Rechte Maustaste bleibt beim Flug")
main.queue_free()
await process_frame
func _rechts() -> InputEvent:
var mb := InputEventMouseButton.new()
mb.button_index = MOUSE_BUTTON_RIGHT
mb.pressed = true
return mb

View file

@ -0,0 +1 @@
uid://dej077j3x2b1u