OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / build_dock_gate.gd

131 lines · gd ref:
extends SceneTree
## THE DOCK — two columns, and nothing in either may eat Tab.
##
##     "$HYPER_TPS_GODOT" --headless --script scenes/test/build_dock_gate.gd
##
## THE TAB RULE IS THE POINT OF THIS FILE. Tab is Godot's ui_focus_next, so any
## Control in a visible dock with FOCUS_ALL enters the traversal order and
## swallows the key before the viewport sees it. build_dock.gd has always known
## this — its headers are FOCUS_NONE and say why — but the rule only covered
## chrome the dock builds itself. The outliner arrived with four Buttons and a
## Tree, all FOCUS_ALL by default, and Tab stopped reaching the create tool's
## shape cycle: it was traversing dock widgets instead.
##
## That is a whole class of bug that reappears with every panel anyone adds, and
## it presents as "a key stopped working" somewhere else entirely. So the rule
## lives in register() and this asserts it over a panel built the careless way —
## one that does nothing to help itself.

var _failures: Array[String] = []
var _frame := 0


func _check(label: String, ok: bool, detail: String) -> void:
	if not ok:
		_failures.append("%s (%s)" % [label, detail])
	print("  %-52s %s  %s" % [label, "OK  " if ok else "FAIL", detail])


## Deliberately hostile: every control here defaults to FOCUS_ALL and the panel
## makes no effort to decline it. If the dock only worked with cooperative
## panels it would not be a rule.
func _careless_panel() -> Control:
	var v := VBoxContainer.new()
	var b := Button.new()
	b.text = "click me"
	v.add_child(b)
	var t := Tree.new()
	v.add_child(t)
	var le := LineEdit.new()
	v.add_child(le)
	return v


func _focus_all_count(node: Node) -> int:
	var n := 0
	if node is Control and (node as Control).focus_mode == Control.FOCUS_ALL:
		n += 1
	for c in node.get_children():
		n += _focus_all_count(c)
	return n


func _column_of(dock: Node, side: String) -> Node:
	return (dock._sides[side]["column"]) as Node


func _process(_delta: float) -> bool:
	_frame += 1
	if _frame < 3:
		return false
	var dock: Node = root.get_node_or_null("/root/BuildDock")
	if dock == null:
		print("FAIL — no /root/BuildDock")
		quit(1)
		return true

	print("build dock gate")
	dock.register("gate_left", "Left", _careless_panel(), true, dock.SIDE_LEFT)
	dock.register("gate_right", "Right", _careless_panel(), true, dock.SIDE_RIGHT)

	_check("both columns exist", dock._sides.has(dock.SIDE_LEFT)
		and dock._sides.has(dock.SIDE_RIGHT), "left + right")
	_check("a left panel lands in the left column",
		_column_of(dock, dock.SIDE_LEFT).get_child_count() > 0,
		"%d frame(s)" % _column_of(dock, dock.SIDE_LEFT).get_child_count())
	_check("a right panel lands in the right column",
		_column_of(dock, dock.SIDE_RIGHT).get_child_count() > 0,
		"%d frame(s)" % _column_of(dock, dock.SIDE_RIGHT).get_child_count())

	# THE RULE.
	var greedy: int = _focus_all_count(dock)
	_check("NOTHING in the dock accepts Tab focus", greedy == 0,
		"%d control(s) still FOCUS_ALL" % greedy)

	# And it is a downgrade, not a lobotomy: click-to-use must survive, or the
	# Tree cannot be renamed in and the fix has broken the panel to save the key.
	var clickable := 0
	for c in _column_of(dock, dock.SIDE_RIGHT).get_children():
		clickable += _focus_click_count(c)
	_check("but click-to-focus survives", clickable >= 3,
		"%d control(s) FOCUS_CLICK" % clickable)

	# THE LEFT EDGE IS SHARED. keet_social's membership and mesh readouts sit at
	# fixed y=56 and y=128 on layer 128, above the game HUD's peer and FPS labels
	# — and above the dock's own layer, so a collision draws over a panel rather
	# than fighting it. Silent, and it looks like the panel is broken.
	var lroot: Control = dock._sides[dock.SIDE_LEFT]["root"]
	var rroot: Control = dock._sides[dock.SIDE_RIGHT]["root"]
	var ltop: int = lroot.get_theme_constant("margin_top")
	_check("the left column clears the HUD strip", ltop >= 170,
		"margin_top %d, strip ends ~170" % ltop)
	_check("and the right column does not pay for it",
		rroot.get_theme_constant("margin_top") <= 32,
		"margin_top %d" % rroot.get_theme_constant("margin_top"))

	# An unknown side must not silently vanish the panel.
	dock.register("gate_bogus", "Bogus", _careless_panel(), true, "sideways")
	_check("an unknown side falls back to left rather than dropping the panel",
		dock.has_panel("gate_bogus"), "registered")

	dock.unregister("gate_left")
	dock.unregister("gate_right")
	dock.unregister("gate_bogus")
	_check("unregister cleans up", not dock.has_panel("gate_left")
		and not dock.has_panel("gate_right"), "gone")

	print("\nRESULT: %s" % ("PASS" if _failures.is_empty() else "FAIL"))
	for f: String in _failures:
		print("  - %s" % f)
	quit(0 if _failures.is_empty() else 1)
	return true


func _focus_click_count(node: Node) -> int:
	var n := 0
	if node is Control and (node as Control).focus_mode == Control.FOCUS_CLICK:
		n += 1
	for c in node.get_children():
		n += _focus_click_count(c)
	return n