keebie/scripts/layouts/parser_kle.gd
2025-08-02 06:01:31 +10:00

274 lines
6.7 KiB
GDScript

class_name ParserKLE extends AbstractParser
const LABEL_TO_KEYCODE_MAP: Dictionary[String, Key] = {
"`": KEY_QUOTELEFT,
"-": KEY_MINUS,
"=": KEY_EQUAL,
"[": KEY_BRACKETLEFT,
"]": KEY_BRACKETRIGHT,
"\\": KEY_BACKSLASH,
";": KEY_SEMICOLON,
"'": KEY_APOSTROPHE,
",": KEY_COMMA,
".": KEY_PERIOD,
"/": KEY_SLASH,
"#": KEY_NUMBERSIGN,
"Return": KEY_ENTER,
"Esc": KEY_ESCAPE,
"esc": KEY_ESCAPE,
"Bksp": KEY_BACKSPACE,
"Back Space": KEY_BACKSPACE,
"Back<br>Space": KEY_BACKSPACE,
"Caps Lock": KEY_CAPSLOCK,
"Caps<br>Lock": KEY_CAPSLOCK,
"caps lock": KEY_CAPSLOCK,
"control": KEY_CTRL,
"Win": KEY_META,
"Cmd": KEY_META,
"super": KEY_META,
"": KEY_META,
"command": KEY_META,
"Meta": KEY_META,
"AltGr": KEY_ALT,
"PrtSc": KEY_PRINT,
"PrintScr SysReq": KEY_PRINT,
"Scroll Lock": KEY_SCROLLLOCK,
"Scroll<br>lock": KEY_SCROLLLOCK,
"PgUp": KEY_PAGEUP,
"Page Up": KEY_PAGEUP,
"Page<br>Up": KEY_PAGEUP,
"PgDn": KEY_PAGEDOWN,
"Page<br>Down": KEY_PAGEDOWN,
"Pause Break": KEY_PAUSE,
"Page Down": KEY_PAGEDOWN,
"Num Lock": KEY_NUMLOCK,
"Fn": KEY_SPECIAL,
"": KEY_UP,
"": KEY_UP,
"&uarr;": KEY_UP,
"": KEY_LEFT,
"": KEY_LEFT,
"&larr;": KEY_LEFT,
"": KEY_DOWN,
"": KEY_DOWN,
"&darr;": KEY_DOWN,
"": KEY_RIGHT,
"": KEY_RIGHT,
"&rarr;": KEY_RIGHT,
}
const LABEL_TO_NUMPAD_KEYCODE_MAP: Dictionary[String, Key] = {
"/": KEY_KP_DIVIDE,
"*": KEY_KP_MULTIPLY,
"-": KEY_KP_SUBTRACT,
"+": KEY_KP_ADD,
".": KEY_KP_PERIOD,
}
const NAME := "name"
const W := "w"
const H := "h"
const X := "x"
const Y := "y"
const W2 := "w2"
const H2 := "h2"
const X2 := "x2"
const Y2 := "y2"
const R := "r"
const RX := "rx"
const RY := "ry"
const N := "n"
const KEY_DICT := "key_dict"
const POS := "pos"
var _name: String
var _rows: Array[Array]
var _file_name: String
func _init(data: Array, file_name: String) -> void:
_file_name = file_name
var key_pos_dicts: Dictionary[Key, Array] = {}
var row_index: int = 0
for data_row: Variant in data:
if data_row is Dictionary and (data_row as Dictionary).has(NAME):
_name = (data_row as Dictionary)[NAME]
if data_row is Array:
var layout_row := _deserialize_row(
data_row as Array, key_pos_dicts, row_index
)
if layout_row:
_rows.append(layout_row)
row_index += 1
_get_key_locations(key_pos_dicts)
func get_name() -> String:
return _name
func get_rows() -> Array[Array]:
return _rows
func _deserialize_row(
data_row: Array, key_pos_dicts: Dictionary[Key, Array], row_index: int
) -> Array[Dictionary]:
var layout_row: Array[Dictionary] = []
var key_pos := Vector2(0, row_index)
var current_key_data_dict: Dictionary = {}
for data_key: Variant in data_row:
if data_key is Dictionary:
current_key_data_dict = data_key as Dictionary
if data_key is String:
var legend := (data_key as String).split("\n")
var keycode := _get_keycode_from_legend(legend, current_key_data_dict)
var key_dict := {KeyProps.KEY: keycode}
key_dict.merge(_deserialize_key(current_key_data_dict))
layout_row.append(key_dict)
key_pos.x += key_dict[KeyProps.X] if key_dict.has(KeyProps.X) else 0.0
var key_pos_dict := {KEY_DICT: key_dict, POS: key_pos}
if key_pos_dicts.has(keycode):
key_pos_dicts[keycode].append(key_pos_dict)
else:
key_pos_dicts[keycode] = [key_pos_dict] as Array[Dictionary]
key_pos.x += key_dict[KeyProps.W] if key_dict.has(KeyProps.W) else 1.0
current_key_data_dict = {}
return layout_row
func _deserialize_key(data_key: Dictionary) -> Dictionary:
var key_dict: Dictionary = {}
if data_key.has(W):
key_dict[KeyProps.W] = data_key[W]
if data_key.has(H):
key_dict[KeyProps.H] = data_key[H]
if data_key.has(X):
key_dict[KeyProps.X] = data_key[X]
if data_key.has(Y):
key_dict[KeyProps.Y] = data_key[Y]
if data_key.has(W2):
key_dict[KeyProps.W2] = data_key[W2]
if data_key.has(H2):
key_dict[KeyProps.H2] = data_key[H2]
if data_key.has(X2):
key_dict[KeyProps.X2] = data_key[X2]
if data_key.has(Y2):
key_dict[KeyProps.Y2] = data_key[Y2]
if data_key.has(R):
key_dict[KeyProps.R] = data_key[R]
if data_key.has(RX):
key_dict[KeyProps.PX] = data_key[RX]
if data_key.has(RY):
key_dict[KeyProps.PY] = data_key[RY]
if data_key.has(N):
key_dict[KeyProps.NUB] = data_key[N]
return key_dict
func _get_keycode_from_legend(legend: Array[String], data_key: Dictionary) -> Key:
if legend.size() == 1 and legend[0] == "" and data_key.has(W) and data_key[W] > 1:
return KEY_SPACE
var keycode := KEY_NONE
keycode = _get_numpad_keycode_from_legend(legend)
if keycode == KEY_NONE:
keycode = OS.find_keycode_from_string(legend[0])
if keycode == KEY_NONE and legend.size() >= 2:
keycode = OS.find_keycode_from_string(legend[1])
if keycode == KEY_NONE and legend.size() >= 2:
if LABEL_TO_KEYCODE_MAP.has(legend[1]):
keycode = LABEL_TO_KEYCODE_MAP[legend[1]]
elif LABEL_TO_KEYCODE_MAP.has(legend[0]):
keycode = LABEL_TO_KEYCODE_MAP[legend[0]]
if (
keycode == KEY_NONE
and legend.size() == 1
and LABEL_TO_KEYCODE_MAP.has(legend[0])
):
keycode = LABEL_TO_KEYCODE_MAP[legend[0]]
if keycode == KEY_NONE and legend.size() > 2:
for i in range(2, legend.size()):
if not legend[i]:
continue
keycode = OS.find_keycode_from_string(legend[i])
if keycode == KEY_NONE and LABEL_TO_KEYCODE_MAP.has(legend[i]):
keycode = LABEL_TO_KEYCODE_MAP[legend[i]]
if keycode != KEY_NONE:
break
if keycode == KEY_NONE:
printerr("%s: could not recognize key label %s" % [_file_name, str(legend)])
return keycode
func _get_numpad_keycode_from_legend(legend: Array[String]) -> Key:
if legend.size() == 1 or legend.size() >= 2:
if legend[0].length() == 1 and legend[0].is_valid_int():
return KEY_KP_0 + int(legend[0]) as Key
if (
LABEL_TO_NUMPAD_KEYCODE_MAP.has(legend[0])
and not (legend.size() >= 2 and legend[1].length() == 1)
):
return LABEL_TO_NUMPAD_KEYCODE_MAP[legend[0]]
return KEY_NONE
func _get_key_locations(key_pos_dicts: Dictionary[Key, Array]) -> void:
for keycode in key_pos_dicts:
var dicts := key_pos_dicts[keycode] as Array[Dictionary]
if dicts.size() == 1:
continue
var key_pos_dict_left: Dictionary
var key_pos_dict_right: Dictionary
for key_pos_dict in dicts:
var key_pos := key_pos_dict[POS] as Vector2
if (
not key_pos_dict_left
or key_pos.x < (key_pos_dict_left[POS] as Vector2).x
):
key_pos_dict_left = key_pos_dict
if (
not key_pos_dict_right
or key_pos.x > (key_pos_dict_right[POS] as Vector2).x
):
key_pos_dict_right = key_pos_dict
if key_pos_dict_left == key_pos_dict_right:
continue
if keycode == KEY_ENTER:
key_pos_dict_right[KEY_DICT][KeyProps.KEY] = KEY_KP_ENTER
continue
key_pos_dict_left[KEY_DICT][KeyProps.LOC] = KEY_LOCATION_LEFT
key_pos_dict_right[KEY_DICT][KeyProps.LOC] = KEY_LOCATION_RIGHT