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:
parent
949d73dfdd
commit
295f79011f
12 changed files with 420 additions and 195 deletions
2
core
2
core
|
|
@ -1 +1 @@
|
||||||
Subproject commit 92ee3fbdd1102d24bd34ad9302a494973a525fc3
|
Subproject commit 3ee60fb9297a3a850f0a504a7032f44dc8ad2dea
|
||||||
|
|
@ -1401,37 +1401,67 @@ func mirror_selected(axis: String) -> void:
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
# Strg-Zwang (Karte t_55257792) aus JEDEM durchgelassenen Event verfolgen —
|
# Strg-Zwang (Karte t_55257792) aus JEDEM durchgelassenen Event verfolgen —
|
||||||
# Maus-Events tragen den Modifier, Tasten-Events ebenso (nicht konsumieren!).
|
# Maus-Events tragen den Modifier, Tasten-Events ebenso (nicht konsumieren!).
|
||||||
if event is InputEventMouseMotion:
|
if event is InputEventKey:
|
||||||
_ctrl_held = (event as InputEventMouseMotion).ctrl_pressed
|
|
||||||
elif event is InputEventMouseButton:
|
|
||||||
_ctrl_held = (event as InputEventMouseButton).ctrl_pressed
|
|
||||||
elif event is InputEventKey:
|
|
||||||
_ctrl_held = (event as InputEventKey).ctrl_pressed
|
_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")
|
var mode: String = main.get("tool_mode")
|
||||||
if mode == "terrain" or mode == "spawns":
|
if mode == "terrain" or mode == "spawns":
|
||||||
return # Terrain-Pinsel / Spawn-Zonen haben das Zepter (eigene Werkzeuge)
|
return false # Terrain-Pinsel / Spawn-Zonen haben das Zepter (eigene Werkzeuge)
|
||||||
if event is InputEventMouseMotion:
|
match p.kind:
|
||||||
var mm := event as InputEventMouseMotion
|
PointerEvent.Kind.MOVE:
|
||||||
if placing:
|
_on_pointer_move(p.position)
|
||||||
_ghost_update(mm.position)
|
PointerEvent.Kind.PRESS:
|
||||||
if _grabbing:
|
if _grabbing:
|
||||||
var gh: Variant = _ground_point(mm.position)
|
grab_confirm()
|
||||||
|
return true
|
||||||
|
_on_left_press(p)
|
||||||
|
return true
|
||||||
|
PointerEvent.Kind.RELEASE:
|
||||||
|
_on_left_release(p)
|
||||||
|
PointerEvent.Kind.SCROLL:
|
||||||
|
if _camera_flying():
|
||||||
|
return false # Mausrad = Flug-Tempo (orbit_camera), kein Objekt-Drehen
|
||||||
|
if placing:
|
||||||
|
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()
|
||||||
|
return true
|
||||||
|
if selected_index >= 0 and not _grabbing:
|
||||||
|
rotate_selected(p.scroll * ROT_STEP_DEG)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _on_pointer_move(pos: Vector2) -> void:
|
||||||
|
if placing:
|
||||||
|
_ghost_update(pos)
|
||||||
|
if _grabbing:
|
||||||
|
var gh: Variant = _ground_point(pos)
|
||||||
if gh != null:
|
if gh != null:
|
||||||
grab_move(gh as Vector3)
|
grab_move(gh as Vector3)
|
||||||
return
|
return
|
||||||
if _rubber_pending or _rubber_active:
|
if _rubber_pending or _rubber_active:
|
||||||
var dist := Vector2(mm.position.x - _press_pos.x, mm.position.y - _press_pos.y).length()
|
var dist := Vector2(pos.x - _press_pos.x, pos.y - _press_pos.y).length()
|
||||||
if dist > DRAG_THRESHOLD_PX:
|
if dist > DRAG_THRESHOLD_PX:
|
||||||
_rubber_active = true
|
_rubber_active = true
|
||||||
_rubber_pending = false
|
_rubber_pending = false
|
||||||
if _rubber_active:
|
if _rubber_active:
|
||||||
main.call("_rubberband_update", _rubber_start, mm.position)
|
main.call("_rubberband_update", _rubber_start, pos)
|
||||||
return
|
return
|
||||||
if _dragging and not placing and not _drag_members.is_empty():
|
if _dragging and not placing and not _drag_members.is_empty():
|
||||||
_drag_members_move(mm.position)
|
_drag_members_move(pos)
|
||||||
elif _dragging and not placing and selected_index >= 0:
|
elif _dragging and not placing and selected_index >= 0:
|
||||||
# Einzel-Greifen (alter Pfad, ohne Gruppe/Mehrfachauswahl)
|
# Einzel-Greifen (alter Pfad, ohne Gruppe/Mehrfachauswahl)
|
||||||
var p: Variant = _ground_point(mm.position)
|
var p: Variant = _ground_point(pos)
|
||||||
if p == null:
|
if p == null:
|
||||||
return
|
return
|
||||||
var new_pos: Vector3 = _apply_grid((p as Vector3) + _grab_offset)
|
var new_pos: Vector3 = _apply_grid((p as Vector3) + _grab_offset)
|
||||||
|
|
@ -1441,71 +1471,36 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
# Höhe: der Terrain-Abstand bleibt beim Ziehen erhalten (angehoben
|
# Höhe: der Terrain-Abstand bleibt beim Ziehen erhalten (angehoben
|
||||||
# schwebt weiter oben) — kein harter Terrain-Clamp mehr, t_7567a06f.
|
# 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
|
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()
|
var dist := Vector2(pos.x - _press_pos.x, pos.y - _press_pos.y).length()
|
||||||
if dist > DRAG_THRESHOLD_PX:
|
if dist > DRAG_THRESHOLD_PX:
|
||||||
_drag_moved = true
|
_drag_moved = true
|
||||||
if _drag_moved:
|
if _drag_moved:
|
||||||
var marker: Node3D = view_builder.object_markers[selected_index]
|
var marker: Node3D = view_builder.object_markers[selected_index]
|
||||||
if marker != null and is_instance_valid(marker):
|
if marker != null and is_instance_valid(marker):
|
||||||
marker.position = new_pos
|
marker.position = new_pos
|
||||||
return
|
|
||||||
if event is InputEventMouseButton:
|
|
||||||
var mb := event as InputEventMouseButton
|
|
||||||
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
|
|
||||||
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:
|
|
||||||
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()
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_left_press(mb: InputEventMouseButton) -> void:
|
func _on_left_press(p: PointerEvent) -> void:
|
||||||
if placing:
|
if placing:
|
||||||
_place_object(mb.position, mb.shift_pressed) # U5: Shift = Serienmodus
|
_place_object(p.position, p.shift) # U5: Shift = Serienmodus
|
||||||
return
|
return
|
||||||
_press_pos = mb.position
|
_press_pos = p.position
|
||||||
var hit := _pick_object(mb.position)
|
var hit := _pick_object(p.position)
|
||||||
if hit >= 0:
|
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
|
# Umschalt+Klick / Strg+Klick (Karte t_c46d31c2): zur Auswahl
|
||||||
# hinzufügen/entfernen — kein Ziehen.
|
# hinzufügen/entfernen — kein Ziehen.
|
||||||
select_toggle(hit)
|
select_toggle(hit)
|
||||||
return
|
return
|
||||||
if not selected_indices.has(hit):
|
if not selected_indices.has(hit):
|
||||||
select_object(hit)
|
select_object(hit)
|
||||||
_begin_drag(hit, mb.position)
|
_begin_drag(hit, p.position)
|
||||||
else:
|
else:
|
||||||
# Leere Stelle: Rahmen-Ausziehen vorbereiten; loser Klick = Auswahl weg.
|
# Leere Stelle: Rahmen-Ausziehen vorbereiten; loser Klick = Auswahl weg.
|
||||||
_rubber_pending = true
|
_rubber_pending = true
|
||||||
_rubber_active = false
|
_rubber_active = false
|
||||||
_rubber_start = mb.position
|
_rubber_start = p.position
|
||||||
_rubber_shift = mb.shift_pressed
|
_rubber_shift = p.shift
|
||||||
|
|
||||||
|
|
||||||
## Zieh-Menge festlegen: Mehrfachauswahl oder komplette Gruppe ziehen GEMEINSAM.
|
## 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()
|
_sym_drag_sync_live()
|
||||||
|
|
||||||
|
|
||||||
func _on_left_release(mb: InputEventMouseButton) -> void:
|
func _on_left_release(p: PointerEvent) -> void:
|
||||||
if _rubber_pending or _rubber_active:
|
if _rubber_pending or _rubber_active:
|
||||||
var was_active := _rubber_active
|
var was_active := _rubber_active
|
||||||
_rubber_pending = false
|
_rubber_pending = false
|
||||||
_rubber_active = false
|
_rubber_active = false
|
||||||
main.call("_rubberband_clear")
|
main.call("_rubberband_clear")
|
||||||
if was_active:
|
if was_active:
|
||||||
_finish_rubberband(mb.position)
|
_finish_rubberband(p.position)
|
||||||
elif not _rubber_shift:
|
elif not _rubber_shift:
|
||||||
# Loser Klick auf leere Fläche: Auswahl aufheben (Klick statt Auswendiglernen).
|
# Loser Klick auf leere Fläche: Auswahl aufheben (Klick statt Auswendiglernen).
|
||||||
if not selected_indices.is_empty():
|
if not selected_indices.is_empty():
|
||||||
|
|
|
||||||
39
src/editor/mouse_pointer.gd
Normal file
39
src/editor/mouse_pointer.gd
Normal 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
|
||||||
1
src/editor/mouse_pointer.gd.uid
Normal file
1
src/editor/mouse_pointer.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://1nyr0qwwdvnv
|
||||||
|
|
@ -83,16 +83,20 @@ func refresh_from_doc() -> void:
|
||||||
# --------------------------------------------------------------- Eingaben ---
|
# --------------------------------------------------------------- Eingaben ---
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
if not active:
|
var p := MousePointer.from_input(event)
|
||||||
return
|
if p != null and handle_pointer(p):
|
||||||
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()
|
get_viewport().set_input_as_handled()
|
||||||
_click(mb.position)
|
|
||||||
|
|
||||||
|
func handle_pointer(p: PointerEvent) -> bool:
|
||||||
|
if not active:
|
||||||
|
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:
|
func _click(viewport_pos: Vector2) -> void:
|
||||||
|
|
|
||||||
13
src/editor/pointer_router.gd
Normal file
13
src/editor/pointer_router.gd
Normal 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
|
||||||
1
src/editor/pointer_router.gd.uid
Normal file
1
src/editor/pointer_router.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bem4edb2s282
|
||||||
|
|
@ -158,18 +158,27 @@ func set_radius(m: float) -> void:
|
||||||
# --------------------------------------------------------------- Eingaben ---
|
# --------------------------------------------------------------- Eingaben ---
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> 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()
|
||||||
|
|
||||||
|
|
||||||
|
## 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:
|
if not active:
|
||||||
return
|
return false
|
||||||
if event is InputEventMouseMotion:
|
match p.kind:
|
||||||
var mm := event as InputEventMouseMotion
|
PointerEvent.Kind.MOVE:
|
||||||
_update_cursor(mm.position)
|
_update_cursor(p.position)
|
||||||
if not _arrow_grab.is_empty():
|
if not _arrow_grab.is_empty():
|
||||||
_drag_arrow(mm.position)
|
_drag_arrow(p.position)
|
||||||
return
|
return false
|
||||||
if _dragging and selected_team >= 0:
|
if _dragging and selected_team >= 0:
|
||||||
var hit: Variant = _ray_hit(mm.position)
|
var hit: Variant = _ray_hit(p.position)
|
||||||
if hit == null:
|
if hit == null:
|
||||||
return
|
return false
|
||||||
var pos: Vector3 = _clamped(hit as Vector3) + _grab_offset
|
var pos: Vector3 = _clamped(hit as Vector3) + _grab_offset
|
||||||
var half := _terrain_half()
|
var half := _terrain_half()
|
||||||
pos.x = clampf(pos.x, -half.x + zone_radius, half.x - zone_radius)
|
pos.x = clampf(pos.x, -half.x + zone_radius, half.x - zone_radius)
|
||||||
|
|
@ -179,17 +188,12 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
var marker: Node3D = _marker_of(selected_team, selected_id)
|
var marker: Node3D = _marker_of(selected_team, selected_id)
|
||||||
if marker != null and is_instance_valid(marker):
|
if marker != null and is_instance_valid(marker):
|
||||||
marker.position = pos
|
marker.position = pos
|
||||||
return
|
PointerEvent.Kind.PRESS:
|
||||||
if event is InputEventMouseButton:
|
_on_left_press(p.position)
|
||||||
var mb := event as InputEventMouseButton
|
return true
|
||||||
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
|
PointerEvent.Kind.RELEASE:
|
||||||
_on_left_press(mb.position)
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
|
|
||||||
_on_left_release()
|
_on_left_release()
|
||||||
# Rechte Maustaste: absichtlich NICHT behandelt (Karte t_2470de89) —
|
return false
|
||||||
# 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 _update_cursor(viewport_pos: Vector2) -> void:
|
func _update_cursor(viewport_pos: Vector2) -> void:
|
||||||
|
|
|
||||||
|
|
@ -220,24 +220,32 @@ func set_paint_material(id: int) -> void:
|
||||||
# --------------------------------------------------------------- Eingaben ---
|
# --------------------------------------------------------------- Eingaben ---
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
if not active:
|
var p := MousePointer.from_input(event)
|
||||||
|
if p == null:
|
||||||
return
|
return
|
||||||
if event is InputEventMouseMotion:
|
|
||||||
var mm := event as InputEventMouseMotion
|
|
||||||
_update_cursor(mm.position)
|
|
||||||
# Stift: pressure > 0; Maus meldet 0 (oder 1) → bleibt voll.
|
# Stift: pressure > 0; Maus meldet 0 (oder 1) → bleibt voll.
|
||||||
pressure = TerrainField.pressure_of(mm.pressure, _trigger_axis())
|
if p.kind == PointerEvent.Kind.MOVE:
|
||||||
return
|
p.pressure = TerrainField.pressure_of(p.pressure, _trigger_axis())
|
||||||
if event is InputEventMouseButton:
|
if handle_pointer(p):
|
||||||
var mb := event as InputEventMouseButton
|
get_viewport().set_input_as_handled()
|
||||||
if mb.button_index == MOUSE_BUTTON_LEFT:
|
|
||||||
if mb.pressed:
|
|
||||||
|
## 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 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
|
# Karte t_2470de89: Strg = Invert-Modifier. Heben↔Senken
|
||||||
# tauschen (Blender/Photoshop-Konvention), im Malen-Modus
|
# tauschen (Blender/Photoshop-Konvention), im Malen-Modus
|
||||||
# malt Strg+Klick Gras zurück (alter Radierer-Weg).
|
# malt Strg+Klick Gras zurück (alter Radierer-Weg).
|
||||||
var mode := brush_mode
|
var mode := brush_mode
|
||||||
var mat := -1
|
var mat := -1
|
||||||
if mb.ctrl_pressed:
|
if p.ctrl:
|
||||||
match brush_mode:
|
match brush_mode:
|
||||||
TerrainField.Mode.RAISE:
|
TerrainField.Mode.RAISE:
|
||||||
mode = TerrainField.Mode.LOWER
|
mode = TerrainField.Mode.LOWER
|
||||||
|
|
@ -246,12 +254,11 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||||
TerrainField.Mode.PAINT:
|
TerrainField.Mode.PAINT:
|
||||||
mat = 0 # Gras zurückmalen
|
mat = 0 # Gras zurückmalen
|
||||||
_begin_stroke(mode, mat)
|
_begin_stroke(mode, mat)
|
||||||
else:
|
return true
|
||||||
|
PointerEvent.Kind.RELEASE:
|
||||||
_end_stroke()
|
_end_stroke()
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
# Rechte Maustaste: absichtlich NICHT behandelt — sie gehört mit
|
return false
|
||||||
# absolutem Vorrang dem Freelook-Flug (orbit_camera.gd, Karte
|
|
||||||
# t_2470de89), in jedem Werkzeugzustand.
|
|
||||||
|
|
||||||
|
|
||||||
## Gamepad-Trigger (rechts) des ersten Pads; 0 ohne Pad. Nur Eingabe-Lesen,
|
## Gamepad-Trigger (rechts) des ersten Pads; 0 ohne Pad. Nur Eingabe-Lesen,
|
||||||
|
|
|
||||||
|
|
@ -220,61 +220,65 @@ func _process(_delta: float) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> 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:
|
if main == null or camera == null:
|
||||||
return
|
return false
|
||||||
var doc: Dictionary = main.get("doc")
|
var doc: Dictionary = main.get("doc")
|
||||||
if doc == null or doc.is_empty():
|
if doc == null or doc.is_empty():
|
||||||
return
|
return false
|
||||||
var tool_mode: String = str(main.get("tool_mode"))
|
var tool_mode: String = str(main.get("tool_mode"))
|
||||||
if tool_mode == "terrain" or tool_mode == "spawns":
|
if tool_mode == "terrain" or tool_mode == "spawns":
|
||||||
return # Terrain-Pinsel / Spawn-Zonen haben das Zepter
|
return false # Terrain-Pinsel / Spawn-Zonen haben das Zepter
|
||||||
if event is InputEventMouseButton:
|
match p.kind:
|
||||||
var mb := event as InputEventMouseButton
|
PointerEvent.Kind.PRESS:
|
||||||
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
|
|
||||||
if _dragging:
|
if _dragging:
|
||||||
return
|
return false
|
||||||
var inter = main.get("interaction")
|
var inter = main.get("interaction")
|
||||||
if inter == null or bool(inter.get("placing")):
|
if inter == null or bool(inter.get("placing")):
|
||||||
return
|
return false
|
||||||
if mode == "rotate":
|
if mode == "rotate":
|
||||||
var ring_axis := pick_ring(mb.position)
|
var ring_axis := pick_ring(p.position)
|
||||||
if ring_axis != "":
|
if ring_axis != "":
|
||||||
_begin_rotate(ring_axis, mb.position)
|
_begin_rotate(ring_axis, p.position)
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
return
|
return false
|
||||||
var axis := pick(mb.position)
|
var axis := pick(p.position)
|
||||||
if axis != "":
|
if axis != "":
|
||||||
_begin_drag(axis, mb.position)
|
_begin_drag(axis, p.position)
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.pressed:
|
PointerEvent.Kind.RELEASE:
|
||||||
if _rot_dragging:
|
if _rot_dragging:
|
||||||
_finish_rotate(true)
|
_finish_rotate(true)
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
elif _dragging:
|
if _dragging:
|
||||||
_finish_drag(true)
|
_finish_drag(true)
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
elif (mb.button_index == MOUSE_BUTTON_WHEEL_UP \
|
PointerEvent.Kind.SCROLL:
|
||||||
or mb.button_index == MOUSE_BUTTON_WHEEL_DOWN) and mb.pressed:
|
|
||||||
if _rot_dragging:
|
if _rot_dragging:
|
||||||
get_viewport().set_input_as_handled() # Mausrad zoomt nicht mitten im Dreh-Zug
|
return true # Mausrad zoomt nicht mitten im Dreh-Zug
|
||||||
elif _dragging:
|
if _dragging:
|
||||||
var dir := 1 if mb.button_index == MOUSE_BUTTON_WHEEL_UP else -1
|
cycle_step(p.scroll)
|
||||||
cycle_step(dir)
|
return true
|
||||||
get_viewport().set_input_as_handled()
|
PointerEvent.Kind.MOVE:
|
||||||
elif event is InputEventMouseMotion and _rot_dragging:
|
if _rot_dragging:
|
||||||
_rotate_motion((event as InputEventMouseMotion).position)
|
_rotate_motion(p.position)
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
elif event is InputEventMouseMotion and _dragging:
|
if _dragging:
|
||||||
_drag_motion((event as InputEventMouseMotion).position)
|
_drag_motion(p.position)
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
elif event is InputEventKey and (event as InputEventKey).pressed \
|
PointerEvent.Kind.CANCEL:
|
||||||
and (event as InputEventKey).keycode == KEY_ESCAPE:
|
|
||||||
if _rot_dragging:
|
if _rot_dragging:
|
||||||
_finish_rotate(false)
|
_finish_rotate(false)
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
elif _dragging:
|
if _dragging:
|
||||||
_finish_drag(false)
|
_finish_drag(false)
|
||||||
get_viewport().set_input_as_handled()
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------- Treffer -----
|
# ------------------------------------------------------------- Treffer -----
|
||||||
|
|
|
||||||
156
tests/test_pointer_router.gd
Normal file
156
tests/test_pointer_router.gd
Normal 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
|
||||||
1
tests/test_pointer_router.gd.uid
Normal file
1
tests/test_pointer_router.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dej077j3x2b1u
|
||||||
Loading…
Reference in a new issue