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
Space": KEY_BACKSPACE, "Caps Lock": KEY_CAPSLOCK, "Caps
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
lock": KEY_SCROLLLOCK, "PgUp": KEY_PAGEUP, "Page Up": KEY_PAGEUP, "Page
Up": KEY_PAGEUP, "PgDn": KEY_PAGEDOWN, "Page
Down": KEY_PAGEDOWN, "Pause Break": KEY_PAUSE, "Page Down": KEY_PAGEDOWN, "Num Lock": KEY_NUMLOCK, "Fn": KEY_SPECIAL, "↑": KEY_UP, "⇧": KEY_UP, "↑": KEY_UP, "←": KEY_LEFT, "⇦": KEY_LEFT, "←": KEY_LEFT, "↓": KEY_DOWN, "⇩": KEY_DOWN, "↓": KEY_DOWN, "→": KEY_RIGHT, "⇨": KEY_RIGHT, "→": 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 ( keycode == KEY_NUMBERSIGN and key_pos_dicts.has(KEY_BACKSLASH) and key_pos_dicts[KEY_BACKSLASH][0][POS].x < dicts[0][POS].x ): dicts[0][KEY_DICT][KeyProps.KEY] = KEY_BACKSLASH key_pos_dicts[KEY_BACKSLASH][0][KEY_DICT][KeyProps.KEY] = KEY_SECTION continue 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 if keycode == KEY_BACKSLASH: key_pos_dict_left[KEY_DICT][KeyProps.KEY] = KEY_SECTION continue key_pos_dict_left[KEY_DICT][KeyProps.LOC] = KEY_LOCATION_LEFT key_pos_dict_right[KEY_DICT][KeyProps.LOC] = KEY_LOCATION_RIGHT