OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_confirm_focus.gd

145 lines · gd ref:
extends SceneTree
## GATE — Enter confirms the active tool even when a dock control holds the keyboard
## (hyper-games #109, 2026-10-12: "when scaling, pressing enter should apply the scale,
## currently you have to manually click SCALE in the editor modal").
##
## gate_scale K3 (now a real key into the map) passes, so Enter dies BEFORE the map:
## through the viewport, a focused Control gets every key first. Each case below sends
## Enter the way the game receives it — Viewport.push_input — not into the map directly.
##
##   F0  control: nothing focused — Enter closes the tool and writes one brush.scale
##   F1  the Outliner's Tree focused (you clicked a row to select the brush; BuildDock
##       leaves a Tree FOCUS_CLICK for inline rename) — the Tree took Enter as
##       "activate row" and the tool never saw it
##
## RUN: CI=true HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$GODOT" --headless --path . --script res://scenes/test/gate_confirm_focus.gd

var _frame := 0
var _map: Node = null
var _level: Node = null
var _persist: Node = null
var fails := 0
var checks := 0
var _step := 0
var _brush := -1
var _nrec := 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 _key(code: int) -> InputEventKey:
	var e := InputEventKey.new()
	e.physical_keycode = code
	e.keycode = code
	e.pressed = true
	return e


func _release(code: int) -> InputEventKey:
	var e := _key(code)
	e.pressed = false
	return e


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 _count() -> int:
	var n := 0
	for r: Dictionary in (_persist.records() as Array):
		if String(r.get("kind", "")) == "brush.scale":
			n += 1
	return n


func _press(code: int) -> void:
	root.push_input(_key(code))
	root.push_input(_release(code))


func _process(_d: float) -> bool:
	_frame += 1
	if _frame == 1:
		root.get_node("/root/GameMode").set_mode("build")
		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")
		_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
		root.add_child(_map)
		return false
	if _frame < 30:
		return false
	# One case per few frames: push_input is delivered on the next frame.
	if _frame % 4 != 0:
		return false
	match _step:
		0: _setup()
		1: _start_case(null)                 # F0
		2: _check("F0 control: nothing focused")
		3: _start_case("tree")               # F1
		4: _check("F1 the Outliner's Tree focused")
		_:
			if checks < 4:
				print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
				fails += 1
			print("\nGATE confirm_focus: %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


func _setup() -> void:
	_level = _map.get("level")
	_persist = _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 = _level.brush_by_key(dot)


func _start_case(focus: Variant) -> void:
	var vp_focus := root.gui_get_focus_owner()
	if vp_focus != null:
		vp_focus.release_focus()
	_map.select_brushes(PackedInt32Array([_brush]))
	_press(KEY_S)                  # scale is S since keymap step 3
	_nrec = _count()
	# Grow one step through the tool directly — the case is about Enter, not ].
	var stack: Node = _map.call("_ensure_ops")
	if stack.active != null:
		stack.active.set_tool_setting("steps", 1)
		stack.active.update_preview()
	if focus == "tree":
		var panel: Node = _map.get("_outliner")
		var tree: Tree = panel.get("_tree") if panel != null else null
		# The REAL focus mode — the dock's rule set it; the gate does not change it.
		print("  [case tree] focus_mode %d (FOCUS_CLICK=%d)" % [tree.focus_mode if tree else -1, Control.FOCUS_CLICK])
		if tree != null:
			tree.grab_focus()
	print("  [case %s] focus owner: %s" % [str(focus), str(root.gui_get_focus_owner())])
	root.push_input(_key(KEY_ENTER))
	root.push_input(_release(KEY_ENTER))


func _check(label: String) -> void:
	var stack: Node = _map.call("_ensure_ops")
	var wrote := _count() - _nrec
	_p(stack.active == null and wrote == 1, label + ": one Enter confirms",
		"active=%s wrote=%d" % [str(stack.active), wrote])
	if stack.active != null:
		stack.cancel() if stack.has_method("cancel") else stack.handle_escape()
	_persist.undo_gesture(_persist.newest_live_gesture()) if wrote > 0 else null
	_persist.refold()
	_p(root.gui_get_focus_owner() == null or not (root.gui_get_focus_owner() is LineEdit),
		label + ": the keyboard is back with the game", str(root.gui_get_focus_owner()))