extends SceneTree
## GATE — A RUNNING TOOL OWNS THE MOUSE (hyper-games #124; user 2026-10-18: "when doing a
## mouse move with g it seems i can still select things in the panels? that should be an
## override since i expect the mouse travel to not have other panels interactive").
## Blender's modal operators take every event. Real events through Viewport.push_input
## (the GUI sees them first), a counting Button in the left column, a real 1920x1080 window.
## T0 control: with no tool up, a click on the panel button presses it
## T1 G up: the pointer travelling over the button does not hover it
## T2 G up: LMB over the button does not press it — it CONFIRMS the move
## T3 G up again: RMB over a panel CANCELS the move, the button untouched
## T4 the tool closed: the panel button works again
## T5 looking (RMB held, pointer captured) with a tool up: the shield is down
## T6 the look ends: the shield is up again
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$GODOT" --headless --path . --script res://scenes/test/gate_tool_owns_mouse.gd
var _frame := 0
var _map: Node = null
var _dock: Node = null
var _btn: Button = null
var _presses := 0
var _brush := -1
var _step := 0
var fails := 0
var checks := 0
func _p(c: bool, m: String, got: Variant = "") -> void:
checks += 1
if not c:
fails += 1
print(" %-66s %s %s" % [m, "OK" if c else "**FAIL**", str(got)])
func _box(lo: Vector3, hi: Vector3) -> Array:
return [1, 0, 0, hi.x, -1, 0, 0, -lo.x, 0, 1, 0, hi.y, 0, -1, 0, -lo.y, 0, 0, 1, hi.z, 0, 0, -1, -lo.z]
func _move(at: Vector2) -> void:
var m := InputEventMouseMotion.new()
m.position = at
m.global_position = at
root.push_input(m)
func _click(at: Vector2, button: MouseButton) -> void:
_move(at)
for pressed: bool in [true, false]:
var b := InputEventMouseButton.new()
b.button_index = button
b.pressed = pressed
b.position = at
b.global_position = at
root.push_input(b)
func _key(code: Key) -> void:
var e := InputEventKey.new()
e.keycode = code
e.physical_keycode = code
e.pressed = true
_map._unhandled_key_input(e)
func _ops() -> Node:
return _map.call("_ensure_ops")
func _start_move() -> void:
_map.select_brushes(PackedInt32Array([_brush]))
_key(KEY_G)
func _btn_at() -> Vector2:
return _btn.get_global_rect().get_center()
func _process(_d: float) -> bool:
_frame += 1
if _frame == 1:
root.size = Vector2i(1920, 1080)
var keet: Node = root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "gate-author"
var gs: Node = root.get_node_or_null("/root/GameState")
if gs != null:
gs.route("world:level:*", 1, "peer")
root.get_node("/root/GameMode").set_mode("build")
_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
root.add_child(_map)
_dock = root.get_node("/root/BuildDock")
_btn = Button.new()
_btn.text = "count"
_btn.pressed.connect(func() -> void: _presses += 1)
_dock.register("gate_mouse", "Gate", _btn, true, _dock.SIDE_LEFT)
return false
if _frame < 40 or _frame % 5 != 0:
return false
match _step:
0:
var persist: Node = _map.get("_persist")
var dot: String = persist.record("brush.create", {"planes": _box(Vector3(0, 0, 0), Vector3(2, 1, 1)),
"proj": [], "material_id": 0, "level_id": 0, "sky": false})
persist.refold()
_brush = _map.get("level").brush_by_key(dot)
_click(_btn_at(), MOUSE_BUTTON_LEFT)
1:
_p(_presses == 1, "T0 control: no tool up, a click presses the panel button", _presses)
_move(_dock.view_rect().get_center()) # off the button, so it is not hovered from T0
_start_move()
2:
_p(_ops().active != null, "T1 (setup) G started the move tool", _ops().active)
_move(_btn_at())
3:
_p(not _btn.is_hovered(), "T1 G up: the pointer over the button does not hover it")
_click(_btn_at(), MOUSE_BUTTON_LEFT)
4:
_p(_presses == 1, "T2 G up: LMB over the button does not press it", _presses)
_p(_ops().active == null, "T2 ... it confirms the move (the tool closed)", _ops().active)
_start_move()
5:
_click(_btn_at(), MOUSE_BUTTON_RIGHT)
6:
_p(_ops().active == null and _presses == 1, "T3 G up: RMB over a panel cancels, the button untouched",
[_ops().active, _presses])
_click(_btn_at(), MOUSE_BUTTON_LEFT)
7:
_p(_presses == 2, "T4 the tool closed: the panel button works again", _presses)
# T5: LOOKING (RMB held, pointer captured) with a tool up: the GUI still gets
# clicks at the screen centre while captured, so the shield must be DOWN or it
# would eat the tool's LMB and the RMB release that ends the look. The look is
# set as GameMode's own RMB handler sets it (_temp_look + its submode signal):
# this asserts the shield FOLLOWS the pointer mode, not how a look starts.
_start_move()
8:
var gm: Node = root.get_node("/root/GameMode")
gm.set("_temp_look", true)
gm.build_submode_changed.emit("fly")
_p(not gm.wants_pointer() and _ops().active != null and not _dock.is_modal(),
"T5 looking with a tool up: the panel shield is down", [gm.wants_pointer(), _dock.is_modal()])
gm.set("_temp_look", false)
gm.build_submode_changed.emit("cursor")
_p(gm.wants_pointer() and _dock.is_modal(), "T6 the look ends: the shield is up again",
[gm.wants_pointer(), _dock.is_modal()])
_ops().cancel()
_dock.unregister("gate_mouse")
_:
if checks < 9:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE tool_owns_mouse: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
quit(0 if fails == 0 else 1)
return true
_step += 1
return false