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