OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_tool_owns_mouse.gd

156 lines · gd ref:
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