extends SceneTree
## GATE — keymap step 5: KEYBOARD LAYOUTS (docs/keymap-blender-279.md §6).
##
## Letters bind by CHARACTER (Blender: GHOST_SystemX11 takes the keysym), everything else
## by POSITION (the number row — Blender forces it to digits —, numpad, [ ] - =, the fly
## modal's E / Q). Each event below is shaped as the layout sends it: physical_keycode =
## where the key IS (QWERTY names), keycode = what the layout TYPES there.
##
## Run: HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_keymap_layouts.gd
var ok := true
var checks := 0
var _frame := 0
var _km: Node
func _p(cond: bool, what: String, note: Variant = "") -> void:
checks += 1
if not cond:
ok = false
print(" %-66s %s %s" % [what, "OK" if cond else "**FAIL**", str(note)])
## A key event as a layout sends it.
func _ev(physical: int, typed: int, ctrl := false, shift := false) -> InputEventKey:
var e := InputEventKey.new()
e.physical_keycode = physical
e.keycode = typed
e.pressed = true
e.ctrl_pressed = ctrl
e.shift_pressed = shift
return e
## Every action in the active contexts this event fires (exact, as Keymap.tapped).
func _fires(e: InputEventKey) -> Array[String]:
var out: Array[String] = []
for ctx: String in _km.DEFAULTS:
if not _km.is_context_active(ctx):
continue
for a: String in (_km.DEFAULTS[ctx] as Dictionary):
if _km.tapped(e, a):
out.append(a)
return out
func _process(_d: float) -> bool:
_frame += 1
if _frame < 3:
return false
_km = root.get_node("/root/Keymap")
var gm: Node = root.get_node("/root/GameMode")
gm.set_mode("build")
_km.push_context("builder")
print("-- AZERTY --")
var a_at_q := _ev(KEY_Q, KEY_A)
_p(_km.tapped(a_at_q, "builder.select_all"), "the key that TYPES A (QWERTY's Q spot) selects all")
_p(not _km.tapped(a_at_q, "builder.q_menu"), "... and is NOT the Q menu")
_p(_fires(a_at_q) == ["builder.select_all"], "... and fires exactly one action", _fires(a_at_q))
var z_at_w := _ev(KEY_W, KEY_Z)
_p(_km.tapped(z_at_w, "builder.wireframe"), "the key that types Z (QWERTY's W spot) is the wireframe")
var amp_at_1 := _ev(KEY_1, KEY_AMPERSAND)
_p(_km.tapped(amp_at_1, "build_mode.sel_vertex"), "the number row's 1 (types &) still selects vertex mode")
var q_at_a := _ev(KEY_A, KEY_Q)
_p(_km.tapped(q_at_a, "builder.q_menu") and not _km.tapped(q_at_a, "builder.select_all"),
"the key that types Q (QWERTY's A spot) is the Q menu, not select all")
print("-- Dvorak --")
var g_at_u := _ev(KEY_U, KEY_G)
_p(_km.tapped(g_at_u, "builder.move"), "the key that types G (QWERTY's U spot) moves")
_p(not _km.tapped(g_at_u, "builder.uv_menu"), "... and is NOT the U menu")
var dvorak_ctrl_z := _ev(KEY_SLASH, KEY_Z, true)
_p(_km.tapped(dvorak_ctrl_z, "builder.undo"), "Ctrl + the key that types Z undoes, wherever Z is")
print("-- German / punctuation by POSITION --")
var ue_at_bracket := _ev(KEY_BRACKETLEFT, 0x00DC) # Ü (Godot keycodes are Unicode)
_p(_km.tapped(ue_at_bracket, "builder.move_in"), "the [ position (types Ü) is still 'move in'")
_p(_km.key_of(ue_at_bracket) == KEY_BRACKETLEFT, "key_of: [ by position", _km.key_of(ue_at_bracket))
_p(_km.key_of(a_at_q) == KEY_A, "key_of: a letter by character", _km.key_of(a_at_q))
print("-- numpad and the fly modal --")
_p(_km.tapped(_ev(KEY_KP_5, KEY_KP_5), "view.persp_ortho"), "Numpad 5 is perspective / ortho")
_p(InputMap.event_is_action(_ev(KEY_E, KEY_PERIOD), "fly.up", true),
"fly up is the E POSITION (Dvorak types . there), like WASD")
_p(not InputMap.event_is_action(_ev(KEY_PERIOD, KEY_E), "fly.up", true),
"... not wherever the layout types E")
print("-- names --")
_p(String(_km.describe("builder.select_all")) == "A", "a letter command is named by its character",
_km.describe("builder.select_all"))
_p(String(_km.describe("build_mode.sel_vertex")) != "", "a positional key has a name", _km.describe("build_mode.sel_vertex"))
gm.set_mode("play")
if checks < 16:
_p(false, "only %d checks ran" % checks)
print("\nGATE keymap_layouts: %s (%d checks)" % ["PASS" if ok else "FAIL", checks])
quit(0 if ok else 1)
return true