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