Compare commits

...

110 Commits
v0.0.1 ... main

Author SHA1 Message Date
eea5cc4e4c move key_helper to utils directory 2025-08-13 15:17:47 +10:00
e6737223be move key idle waving anim out of sod 2025-08-13 15:17:13 +10:00
d609bdb21d set version to 0.0.2
All checks were successful
Build Godot Project1 / godot (${PROJECT_NAME}.exe, windows) (push) Successful in 2m39s
2025-08-11 19:57:46 +10:00
87c06cfd04 return key press light pos 2025-08-08 15:37:30 +10:00
a2e2e0f18c make key material local to scene 2025-08-08 15:34:09 +10:00
d328836ec5 add debug display hiding layout labels 2025-08-08 02:31:28 +10:00
78667ade3d fix keyboard sfx playing 2025-08-08 02:30:47 +10:00
95eb1ebd58 add move sounds to THIRDPARTY.md 2025-08-08 02:06:59 +10:00
534b81e1ba add player hopping sfx 2025-08-08 02:05:43 +10:00
f1666b3085 add sfx/THIRDPARTY.md 2025-08-08 01:48:32 +10:00
8ffc7bb427 change default bus volume 2025-08-08 01:48:16 +10:00
b2594f2983 remove sfx attenuation 2025-08-07 23:56:10 +10:00
106a8611be replace key.glb with key.gltf 2025-08-06 01:34:41 +10:00
564cf7c168 add query_keys_by_keycodes method 2025-08-06 01:34:14 +10:00
0ba9de1095 fix plater rotation and key pressed signal 2025-08-05 21:22:48 +10:00
7878d8b764 add the keyboard check back to game key ready 2025-08-04 07:49:34 +10:00
fcb5ba372f store config keys as keycode strings instead of keycode enum values 2025-08-04 04:55:06 +10:00
d791ec24b3 make namings more clear 2025-08-04 04:48:14 +10:00
2bc77aa297 replace dict key enums with string constants 2025-08-04 02:59:36 +10:00
3db3d07029 move key iteration to KeyHelper 2025-08-04 01:36:22 +10:00
012fae3b3a remove != OK 2025-08-04 00:20:54 +10:00
728cc4452d add error_string to error codes 2025-08-03 23:50:13 +10:00
a0428d90d6 replace printerr with push_error and push_warning 2025-08-03 23:33:35 +10:00
82c9a229da rename key request to key query 2025-08-03 16:18:03 +10:00
02e77f8bce remove exports from key props 2025-08-03 03:56:33 +10:00
c1e4cc93a9 rename key props physical_keycode to keycode 2025-08-03 03:54:24 +10:00
e9a6d5ed5c move sort func to key helper 2025-08-03 03:52:29 +10:00
532505c812 refactor static methods 2025-08-03 03:49:32 +10:00
8df1181be3 rename KeyAdjacency to KeyHelper and move is_adjacent to GameKey 2025-08-03 03:40:46 +10:00
8998fcc84f group nodes in game key scene 2025-08-03 03:32:00 +10:00
e2c67b79a9 remove @export from props in game key class 2025-08-03 03:30:18 +10:00
dc7f20face simplify check for no keyboard in game key ready 2025-08-03 03:28:47 +10:00
aa44311c1f make keyboard and keys tool scripts and add editor keys generation 2025-08-03 03:03:42 +10:00
f2be1197d6 add number row to key adjacency map 2025-08-03 00:55:56 +10:00
c5c0f50aad add emit_player_key_change on ready and add extra checks for empty _current_key 2025-08-03 00:41:15 +10:00
7966e2073c rename _push_outwards to _push_radial 2025-08-03 00:13:57 +10:00
9cf81ffe62 add key animation when player lands on it 2025-08-03 00:03:47 +10:00
f1af3e00cc change keyboard pressed positions erasing to just setting false 2025-08-02 23:38:08 +10:00
3888476c41 add hopping when moving 2025-08-02 23:33:55 +10:00
b5dac49ef0 tweak camera position and fov 2025-08-02 23:07:11 +10:00
53fb1c6958 refactor player transforming 2025-08-02 23:03:26 +10:00
6f399db783 add player with basic movement 2025-08-02 22:51:10 +10:00
acbad4aadf make game key keyboard property private 2025-08-02 21:42:35 +10:00
030c1685c7 add more details to custom layout readme 2025-08-02 21:08:01 +10:00
8a1d4aa1ed add non-letter keys to adjacency map 2025-08-02 20:23:40 +10:00
5b32ea57d6 add lights to visualize key adjacency 2025-08-02 19:41:17 +10:00
e6b58236d7 add key adjacency map 2025-08-02 19:21:24 +10:00
da1c491a09 add limit to key requests 2025-08-02 18:28:15 +10:00
c058c274ac add extra check for 'KEY_NONE' in game key input 2025-08-02 18:23:10 +10:00
0376dd6450 add extra check for space when setting labels and rename 'is_char' to 'is_unicode' 2025-08-02 18:17:12 +10:00
1f73e04865 add .vscode/ to .gitignore 2025-08-02 14:04:50 +10:00
263e06a944 replace newlines in printerr with empty prints 2025-08-02 13:58:44 +10:00
02f112f168 improve parsing error handling 2025-08-02 13:48:56 +10:00
60d603bcef remove quotation marks around kle legend in error message 2025-08-02 13:24:46 +10:00
ecc7912529 add better error log formatting 2025-08-02 13:19:35 +10:00
2cb96155f2 add info about qmk to custom layout readme 2025-08-02 12:31:23 +10:00
e967664b73 move is_configuring to keyboard class and add pushing animations 2025-08-02 12:04:49 +10:00
782e24e411 add shaking when changing configuring state 2025-08-02 11:52:07 +10:00
ead41f16d5 move _set_keyboard_pressed_position to _on_keyboard_layout_size_changed 2025-08-02 11:36:58 +10:00
2863a7fc7d add sorting to requested keys 2025-08-02 11:05:44 +10:00
35511f2d66 make request_keys return the keys 2025-08-02 10:50:35 +10:00
fab669738d replace static key scene with a local var 2025-08-02 10:44:29 +10:00
a6e31e55ed add ability to get keyboard keys by filter 2025-08-02 10:39:33 +10:00
2d966647d2 add error message for when the layout is being replaced 2025-08-02 10:17:24 +10:00
2217bf76d1 replace unknown and special keys with none 2025-08-02 10:04:33 +10:00
e57b7829a9 consider rotation when getting kle key location 2025-08-02 09:45:35 +10:00
b046094f82 add check for pivot when adding scaling keys with gaps 2025-08-02 09:34:03 +10:00
21b8c41a9f add key rotation damping 2025-08-02 08:41:32 +10:00
5dba5cdc2f make detecting numpad enter dependant on previous keycode 2025-08-02 08:32:45 +10:00
a17becae83 add extra checks for kle iso section key 2025-08-02 08:19:59 +10:00
b6b0f45956 simplify qmk parsing by sorting keys by position 2025-08-02 07:55:58 +10:00
dadf9298cb make error messages more consistent and add more of them 2025-08-02 06:19:50 +10:00
aeda4516af add LALT and RALT to keycode map 2025-08-02 06:03:56 +10:00
296f003e58 add qmk keycode detection 2025-08-02 06:01:31 +10:00
5bfe9da28a add getting keycodes from qmk keymap.c file 2025-08-02 04:31:11 +10:00
ea32353769 add basic parsing of qmk json 2025-08-02 02:13:33 +10:00
95f74d10cc replace file path to file name in error messages 2025-08-02 01:28:43 +10:00
3221c26769 add line about wacky layouts to custom layout readme 2025-08-02 00:27:29 +10:00
e0c80fc420 externalize big ass enter shapes 2025-08-02 00:19:47 +10:00
10e4038fea remove extra flags for secondary rect and rotation 2025-08-02 00:14:11 +10:00
6dfeab105e refactor key iteration 2025-08-01 23:59:54 +10:00
14a0f44069 add extra checks for empty arrays 2025-08-01 23:29:45 +10:00
30900fcdce move kle parser to separate class 2025-08-01 23:22:22 +10:00
0f22e17422 set custom layout name from metadata 2025-08-01 21:57:26 +10:00
32de8897c6 rename LayoutKLE to LayoutCustom 2025-08-01 21:38:08 +10:00
084915b877 tweak custom layout readme 2025-08-01 21:36:27 +10:00
4a4eb43bf0 refactor config loading 2025-08-01 20:43:56 +10:00
c11d6f5f76 move test scene to a separate dir 2025-08-01 20:28:22 +10:00
442e5f1201 rename layout class methods and remove static name method 2025-08-01 20:06:50 +10:00
7c58d2994c add readme to custom layout user dir 2025-08-01 19:52:04 +10:00
64ba8b4f80 add support for custom layouts in user directory 2025-08-01 18:39:30 +10:00
e7c695afdc rename CommonLayout to CommonRows 2025-08-01 17:56:22 +10:00
5b111d060f change key height and add homing nubs 2025-08-01 17:50:41 +10:00
75383806f2 rename big enter to big-ass enter (it's the proper name) 2025-08-01 16:52:10 +10:00
1e12e05d0b add page number to key debug display 2025-08-01 16:51:32 +10:00
888861840e add lenovo loq layout 2025-08-01 16:06:14 +10:00
7c33fbc255 move kle layouts to resources directory 2025-08-01 15:51:43 +10:00
ff34f393cd add layout swap list pages 2025-08-01 01:13:07 +10:00
ae15500f76 replace jd40 and planck classes with json 2025-08-01 00:47:51 +10:00
fc5fcd4299 replace prop dict key constants with an enum 2025-08-01 00:31:21 +10:00
cc1d89eb28 add more labels to keycode map 2025-08-01 00:24:52 +10:00
57b990d76e set center label position 2025-08-01 00:24:41 +10:00
e27d943e56 add apple layout 2025-08-01 00:19:47 +10:00
5598a5ac52 simplify rect mesh position calculation 2025-07-31 23:02:11 +10:00
b95a7c2dcc improve layout rect sizing 2025-07-31 22:57:32 +10:00
d35d2dc306 fix key sod starting in center instead of default pos 2025-07-31 22:22:48 +10:00
3f8017a030 rewrite layout size calculation 2025-07-31 22:18:48 +10:00
49c766e6f4 add atreus layout 2025-07-31 21:27:57 +10:00
7b8401fb78 add support for key rotation and refactor key generation 2025-07-31 21:22:42 +10:00
b9392fa7d1 add kinesis advantage layout 2025-07-31 14:41:43 +10:00
66 changed files with 3984 additions and 919 deletions

3
.gitignore vendored
View File

@ -1,7 +1,8 @@
.godot/
/android/
.vscode/
/build/*
/build/
!.gdignore

BIN
assets/models/key.bin (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/models/key.glb (Stored with Git LFS)

Binary file not shown.

277
assets/models/key.gltf Normal file
View File

@ -0,0 +1,277 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.5.47",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Collection",
"nodes":[
0,
6
]
}
],
"nodes":[
{
"mesh":0,
"name":"NubMesh",
"translation":[
0,
0.25,
0.42500001192092896
]
},
{
"name":"TopLeft",
"translation":[
-0.5,
0,
-0.5
]
},
{
"name":"BottomRight",
"translation":[
0.5,
0,
0.5
]
},
{
"name":"TopRight",
"translation":[
0.5,
0,
-0.5
]
},
{
"name":"BottomLeft",
"translation":[
-0.5,
0,
0.5
]
},
{
"mesh":1,
"name":"KeyMesh",
"skin":0
},
{
"children":[
5,
1,
2,
3,
4
],
"name":"KeyArmature"
}
],
"meshes":[
{
"name":"NubMesh",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3
}
]
},
{
"name":"KeyMesh",
"primitives":[
{
"attributes":{
"POSITION":4,
"NORMAL":5,
"TEXCOORD_0":6,
"JOINTS_0":7,
"WEIGHTS_0":8
},
"indices":9
}
]
}
],
"skins":[
{
"inverseBindMatrices":10,
"joints":[
1,
2,
3,
4
],
"name":"KeyArmature"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":25,
"max":[
0.15000000596046448,
0.03999999910593033,
0.02499999664723873
],
"min":[
-0.15000000596046448,
0,
-0.02499999664723873
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":25,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":25,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":90,
"type":"SCALAR"
},
{
"bufferView":4,
"componentType":5126,
"count":115,
"max":[
0.5,
0.2500000596046448,
0.5
],
"min":[
-0.5,
-0.25000008940696716,
-0.5
],
"type":"VEC3"
},
{
"bufferView":5,
"componentType":5126,
"count":115,
"type":"VEC3"
},
{
"bufferView":6,
"componentType":5126,
"count":115,
"type":"VEC2"
},
{
"bufferView":7,
"componentType":5121,
"count":115,
"type":"VEC4"
},
{
"bufferView":8,
"componentType":5126,
"count":115,
"type":"VEC4"
},
{
"bufferView":9,
"componentType":5123,
"count":564,
"type":"SCALAR"
},
{
"bufferView":10,
"componentType":5126,
"count":4,
"type":"MAT4"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":300,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":300,
"byteOffset":300,
"target":34962
},
{
"buffer":0,
"byteLength":200,
"byteOffset":600,
"target":34962
},
{
"buffer":0,
"byteLength":180,
"byteOffset":800,
"target":34963
},
{
"buffer":0,
"byteLength":1380,
"byteOffset":980,
"target":34962
},
{
"buffer":0,
"byteLength":1380,
"byteOffset":2360,
"target":34962
},
{
"buffer":0,
"byteLength":920,
"byteOffset":3740,
"target":34962
},
{
"buffer":0,
"byteLength":460,
"byteOffset":4660,
"target":34962
},
{
"buffer":0,
"byteLength":1840,
"byteOffset":5120,
"target":34962
},
{
"buffer":0,
"byteLength":1128,
"byteOffset":6960,
"target":34963
},
{
"buffer":0,
"byteLength":256,
"byteOffset":8088
}
],
"buffers":[
{
"byteLength":8344,
"uri":"key.bin"
}
]
}

View File

@ -3,13 +3,13 @@
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bmswlpu3ym25i"
path="res://.godot/imported/key.glb-28a29c675eb9833cbdb70f3e33f99c5f.scn"
uid="uid://d4ffyk6iou3ns"
path="res://.godot/imported/key.gltf-680011bf56a1e8dedf6b8edbd0ccfade.scn"
[deps]
source_file="res://assets/models/key.glb"
dest_files=["res://.godot/imported/key.glb-28a29c675eb9833cbdb70f3e33f99c5f.scn"]
source_file="res://assets/models/key.gltf"
dest_files=["res://.godot/imported/key.gltf-680011bf56a1e8dedf6b8edbd0ccfade.scn"]
[params]

5
assets/sfx/THIRDPARTY.md Normal file
View File

@ -0,0 +1,5 @@
The Hollywood Edge - The Super Single Volume 1
- keyboard/typing/*
Zero-G - Datafile 1
- player/move/*

BIN
assets/sfx/player/move/move_1.wav (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://86cbncjjh15f"
path="res://.godot/imported/move_1.wav-56d884599c9cde94cc568eebc198ee3b.sample"
[deps]
source_file="res://assets/sfx/player/move/move_1.wav"
dest_files=["res://.godot/imported/move_1.wav-56d884599c9cde94cc568eebc198ee3b.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=true
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

BIN
assets/sfx/player/move/move_2.wav (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://daqhk1ae8oocx"
path="res://.godot/imported/move_2.wav-9b2ccb06ce79e3adc46b87f25810f7b7.sample"
[deps]
source_file="res://assets/sfx/player/move/move_2.wav"
dest_files=["res://.godot/imported/move_2.wav-9b2ccb06ce79e3adc46b87f25810f7b7.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=true
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

BIN
assets/sfx/player/move/move_3.wav (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://c02pnx1fm8bqa"
path="res://.godot/imported/move_3.wav-9b07a720ee30a308d65b101571150e05.sample"
[deps]
source_file="res://assets/sfx/player/move/move_3.wav"
dest_files=["res://.godot/imported/move_3.wav-9b07a720ee30a308d65b101571150e05.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=true
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

BIN
assets/sfx/player/move/move_4.wav (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dmoypbjc535kp"
path="res://.godot/imported/move_4.wav-2bb98dafa59430d4852b755f935f8454.sample"
[deps]
source_file="res://assets/sfx/player/move/move_4.wav"
dest_files=["res://.godot/imported/move_4.wav-2bb98dafa59430d4852b755f935f8454.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=true
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

View File

@ -1,6 +1,7 @@
[gd_resource type="AudioBusLayout" format=3 uid="uid://dhchfbeo7pxbe"]
[resource]
bus/0/volume_db = -11.952
bus/1/name = &"SFX"
bus/1/solo = false
bus/1/mute = false

View File

@ -7,7 +7,7 @@ advanced_options=true
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
include_filter="resources/kle/README.txt"
exclude_filter=""
export_path="build/windows/keebie.exe"
patches=PackedStringArray()

View File

@ -11,7 +11,7 @@ config_version=5
[application]
config/name="keebie"
config/version="0.0.1"
config/version="0.0.2"
run/main_scene="uid://djmj08xtpgpgk"
config/use_custom_user_dir=true
config/custom_user_dir_name="keebie"
@ -57,6 +57,11 @@ reset_animations={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194340,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
toggle_configuring={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
[physics]

View File

@ -0,0 +1,36 @@
Here you can add custom physical layouts.
Beware that wacky layouts may break the game.
You can use Keyboard Layout Editor to create and edit layouts:
https://www.keyboard-layout-editor.com
Once you have finished making the layout, export it in JSON format (Download > Download JSON)
and put the JSON file in this directory.
Keep in mind that key labels should adhere to the standard US QWERTY layout,
so that the game could correctly infer their keycodes.
Also you can use QMK Firmware keyboard configurations:
https://qmk.fm
You'll need to take the keyboard's info.json (to get the layout) and keymap.c (to get the keycodes),
rename them to have the same file name (e.g. my-keyboard.json and my-keyboard.c)
and put them in this directory.
If your keyboard has already been added to QMK, you can get its configuration here:
https://github.com/qmk/qmk_firmware/tree/master/keyboards
The game will only read the first occurring layout definition and keymap layer,
so you may need to edit them and move the ones you want to be the first.
Also if your keymap.c has language-specific keycode aliases, you will need to replace them
with actual keycodes corresponding to the standard US QWERTY layout,
so that the game could correctly infer them.
If the game doesn't load the layout, check the log file for errors (../logs/godot.log).

View File

@ -1,6 +1,6 @@
[
{
"name": "ANSI 104 (big enter)"
"name": "ANSI 104 (Big-ass Enter)"
},
[
"Esc",
@ -116,9 +116,15 @@
"A",
"S",
"D",
{
"n": true
},
"F",
"G",
"H",
{
"n": true
},
"J",
"K",
"L",

View File

@ -113,9 +113,15 @@
"A",
"S",
"D",
{
"n": true
},
"F",
"G",
"H",
{
"n": true
},
"J",
"K",
"L",

View File

@ -0,0 +1,313 @@
[
{
"backcolor": "#dbdbdb",
"name": "Apple Wireless Keyboard",
"author": "Alistair Calder",
"radii": "6px 6px 12px 12px / 18px 18px 12px 12px",
"css": "@import url(http://fonts.googleapis.com/css?family=Varela+Round);\n\n#keyboard-bg { \n background-image: linear-gradient(to bottom, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 4%, rgba(255,255,255,0.3) 6%, rgba(0,0,0,0) 10%), \n linear-gradient(to right, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0) 100%) !important; \n}\n\n.keylabel {\n font-family: 'volkswagen_serialregular';\n}\n\n/* Strangely, \"Volkswagen Serial\" doesn't have a tilde character */\n.varela { \n font-family: 'Varela Round'; \n display: inline-block; \n font-size: inherit; \n text-rendering: auto; \n -webkit-font-smoothing: antialiased; \n -moz-osx-font-smoothing: grayscale;\n transform: translate(0, 0);\n}\n.varela-tilde:after { content: \"\\07e\"; }"
},
[
{
"y": 0.75,
"t": "#666666",
"p": "CHICKLET",
"a": 7,
"f": 2,
"w": 1.0357,
"h": 0.75
},
"esc",
{
"a": 4,
"fa": [
0,
0,
0,
1
],
"w": 1.0357,
"h": 0.75
},
"\n\n\nF1",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF2",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF3",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF4",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF5",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF6",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF7\n\n\n\n\n\n<i class='fa fa-backward'></i>",
{
"fa": [
0,
0,
0,
1,
0,
0,
0,
0,
0,
1
],
"w": 1.0357,
"h": 0.75
},
"\n\n\nF8\n\n\n\n\n\n<i class='fa fa-play'></i><i class='fa fa-pause'></i>",
{
"fa": [
0,
0,
0,
1
],
"w": 1.0357,
"h": 0.75
},
"\n\n\nF9\n\n\n\n\n\n<i class='fa fa-forward'></i>",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF10\n\n\n\n\n\n<i class='fa fa-volume-off'></i>",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF11\n\n\n\n\n\n<i class='fa fa-volume-down'></i>",
{
"w": 1.0357,
"h": 0.75
},
"\n\n\nF12\n\n\n\n\n\n<i class='fa fa-volume-up'></i>",
{
"a": 7,
"w": 1.0357,
"h": 0.75
},
"<i class='fa fa-eject'></i>"
],
[
{
"y": -0.25,
"a": 5,
"f": 5
},
"<i class=\"varela varela-tilde\"></i>\n`",
"!\n1",
"@\n2",
"#\n3",
"$\n4",
"%\n5",
"^\n6",
"&\n7",
"*\n8",
"(\n9",
")\n0",
"_\n-",
"+\n=",
{
"a": 4,
"f": 2,
"w": 1.5
},
"\n\n\ndelete"
],
[
{
"w": 1.5
},
"\ntab",
{
"a": 7,
"f": 5
},
"Q",
"W",
"E",
"R",
"T",
"Y",
"U",
"I",
"O",
"P",
{
"a": 5
},
"{\n[",
"}\n]",
"|\n\\"
],
[
{
"a": 4,
"f": 2,
"fa": [
1
],
"w": 1.75
},
"<i class='kb kb-Multimedia-Record'></i>\ncaps lock",
{
"a": 7,
"f": 5
},
"A",
"S",
"D",
{
"n": true
},
"F",
"G",
"H",
{
"n": true
},
"J",
"K",
"L",
{
"a": 5
},
":\n;",
"\"\n'",
{
"a": 4,
"f": 2,
"fa": [
0,
0,
1
],
"w": 1.75
},
"\n\nenter\nreturn"
],
[
{
"w": 2.25
},
"\nshift",
{
"a": 7,
"f": 5
},
"Z",
"X",
"C",
"V",
"B",
"N",
"M",
{
"a": 5
},
"<\n,",
">\n.",
"?\n/",
{
"a": 4,
"f": 2,
"w": 2.25
},
"\n\n\nshift"
],
[
{
"h": 1.111
},
"\nfn",
{
"h": 1.111
},
"\ncontrol",
{
"fa": [
1
],
"h": 1.111
},
"alt\noption",
{
"fa": [
1,
0,
5
],
"w": 1.25,
"h": 1.111
},
"\n\n⌘\ncommand",
{
"a": 7,
"w": 5,
"h": 1.111
},
"",
{
"a": 4,
"fa": [
5
],
"w": 1.25,
"h": 1.111
},
"⌘\ncommand",
{
"fa": [
5,
0,
1
],
"h": 1.111
},
"\n\nalt\noption",
{
"x": 1,
"a": 7,
"f": 5,
"h": 0.611
},
"↑"
],
[
{
"y": -0.5,
"x": 11.5,
"h": 0.6111
},
"←",
{
"h": 0.6111
},
"↓",
{
"h": 0.6111
},
"→"
]
]

View File

@ -0,0 +1,276 @@
[
{
"name": "Atreus"
},
[
{
"r": 10,
"rx": 1,
"y": -0.09999999999999998,
"x": 2
},
"E"
],
[
{
"y": -0.65,
"x": 1
},
"W",
{
"x": 1
},
"R"
],
[
{
"y": -0.75
},
"Q"
],
[
{
"y": -0.9,
"x": 4
},
"T"
],
[
{
"y": -0.7000000000000001,
"x": 2
},
"D"
],
[
{
"y": -0.6499999999999999,
"x": 1
},
"S",
{
"x": 1,
"n": true
},
"F"
],
[
{
"y": -0.75
},
"A"
],
[
{
"y": -0.8999999999999999,
"x": 4
},
"G"
],
[
{
"y": -0.7000000000000002,
"x": 2
},
"C"
],
[
{
"y": -0.6499999999999999,
"x": 1
},
"X",
{
"x": 1
},
"V"
],
[
{
"y": -0.75
},
"Z"
],
[
{
"y": -0.8999999999999999,
"x": 4
},
"B"
],
[
{
"y": -0.75,
"x": 5,
"h": 1.5
},
"Ctrl"
],
[
{
"y": -0.9500000000000002,
"x": 2
},
"super"
],
[
{
"y": -0.6499999999999999,
"x": 1
},
"Tab",
{
"x": 1
},
"Shift"
],
[
{
"y": -0.75
},
"Esc"
],
[
{
"y": -0.8999999999999999,
"x": 4
},
"Bksp"
],
[
{
"r": -10,
"rx": 7,
"ry": 0.965,
"y": -0.20000000000000018,
"x": 2
},
"I"
],
[
{
"y": -0.6499999999999999,
"x": 1
},
"U",
{
"x": 1
},
"O"
],
[
{
"y": -0.75,
"x": 4
},
"P"
],
[
{
"y": -0.8999999999999999
},
"Y"
],
[
{
"y": -0.7000000000000002,
"x": 2
},
"K"
],
[
{
"y": -0.6499999999999999,
"x": 1,
"n": true
},
"J",
{
"x": 1
},
"L"
],
[
{
"y": -0.75,
"x": 4
},
":\n;"
],
[
{
"y": -0.8999999999999999
},
"H"
],
[
{
"y": -0.7000000000000002,
"x": 2
},
"<\n,"
],
[
{
"y": -0.6499999999999999,
"x": 1
},
"M",
{
"x": 1
},
">\n."
],
[
{
"y": -0.7500000000000004,
"x": 4
},
"?\n/"
],
[
{
"y": -0.9000000000000004
},
"N"
],
[
{
"y": -0.7499999999999996,
"x": -1,
"h": 1.5
},
"Alt"
],
[
{
"y": -0.9499999999999997,
"x": 2
},
"_\n-"
],
[
{
"y": -0.6500000000000004,
"x": 1
},
"fn",
{
"x": 1
},
"\"\n'"
],
[
{
"y": -0.75,
"x": 4
},
"Enter"
],
[
{
"y": -0.9000000000000004
},
"Space"
]
]

View File

@ -118,9 +118,15 @@
"A",
"S",
"D",
{
"n": true
},
"F",
"G",
"H",
{
"n": true
},
"J",
"K",
"L",

View File

@ -0,0 +1,85 @@
[
{
"name": "JD40"
},
[
"Esc",
"Q",
"W",
"E",
"R",
"T",
"Y",
"U",
"I",
"O",
"P",
"Back<br>Space"
],
[
{
"w": 1.25
},
"Tab",
"A",
"S",
"D",
{
"n": true
},
"F",
"G",
"H",
{
"n": true
},
"J",
"K",
"L",
{
"w": 1.75
},
"Enter"
],
[
{
"w": 1.75
},
"Shift",
"Z",
"X",
"C",
"V",
"B",
"N",
"M",
"<\n.",
{
"w": 1.25
},
"Shift",
"Fn"
],
[
{
"w": 1.25
},
"Hyper",
"Super",
"Meta",
{
"a": 7,
"w": 6.25
},
"",
{
"a": 4,
"w": 1.25
},
"Meta",
{
"w": 1.25
},
"Super"
]
]

View File

@ -86,9 +86,15 @@
"A",
"S",
"D",
{
"n": true
},
"F",
"G",
"H",
{
"n": true
},
"J",
"K",
"L",

View File

@ -0,0 +1,381 @@
[
{
"name": "Kinesis Advantage"
},
[
{
"f": 1,
"f2": 2,
"w": 0.675,
"h": 0.85
},
"\nEsc",
{
"x": 0.07499999999999996,
"w": 0.675,
"h": 0.85
},
"\nF1",
{
"x": 0.07499999999999996,
"w": 0.675,
"h": 0.85
},
"\nF2",
{
"x": 0.07500000000000018,
"w": 0.675,
"h": 0.85
},
"\nF3",
{
"x": 0.07500000000000018,
"w": 0.675,
"h": 0.85
},
"\nF4",
{
"x": 0.07500000000000018,
"w": 0.675,
"h": 0.85
},
"\nF5",
{
"x": 0.07500000000000018,
"w": 0.675,
"h": 0.85
},
"\nF6",
{
"x": 0.07500000000000018,
"w": 0.675,
"h": 0.85
},
"\nF7",
{
"x": 0.07500000000000018,
"w": 0.675,
"h": 0.85
},
"\nF8",
{
"x": 4.825,
"w": 0.675,
"h": 0.85
},
"Repeat Rate\nF9",
{
"x": 0.07499999999999929,
"w": 0.675,
"h": 0.85
},
"Disable Macro\nF10",
{
"x": 0.07499999999999929,
"w": 0.675,
"h": 0.85
},
"Macro\nF11",
{
"x": 0.07499999999999929,
"w": 0.675,
"h": 0.85
},
"Remap\nF12",
{
"x": 0.07499999999999929,
"w": 0.675,
"h": 0.85
},
"PrintScr SysReq",
{
"x": 0.07499999999999929,
"w": 0.675,
"h": 0.85
},
"Scroll<br>lock",
{
"x": 0.07499999999999929,
"w": 0.675,
"h": 0.85
},
"Pause Break",
{
"x": 0.07499999999999929,
"w": 0.675,
"h": 0.85
},
"Keypad",
{
"x": 0.07499999999999929,
"w": 0.675,
"h": 0.85
},
"Progrm"
],
[
{
"x": 2.25,
"f": 3
},
"@\n2",
"#\n3",
"$\n4",
"%\n5",
{
"x": 5.5
},
"^\n6",
"&\n7\n\n\nNm Lk",
"*\n8\n\n\n=",
"(\n9\n\n\n="
],
[
{
"y": -0.75,
"w": 1.25
},
"+\n=",
"!\n1",
{
"x": 13.5
},
")\n0\n\n\n*",
{
"w": 1.25
},
"_\n-"
],
[
{
"y": -0.25,
"x": 2.25,
"f": 6
},
"W",
"E",
"R",
"T",
{
"x": 5.5
},
"Y",
"U\n\n\n\n7",
"I\n\n\n\n8",
"O\n\n\n\n9"
],
[
{
"y": -0.75,
"a": 6,
"f": 3,
"w": 1.25
},
"Tab",
{
"a": 4,
"f": 6
},
"Q",
{
"x": 13.5
},
"P\n\n\n\n-",
{
"f": 3,
"w": 1.25
},
"|\n\\"
],
[
{
"y": -0.25,
"x": 2.25,
"f": 6
},
"S",
"D",
{
"n": true
},
"F",
"G",
{
"x": 5.5
},
"H",
{
"n": true
},
"J\n\n\n\n4",
"K\n\n\n\n5",
"L\n\n\n\n6"
],
[
{
"y": -0.75,
"a": 6,
"f": 3,
"w": 1.25
},
"Caps<br>Lock",
{
"a": 4,
"f": 6
},
"A",
{
"x": 13.5,
"f": 3
},
":\n;\n\n\n+",
{
"w": 1.25
},
"\"\n'"
],
[
{
"y": -0.25,
"x": 2.25,
"f": 6
},
"X",
"C",
"V",
"B",
{
"x": 5.5
},
"N",
"M\n\n\n\n1",
{
"f": 3
},
"<\n,\n\n\n2",
">\n.\n\n\n3"
],
[
{
"y": -0.75,
"a": 6,
"w": 1.25
},
"Shift",
{
"a": 4,
"f": 6
},
"Z",
{
"x": 13.5,
"f": 3
},
"?\n/\n\n\nEnter",
{
"a": 6,
"w": 1.25
},
"Shift"
],
[
{
"y": -0.25,
"x": 2.25,
"a": 4
},
"|\n\\\n\n\nInsert",
{
"a": 5,
"f": 5
},
"⇦\n\n\n\n⇦",
"⇨\n\n\n\n⇨",
{
"x": 7.5
},
"⇧\n\n\n\n⇧",
"⇩\n\n\n\n⇩",
{
"a": 4,
"f": 3
},
"{\n[\n\n\n."
],
[
{
"y": -0.75,
"x": 1.25
},
"~\n`",
{
"x": 13.5
},
"}\n]\n\n\nEnter"
],
[
{
"r": 15,
"rx": 5.25,
"ry": 4,
"x": 1.5
},
"Ctrl",
"Alt"
],
[
{
"x": 0.5,
"a": 7,
"h": 2
},
"Back<br>Space",
{
"h": 2
},
"Delete",
"Home"
],
[
{
"x": 2.5
},
"End"
],
[
{
"r": -15,
"rx": 12.75,
"x": -3.5,
"a": 4
},
"Cmd\n\n\n\n\n\nWin",
{
"a": 7
},
"Ctrl"
],
[
{
"x": -3.5,
"a": 6
},
"Page<br>Up",
{
"a": 7,
"h": 2
},
"Enter",
{
"h": 2
},
"Space"
],
[
{
"x": -3.5,
"a": 6
},
"Page<br>Down"
]
]

View File

@ -0,0 +1,408 @@
[
{
"name": "Lenovo LOQ"
},
[
{
"f2": 1,
"h": 0.666
},
"Esc\nFnLock",
{
"w": 0.8833,
"h": 0.666
},
"F1",
{
"w": 0.8833,
"h": 0.666
},
"F2",
{
"w": 0.8833,
"h": 0.666
},
"F3",
{
"x": 4.440892098500626e-16,
"w": 0.8833,
"h": 0.666
},
"F4",
{
"w": 0.8833,
"h": 0.666
},
"F5",
{
"w": 0.8833,
"h": 0.666
},
"F6",
{
"w": 0.8833,
"h": 0.666
},
"F7",
{
"x": -8.881784197001252e-16,
"w": 0.8833,
"h": 0.666
},
"F8",
{
"w": 0.8833,
"h": 0.666
},
"F9",
{
"w": 0.8833,
"h": 0.666
},
"F10",
{
"w": 0.8833,
"h": 0.666
},
"F11",
{
"w": 0.8833,
"h": 0.666
},
"F12",
{
"w": 0.8833,
"h": 0.666
},
"Insert",
{
"w": 0.8833,
"h": 0.666
},
"PrtSc",
{
"x": -1.7763568394002505e-15,
"h": 0.666
},
"Delete",
{
"w": 0.833,
"h": 0.666
},
"Home",
{
"w": 0.833,
"h": 0.666
},
"End",
{
"w": 0.833,
"h": 0.666
},
"PgUp",
{
"x": 3.552713678800501e-15,
"w": 0.833,
"h": 0.666
},
"PgDn"
],
[
{
"y": -0.33399999999999996,
"f": 3,
"w": 0.833
},
"~\n`",
{
"f": 3
},
"!\n1",
{
"f": 3
},
"@\n2",
{
"f": 3
},
"#\n3",
{
"f": 3
},
"$\n4",
{
"f": 3
},
"%\n5",
{
"f": 3
},
"^\n6",
{
"f": 3
},
"&\n7",
{
"f": 3
},
"*\n8",
{
"f": 3
},
"(\n9",
{
"f": 3
},
")\n0",
{
"f": 3
},
"_\n-",
{
"f": 3
},
"+\n=",
{
"w": 1.5332
},
"Backspace"
],
[
{
"y": -0.9959999999999999,
"x": 14.3662,
"w": 0.833
},
"Num Lock",
{
"w": 0.833
},
"/",
{
"w": 0.833
},
"*",
{
"x": 3.552713678800501e-15,
"w": 0.833
},
"-"
],
[
{
"y": -0.0040000000000000036,
"w": 1.25
},
"Tab"
],
[
{
"y": -0.996,
"x": 1.25
},
"Q",
"W",
"E",
"R",
"T",
"Y",
"U",
"I",
"O",
"P",
{
"f": 3
},
"{\n[",
{
"f": 3
},
"}\n]",
{
"f": 3,
"w": 1.1162
},
"|\n\\",
{
"f": 3,
"w": 0.833
},
"7\nHome",
{
"f": 3,
"w": 0.833
},
"8\n↑",
{
"f": 3,
"w": 0.833
},
"9\nPgUp",
{
"x": 3.552713678800501e-15,
"w": 0.833,
"h": 2
},
"+"
],
[
{
"y": -0.009999999999999787,
"w": 1.5
},
"Caps Lock",
"A",
{
"n": true
},
"S",
"D",
{
"n": true
},
"F",
"G",
"H",
{
"n": true
},
"J",
"K",
"L",
{
"f": 3
},
":\n;",
{
"f": 3
},
"\"\n'",
{
"w": 1.8662
},
"Enter",
{
"f": 3,
"w": 0.833
},
"4\n←",
{
"w": 0.833
},
"5",
{
"f": 3,
"w": 0.833
},
"6\n→"
],
[
{
"y": 0.009999999999999787,
"w": 2
},
"Shift",
"Z",
"X",
"C",
"V",
"B",
"N",
"M",
{
"f": 3
},
"<\n,",
{
"f": 3
},
">\n.",
{
"f": 3,
"w": 1.122
},
"?\n/",
{
"w": 2.244
},
"Shift",
{
"x": 0.00019999999999953388,
"f": 3,
"w": 0.833
},
"1\nEnd",
{
"f": 3,
"w": 0.833
},
"2\n↓",
{
"f": 3,
"w": 0.833
},
"3\nPgDn",
{
"x": 3.552713678800501e-15,
"w": 0.833,
"h": 2
},
"Enter"
],
[
{
"y": -0.0039999999999995595
},
"Ctrl"
],
[
{
"y": -0.9960000000000004,
"x": 1
},
"Fn",
"Win",
"Alt",
{
"a": 7,
"w": 5
},
"",
{
"a": 4
},
"Alt",
"Win",
{
"x": 1.1219999999999999,
"w": 1.122
},
"↑",
{
"x": 1.1221999999999994,
"f": 3,
"w": 1.666
},
"0\nIns",
{
"f": 3,
"w": 0.833
},
".\nDel"
],
[
{
"x": 11,
"w": 1.122
},
"←",
{
"w": 1.122
},
"↓",
{
"w": 1.122
},
"→"
]
]

View File

@ -0,0 +1,72 @@
[
{
"name": "Planck"
},
[
{
"a": 7
},
"Tab",
"Q",
"W",
"E",
"R",
"T",
"Y",
"U",
"I",
"O",
"P",
"Back Space"
],
[
"Esc",
"A",
"S",
"D",
{
"n": true
},
"F",
"G",
"H",
{
"n": true
},
"J",
"K",
"L",
";",
"'"
],
[
"Shift",
"Z",
"X",
"C",
"V",
"B",
"N",
"M",
",",
".",
"/",
"Return"
],
[
"",
"Ctrl",
"Alt",
"Super",
"&dArr;",
{
"w": 2
},
"",
"&uArr;",
"&larr;",
"&darr;",
"&uarr;",
"&rarr;"
]
]

View File

@ -1,5 +1,6 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://dkydluksv2ry0"]
[resource]
resource_local_to_scene = true
albedo_color = Color(0.125911, 0.125911, 0.125911, 1)
roughness = 0.6

View File

@ -22,6 +22,7 @@ _typing_label = NodePath("TypingLabel")
_layout_swap_label = NodePath("LayoutSwapLabel")
[node name="InputLabel" type="RichTextLabel" parent="."]
visible = false
layout_mode = 1
offset_left = 14.0
offset_top = 15.0
@ -56,9 +57,9 @@ anchor_left = 0.5
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -400.0
offset_top = 299.0
offset_top = 341.0
offset_right = 400.0
offset_bottom = -713.0
offset_bottom = -524.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
@ -76,9 +77,9 @@ anchor_left = 0.5
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -400.0
offset_top = 401.0
offset_top = 422.0
offset_right = 400.0
offset_bottom = -616.0
offset_bottom = -421.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2

View File

@ -1,7 +1,6 @@
[gd_scene load_steps=20 format=3 uid="uid://bryima34hc3yp"]
[gd_scene load_steps=21 format=3 uid="uid://bryima34hc3yp"]
[ext_resource type="Script" uid="uid://cl0asbgqd3anu" path="res://scripts/game_key.gd" id="1_sypr4"]
[ext_resource type="PackedScene" uid="uid://bmswlpu3ym25i" path="res://assets/models/key.glb" id="2_6rsff"]
[ext_resource type="AudioStream" uid="uid://bq0ikue882kg6" path="res://assets/sfx/keyboard/typing/press_01.wav" id="2_cqjrw"]
[ext_resource type="AudioStream" uid="uid://dgndsx3o21yss" path="res://assets/sfx/keyboard/typing/press_02.wav" id="3_p6qfn"]
[ext_resource type="AudioStream" uid="uid://1l34knbt0avm" path="res://assets/sfx/keyboard/typing/press_03.wav" id="4_y31qj"]
@ -16,6 +15,8 @@
[ext_resource type="AudioStream" uid="uid://dj7cpa4c8thp6" path="res://assets/sfx/keyboard/typing/release_05.wav" id="13_tj68p"]
[ext_resource type="AudioStream" uid="uid://41d8y11au44e" path="res://assets/sfx/keyboard/typing/release_06.wav" id="14_kt0yx"]
[ext_resource type="AudioStream" uid="uid://8rgf5ic4w1r3" path="res://assets/sfx/keyboard/typing/release_07.wav" id="15_w7o33"]
[ext_resource type="PackedScene" uid="uid://d4ffyk6iou3ns" path="res://assets/models/key.gltf" id="16_cqjrw"]
[ext_resource type="Material" uid="uid://dkydluksv2ry0" path="res://resources/materials/key_mat.tres" id="17_p6qfn"]
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_agrko"]
random_pitch = 1.1
@ -41,39 +42,55 @@ stream_6/stream = ExtResource("15_w7o33")
[sub_resource type="AudioStreamPolyphonic" id="AudioStreamPolyphonic_6rsff"]
[node name="GameKey" type="Node3D" node_paths=PackedStringArray("_skeleton_primary", "_skeleton_secondary", "_upper_left_label", "_upper_right_label", "_lower_left_label", "_lower_right_label", "_center_label", "_press_light", "_sfx_player")]
[node name="GameKey" type="Node3D" node_paths=PackedStringArray("player_pos_marker", "_skeleton_primary", "_skeleton_secondary", "_nub_mesh", "_upper_left_label", "_upper_right_label", "_lower_left_label", "_lower_right_label", "_center_label", "_press_light", "_adjacency_light", "_sfx_player")]
script = ExtResource("1_sypr4")
_skeleton_primary = NodePath("ModelPrimary/KeyArmature/Skeleton3D")
player_pos_marker = NodePath("PlayerPosMarker")
_skeleton_primary = NodePath("Model/KeyArmature/Skeleton3D")
_skeleton_secondary = NodePath("ModelSecondary/KeyArmature/Skeleton3D")
_upper_left_label = NodePath("UpperLeftAttachment/UpperLeftLabel")
_upper_right_label = NodePath("UpperRightAttachment/UpperRightLabel")
_lower_left_label = NodePath("LowerLeftAttachment/LowerLeftLabel")
_lower_right_label = NodePath("LowerRightAttachment/LowerRightLabel")
_center_label = NodePath("CenterLabel")
_press_light = NodePath("PressLight")
_nub_mesh = NodePath("Model/NubMesh")
_upper_left_label = NodePath("Labels/UpperLeftAttachment/UpperLeftLabel")
_upper_right_label = NodePath("Labels/UpperRightAttachment/UpperRightLabel")
_lower_left_label = NodePath("Labels/LowerLeftAttachment/LowerLeftLabel")
_lower_right_label = NodePath("Labels/LowerRightAttachment/LowerRightLabel")
_center_label = NodePath("Labels/CenterLabel")
_press_light = NodePath("Effects/PressLight")
_adjacency_light = NodePath("Effects/AdjacencyLight")
_sfx_player = NodePath("SFXPlayer")
_press_sfx = SubResource("AudioStreamRandomizer_agrko")
_release_sfx = SubResource("AudioStreamRandomizer_ch32x")
[node name="ModelPrimary" parent="." instance=ExtResource("2_6rsff")]
[node name="Model" parent="." instance=ExtResource("16_cqjrw")]
[node name="KeyMesh" parent="ModelPrimary/KeyArmature/Skeleton3D" index="0"]
gi_mode = 2
[node name="NubMesh" parent="Model" index="0"]
visible = false
surface_material_override/0 = ExtResource("17_p6qfn")
[node name="ModelSecondary" parent="." instance=ExtResource("2_6rsff")]
[node name="KeyMesh" parent="Model/KeyArmature/Skeleton3D" index="0"]
surface_material_override/0 = ExtResource("17_p6qfn")
[node name="ModelSecondary" parent="." instance=ExtResource("16_cqjrw")]
[node name="NubMesh" parent="ModelSecondary" index="0"]
visible = false
surface_material_override/0 = ExtResource("17_p6qfn")
[node name="Skeleton3D" parent="ModelSecondary/KeyArmature" index="0"]
visible = false
[node name="KeyMesh" parent="ModelSecondary/KeyArmature/Skeleton3D" index="0"]
gi_mode = 2
surface_material_override/0 = ExtResource("17_p6qfn")
[node name="UpperLeftAttachment" type="BoneAttachment3D" parent="."]
[node name="Labels" type="Node3D" parent="."]
[node name="UpperLeftAttachment" type="BoneAttachment3D" parent="Labels"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0, -0.5)
bone_name = "TopLeft"
bone_idx = 0
use_external_skeleton = true
external_skeleton = NodePath("../ModelPrimary/KeyArmature/Skeleton3D")
external_skeleton = NodePath("../../Model/KeyArmature/Skeleton3D")
[node name="UpperLeftLabel" type="Label3D" parent="UpperLeftAttachment"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.256, 0.306, 0.264)
[node name="UpperLeftLabel" type="Label3D" parent="Labels/UpperLeftAttachment"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.256, 0.251, 0.264)
pixel_size = 0.006
shaded = true
double_sided = false
@ -84,15 +101,15 @@ text = "Q"
font_size = 64
outline_size = 0
[node name="UpperRightAttachment" type="BoneAttachment3D" parent="."]
[node name="UpperRightAttachment" type="BoneAttachment3D" parent="Labels"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, -0.5)
bone_name = "TopRight"
bone_idx = 2
use_external_skeleton = true
external_skeleton = NodePath("../ModelPrimary/KeyArmature/Skeleton3D")
external_skeleton = NodePath("../../Model/KeyArmature/Skeleton3D")
[node name="UpperRightLabel" type="Label3D" parent="UpperRightAttachment"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.256, 0.306, 0.264)
[node name="UpperRightLabel" type="Label3D" parent="Labels/UpperRightAttachment"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.256, 0.251, 0.264)
pixel_size = 0.006
shaded = true
double_sided = false
@ -103,15 +120,15 @@ text = "Ё"
font_size = 64
outline_size = 0
[node name="LowerLeftAttachment" type="BoneAttachment3D" parent="."]
[node name="LowerLeftAttachment" type="BoneAttachment3D" parent="Labels"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0, 0.5)
bone_name = "BottomLeft"
bone_idx = 3
use_external_skeleton = true
external_skeleton = NodePath("../ModelPrimary/KeyArmature/Skeleton3D")
external_skeleton = NodePath("../../Model/KeyArmature/Skeleton3D")
[node name="LowerLeftLabel" type="Label3D" parent="LowerLeftAttachment"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.256, 0.306, -0.264)
[node name="LowerLeftLabel" type="Label3D" parent="Labels/LowerLeftAttachment"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.256, 0.251, -0.264)
pixel_size = 0.006
shaded = true
double_sided = false
@ -122,15 +139,15 @@ text = "Д"
font_size = 64
outline_size = 0
[node name="LowerRightAttachment" type="BoneAttachment3D" parent="."]
[node name="LowerRightAttachment" type="BoneAttachment3D" parent="Labels"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 0.5)
bone_name = "BottomRight"
bone_idx = 1
use_external_skeleton = true
external_skeleton = NodePath("../ModelPrimary/KeyArmature/Skeleton3D")
external_skeleton = NodePath("../../Model/KeyArmature/Skeleton3D")
[node name="LowerRightLabel" type="Label3D" parent="LowerRightAttachment"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.256, 0.306, -0.264)
[node name="LowerRightLabel" type="Label3D" parent="Labels/LowerRightAttachment"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.256, 0.251, -0.264)
pixel_size = 0.006
shaded = true
double_sided = false
@ -141,8 +158,8 @@ text = ","
font_size = 64
outline_size = 0
[node name="CenterLabel" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.305619, 0)
[node name="CenterLabel" type="Label3D" parent="Labels"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.251, 0)
pixel_size = 0.006
shaded = true
double_sided = false
@ -153,18 +170,30 @@ text = "A"
font_size = 48
outline_size = 0
[node name="PressLight" type="OmniLight3D" parent="."]
[node name="Effects" type="Node3D" parent="."]
[node name="PressLight" type="OmniLight3D" parent="Effects"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
visible = false
light_color = Color(1, 0.687333, 0.33, 1)
light_energy = 6.0
omni_range = 2.0
[node name="AdjacencyLight" type="OmniLight3D" parent="Effects"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.45, 0)
visible = false
light_color = Color(0.8325, 1, 0.33, 1)
omni_range = 0.6
[node name="PlayerPosMarker" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.25, 0)
gizmo_extents = 0.2
[node name="SFXPlayer" type="AudioStreamPlayer3D" parent="."]
stream = SubResource("AudioStreamPolyphonic_6rsff")
volume_db = -15.0
autoplay = true
attenuation_model = 3
bus = &"SFX"
attenuation_filter_cutoff_hz = 20500.0
[editable path="ModelPrimary"]
[editable path="Model"]
[editable path="ModelSecondary"]

View File

@ -1,6 +1,7 @@
[gd_scene load_steps=5 format=3 uid="uid://dl5jgx2ucmvk7"]
[gd_scene load_steps=8 format=3 uid="uid://dl5jgx2ucmvk7"]
[ext_resource type="Script" uid="uid://c7ikemicshkhv" path="res://scripts/game_keyboard.gd" id="1_3k4ps"]
[ext_resource type="PackedScene" uid="uid://bryima34hc3yp" path="res://scenes/game_key.tscn" id="2_hylia"]
[ext_resource type="AudioStream" uid="uid://db7pm7d6amnpk" path="res://assets/sfx/keyboard/swap.wav" id="2_j5t22"]
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_hylia"]
@ -10,17 +11,33 @@ stream_0/stream = ExtResource("2_j5t22")
[sub_resource type="AudioStreamPolyphonic" id="AudioStreamPolyphonic_3k4ps"]
[node name="GameKeyboard" type="Node3D" node_paths=PackedStringArray("_keys_holder", "_sfx_player")]
[sub_resource type="BoxMesh" id="BoxMesh_j5t22"]
size = Vector3(0.09, 2.075, 0.09)
[sub_resource type="QuadMesh" id="QuadMesh_j5t22"]
orientation = 1
[node name="GameKeyboard" type="Node3D" node_paths=PackedStringArray("_keys_holder", "_sfx_player", "_rect_mesh")]
script = ExtResource("1_3k4ps")
_keys_holder = NodePath("Keys")
_sfx_player = NodePath("SFXPlayer")
_rect_mesh = NodePath("RectMesh")
_key_scene = ExtResource("2_hylia")
_layout_swap_sfx = SubResource("AudioStreamRandomizer_hylia")
[node name="Keys" type="Node3D" parent="."]
[node name="SFXPlayer" type="AudioStreamPlayer3D" parent="."]
stream = SubResource("AudioStreamPolyphonic_3k4ps")
volume_db = -15.0
autoplay = true
attenuation_model = 3
bus = &"SFX"
attenuation_filter_cutoff_hz = 20500.0
[node name="CenterMesh" type="MeshInstance3D" parent="."]
visible = false
mesh = SubResource("BoxMesh_j5t22")
[node name="RectMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(16.4, 0, 0, 0, 1, 0, 0, 0, 5.4, 0, -0.968737, 0)
visible = false
mesh = SubResource("QuadMesh_j5t22")

43
scenes/player.tscn Normal file
View File

@ -0,0 +1,43 @@
[gd_scene load_steps=10 format=3 uid="uid://b8gu0udt0ow8t"]
[ext_resource type="Script" uid="uid://dmfdlb4ae57qv" path="res://scripts/player.gd" id="1_3vyb7"]
[ext_resource type="AudioStream" uid="uid://86cbncjjh15f" path="res://assets/sfx/player/move/move_1.wav" id="2_qhqgy"]
[ext_resource type="AudioStream" uid="uid://daqhk1ae8oocx" path="res://assets/sfx/player/move/move_2.wav" id="3_dqkch"]
[ext_resource type="AudioStream" uid="uid://c02pnx1fm8bqa" path="res://assets/sfx/player/move/move_3.wav" id="4_qlg0r"]
[ext_resource type="AudioStream" uid="uid://dmoypbjc535kp" path="res://assets/sfx/player/move/move_4.wav" id="5_tuyoq"]
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_qlg0r"]
random_pitch = 1.15
streams_count = 4
stream_0/stream = ExtResource("2_qhqgy")
stream_1/stream = ExtResource("3_dqkch")
stream_2/stream = ExtResource("4_qlg0r")
stream_3/stream = ExtResource("5_tuyoq")
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3vyb7"]
metallic_specular = 0.0
[sub_resource type="BoxMesh" id="BoxMesh_u8vuu"]
material = SubResource("StandardMaterial3D_3vyb7")
size = Vector3(0.6, 0.6, 0.6)
[sub_resource type="AudioStreamPolyphonic" id="AudioStreamPolyphonic_3vyb7"]
[node name="Player" type="Node3D" node_paths=PackedStringArray("_sfx_player")]
script = ExtResource("1_3vyb7")
_sfx_player = NodePath("SFXPlayer")
_hop_sfx = SubResource("AudioStreamRandomizer_qlg0r")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0)
mesh = SubResource("BoxMesh_u8vuu")
[node name="MeshInstance3D2" type="MeshInstance3D" parent="."]
transform = Transform3D(0.310767, 0, 0, 0, 0.310767, 0, 0, 0, 0.310767, 0, 0.394854, 0.356113)
mesh = SubResource("BoxMesh_u8vuu")
[node name="SFXPlayer" type="AudioStreamPlayer3D" parent="."]
stream = SubResource("AudioStreamPolyphonic_3vyb7")
attenuation_model = 3
bus = &"SFX"
attenuation_filter_cutoff_hz = 20500.0

View File

@ -1,7 +1,8 @@
[gd_scene load_steps=6 format=3 uid="uid://djmj08xtpgpgk"]
[gd_scene load_steps=7 format=3 uid="uid://djmj08xtpgpgk"]
[ext_resource type="PackedScene" uid="uid://dl5jgx2ucmvk7" path="res://scenes/game_keyboard.tscn" id="1_errlg"]
[ext_resource type="PackedScene" uid="uid://df16rl6w7vvw" path="res://scenes/debug_key_display.tscn" id="2_bl13t"]
[ext_resource type="PackedScene" uid="uid://dl5jgx2ucmvk7" path="res://scenes/game_keyboard.tscn" id="1_da6vm"]
[ext_resource type="PackedScene" uid="uid://b8gu0udt0ow8t" path="res://scenes/player.tscn" id="2_51e2l"]
[ext_resource type="PackedScene" uid="uid://df16rl6w7vvw" path="res://scenes/debug_key_display.tscn" id="2_st4qh"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_errlg"]
albedo_color = Color(0.2, 0.2, 0.2, 1)
@ -22,13 +23,14 @@ glow_strength = 1.5
[node name="Test" type="Node3D"]
[node name="OrthogonalCamera" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 19.632, -0.848)
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 19.632, 0)
projection = 1
size = 11.58
size = 14.532
[node name="PerspectiveCamera" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.48725, 0.873262, 0, -0.873262, 0.48725, 0, 11.172, 3.456)
transform = Transform3D(1, 0, 0, 0, 0.642787, 0.766044, 0, -0.766044, 0.642787, 0, 12.5, 8)
current = true
fov = 60.0
[node name="FloorMesh" type="MeshInstance3D" parent="."]
visible = false
@ -40,8 +42,10 @@ transform = Transform3D(0.312269, -0.815913, 0.486594, -2.95249e-09, 0.512208, 0
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_ij1v8")
[node name="GameKeyboard" parent="." instance=ExtResource("1_errlg")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0721761, 0)
[node name="GameKeyboard" parent="." instance=ExtResource("1_da6vm")]
[node name="DebugKeyDisplay" parent="." node_paths=PackedStringArray("_keyboard") instance=ExtResource("2_bl13t")]
[node name="Player" parent="." node_paths=PackedStringArray("_keyboard") instance=ExtResource("2_51e2l")]
_keyboard = NodePath("../GameKeyboard")
[node name="DebugKeyDisplay" parent="." node_paths=PackedStringArray("_keyboard") instance=ExtResource("2_st4qh")]
_keyboard = NodePath("../GameKeyboard")

View File

@ -9,13 +9,19 @@ extends Control
func _ready() -> void:
_set_layout_label(LayoutConfig.current_layout)
_set_layout_swap_label()
_set_layout_swap_label(_keyboard.prompt_page)
_input_label.text = "awaiting input..."
_typing_label.text = ""
_keyboard.key_press_changed.connect(_on_keyboard_key_press_changed)
_keyboard.key_input_event_emitted.connect(_on_keyboard_key_input_event_emitted)
_keyboard.prompt_page_turned.connect(_on_keyboard_prompt_page_turned)
LayoutConfig.layout_swapped.connect(_set_layout_label)
func _process(_delta: float) -> void:
_layout_label.visible = _keyboard.is_configuring
_layout_swap_label.visible = _keyboard.is_configuring
func _unhandled_input(event: InputEvent) -> void:
if event is not InputEventKey or not event.is_pressed():
return
@ -35,7 +41,9 @@ func _unhandled_input(event: InputEvent) -> void:
text += "as_text_location: %s\n" % event_key.as_text_location()
text += "as_text_physical_keycode: %s\n" % event_key.as_text_physical_keycode()
text += "\n"
text += "get_key_label_with_modifiers: %s\n" % event_key.get_key_label_with_modifiers()
text += (
"get_key_label_with_modifiers: %s\n" % event_key.get_key_label_with_modifiers()
)
text += "get_keycode_with_modifiers: %s\n" % event_key.get_keycode_with_modifiers()
text += (
"get_physical_keycode_with_modifiers: %s\n"
@ -46,20 +54,24 @@ func _unhandled_input(event: InputEvent) -> void:
func _set_layout_label(layout: AbstractLayout) -> void:
_layout_label.text = "physical layout: %s" % layout.name()
_layout_label.text = "physical layout: %s" % layout.get_name()
func _set_layout_swap_label() -> void:
var text := ""
func _set_layout_swap_label(page: int) -> void:
var text := (
"page %s/%s Kp 0: next page >>>\n"
% [_keyboard.prompt_page + 1, _keyboard.prompt_pages_total]
)
var layouts := LayoutConfig.layouts.values() as Array[AbstractLayout]
for i in layouts.size():
var key := (KEY_KP_1 + i) as Key
var key: int = KEY_KP_1
for i in range(page * 9, mini(layouts.size(), page * 9 + 9)):
var input_event := InputEventKey.new()
input_event.physical_keycode = key
input_event.physical_keycode = key as Key
text += (
"%s: %s\n" % [input_event.as_text_physical_keycode(), layouts[i].name()]
"%s: %s\n" % [input_event.as_text_physical_keycode(), layouts[i].get_name()]
)
key += 1
_layout_swap_label.text = text
@ -75,13 +87,19 @@ func _erase_typing_label() -> void:
_typing_label.text = _typing_label.text.substr(0, _typing_label.text.length() - 1)
func _on_keyboard_key_press_changed(game_key: GameKey, event: InputEventKey) -> void:
func _on_keyboard_key_input_event_emitted(
game_key: GameKey, event: InputEventKey
) -> void:
if not event.is_pressed():
return
if game_key.props.is_char():
if game_key.props.is_unicode():
_set_typing_label(char(event.unicode))
elif game_key.props.physical_keycode == KEY_SPACE:
elif game_key.props.keycode == KEY_SPACE:
_set_typing_label(" ")
elif game_key.props.physical_keycode == KEY_BACKSPACE:
elif game_key.props.keycode == KEY_BACKSPACE:
_erase_typing_label()
func _on_keyboard_prompt_page_turned(page: int) -> void:
_set_layout_swap_label(page)

View File

@ -1,17 +1,20 @@
@tool
class_name GameKey extends Node3D
#region variables
static var _scene := preload("res://scenes/game_key.tscn")
@export_group("References")
@export var player_pos_marker: Node3D
@export_group("Node references")
@export var _skeleton_primary: Skeleton3D
@export var _skeleton_secondary: Skeleton3D
@export var _nub_mesh: MeshInstance3D
@export var _upper_left_label: Label3D
@export var _upper_right_label: Label3D
@export var _lower_left_label: Label3D
@export var _lower_right_label: Label3D
@export var _center_label: Label3D
@export var _press_light: OmniLight3D
@export var _adjacency_light: OmniLight3D
@export var _sfx_player: AudioStreamPlayer3D
@export_group("Bones")
@ -24,51 +27,62 @@ static var _scene := preload("res://scenes/game_key.tscn")
@export_group("Animation")
@export var _pos_sod_fzr: Vector3 = Vector3(5, 0.15, 0)
@export var _light_fade_time: float = 0.25
@export var _star_pop_offset: Vector3 = Vector3(0, -2, 0)
@export var _light_fade_duration: float = 0.25
@export var _starting_pop_offset: Vector3 = Vector3(0, -2.5, 0)
@export var _rotation_damping: float = 10
@export_subgroup("Idle")
@export var _idle_amplitude: float = 0.4
@export var _idle_frequency := Vector2(0.25, -0.25)
@export var _press_offset: float = 0.4
@export_subgroup("Player")
@export var _hop_land_offset: float = 0.8
@export_group("SFX")
@export var _press_sfx: AudioStream
@export var _release_sfx: AudioStream
var props: KeyProps
var keyboard: GameKeyboard
var player_pos_marker_transform: Transform3D
var _keyboard: GameKeyboard
var _is_pressed: bool
var _light_timer: float
var _default_position: Vector3
var _init_pos: Vector3
var _default_pos: Vector3
var _default_rot: Vector3
var _pos_sod: SecondOrderDynamics
@onready
var _polyphonic := _sfx_player.get_stream_playback() as AudioStreamPlaybackPolyphonic
var _polyphonic: AudioStreamPlaybackPolyphonic
@onready var _light_energy := _press_light.light_energy
#endregion
#region static methods
#region static
static func instantiate_with_props(
_props: KeyProps, default_position: Vector3, _keyboard: GameKeyboard
_props: KeyProps,
init_pos: Vector3,
angle: float,
keyboard: GameKeyboard,
scene: PackedScene
) -> GameKey:
var node := _scene.instantiate() as GameKey
node.keyboard = _keyboard
var game_key := scene.instantiate() as GameKey
game_key._keyboard = keyboard
game_key.load_props(_props, init_pos, angle)
if _props.physical_keycode != KEY_SPECIAL:
node.name = node.name + " " + OS.get_keycode_string(_props.physical_keycode)
if _props.location != KEY_LOCATION_UNSPECIFIED:
node.name += (
" " + ("Left" if _props.location == KEY_LOCATION_LEFT else "Right")
)
if _props.keycode != KEY_NONE:
game_key.name += " " + OS.get_keycode_string(_props.keycode)
if _props.location != KEY_LOCATION_UNSPECIFIED:
game_key.name += (
" " + ("Left" if _props.location == KEY_LOCATION_LEFT else "Right")
)
node.load_props(_props, default_position)
return node
return game_key
#endregion
@ -76,50 +90,72 @@ static func instantiate_with_props(
#region builtins
func _ready() -> void:
if Engine.is_editor_hint() and not _keyboard:
return
_adjacency_light.visible = false
_keyboard.layout_changed.connect(_on_keyboard_layout_changed)
_keyboard.keys_queried.connect(_on_keyboard_keys_queried)
_keyboard.configuring_changed.connect(_on_keyboard_configuring_changed)
_keyboard.player_current_key_changed.connect(
_on_keyboard_player_current_key_changed
)
_keyboard.player_finished_move.connect(_on_keyboard_player_finished_move)
_set_labels()
_reset_animations()
if Engine.is_editor_hint():
return
_sfx_player.play()
_polyphonic = _sfx_player.get_stream_playback()
func _process(delta: float) -> void:
if Engine.is_editor_hint():
return
_animate(delta)
_animate_light(delta)
func _unhandled_input(event: InputEvent) -> void:
if Engine.is_editor_hint():
return
if event.is_action_pressed("reset_animations"):
_reset_animations()
return
if event is not InputEventKey:
return
var event_key := event as InputEventKey
if (
event_key.physical_keycode != props.physical_keycode
or event_key.location != props.location
event_key.physical_keycode != props.keycode
or event_key.echo
or event_key.physical_keycode == KEY_NONE
or props.keycode == KEY_NONE
or (
event_key.location != KEY_LOCATION_UNSPECIFIED
and props.location != KEY_LOCATION_UNSPECIFIED
and event_key.location != props.location
)
):
return
_set_pressing_from_event(event_key)
_set_pressing(event_key.is_pressed())
_keyboard.emit_key_input_event_emitted(self, event_key)
if event_key.echo:
return
if LayoutConfig.is_configuring and event_key.is_pressed():
var new_char_type: KeyProps.Char
if not keyboard.alt_visual_layout:
if _keyboard.is_configuring and event_key.is_pressed():
var new_char_type: String
if not _keyboard.alt_visual_layout:
if not event_key.shift_pressed:
new_char_type = KeyProps.Char.MAIN
new_char_type = KeyProps.CHAR_MAIN
else:
new_char_type = KeyProps.Char.SHIFT
new_char_type = KeyProps.CHAR_SHIFT
else:
if not event_key.shift_pressed:
new_char_type = KeyProps.Char.ALT
new_char_type = KeyProps.CHAR_ALT
else:
new_char_type = KeyProps.Char.ALT_SHIFT
new_char_type = KeyProps.CHAR_ALT_SHIFT
var chars_dict: Dictionary[KeyProps.Char, String] = {
var chars_dict: Dictionary[String, String] = {
new_char_type: char(event_key.unicode).to_upper()
}
@ -127,60 +163,66 @@ func _unhandled_input(event: InputEvent) -> void:
func _exit_tree() -> void:
_set_keyboard_pressed_position(false)
if _keyboard:
_erase_keyboard_pressed_position()
#endregion
#region public
func load_props(_props: KeyProps, default_position: Vector3) -> void:
_set_keyboard_pressed_position(false)
_default_position = default_position
if _is_pressed:
_set_keyboard_pressed_position(true)
func load_props(_props: KeyProps, init_pos: Vector3, angle: float) -> void:
_init_pos = init_pos
_default_rot.y = angle
if props:
_props.chars_from_dict(props.chars_to_dict())
props = _props
_skeleton_primary.visible = true
_skeleton_secondary.visible = props.secondary_rect
_skeleton_secondary.visible = props.has_secondary_rect()
_set_bones()
_nub_mesh.visible = props.homing_nub
func get_default_transform() -> Transform3D:
return Transform3D(Quaternion.from_euler(_default_rot), _default_pos)
func is_adjacent(to: GameKey) -> bool:
return (
KeyHelper.ADJACENCY_MAP.has(props.keycode)
and (to.props.keycode in KeyHelper.ADJACENCY_MAP[props.keycode])
)
#endregion
#region pressing
func _set_pressing_from_event(event: InputEventKey) -> void:
_set_pressing(event.is_pressed())
keyboard.emit_key_press(self, event)
func _set_pressing(is_pressed: bool) -> void:
if _is_pressed == is_pressed:
return
_is_pressed = is_pressed
_set_keyboard_pressed_position(_is_pressed)
if _is_pressed:
_play_sfx(_press_sfx)
else:
_play_sfx(_release_sfx)
_keyboard.emit_key_pressed(self)
_play_sfx(_press_sfx if _is_pressed else _release_sfx)
func _set_keyboard_pressed_position(pressed: bool) -> void:
if pressed:
keyboard.pressed_positions[_default_position] = pressed
else:
keyboard.pressed_positions.erase(_default_position)
_keyboard.pressed_positions[_default_pos] = pressed
func _erase_keyboard_pressed_position() -> void:
_keyboard.pressed_positions.erase(_default_pos)
#endregion
#region labels
func _set_labels(chars_dict: Dictionary[KeyProps.Char, String] = {}) -> void:
func _set_labels(chars_dict: Dictionary[String, String] = {}) -> void:
_upper_left_label.text = ""
_upper_right_label.text = ""
_lower_left_label.text = ""
@ -196,12 +238,9 @@ func _set_labels(chars_dict: Dictionary[KeyProps.Char, String] = {}) -> void:
_center_label.visible = _center_label.text != ""
func _set_labels_text(chars_dict: Dictionary[KeyProps.Char, String]) -> void:
if props.physical_keycode == KEY_SPECIAL:
return
if not props.is_char():
_center_label.text = OS.get_keycode_string(props.physical_keycode)
func _set_labels_text(chars_dict: Dictionary[String, String]) -> void:
if Engine.is_editor_hint() or not props.is_unicode():
_center_label.text = OS.get_keycode_string(props.keycode)
return
if chars_dict:
@ -230,20 +269,24 @@ func _set_labels_text(chars_dict: Dictionary[KeyProps.Char, String]) -> void:
#region bones
func _set_bones() -> void:
var rect := Rect2(
-(keyboard.key_size * props.width) / 2,
-keyboard.key_size / 2,
keyboard.key_size * props.width,
keyboard.key_size * props.height
-(_keyboard.key_size * props.width) / 2,
-_keyboard.key_size / 2,
_keyboard.key_size * props.width,
_keyboard.key_size * props.height
)
var rect_center := rect.get_center()
_center_label.position.x = rect_center.x
_center_label.position.z = rect_center.y
_set_bones_from_rect(rect, _skeleton_primary)
if props.secondary_rect:
if props.has_secondary_rect():
var rect_secondary := Rect2(
rect.position.x + props.x2 * keyboard.key_size,
rect.position.y + props.y2 * keyboard.key_size,
keyboard.key_size * props.width2,
keyboard.key_size * props.height2
rect.position.x + props.x2 * _keyboard.key_size,
rect.position.y + props.y2 * _keyboard.key_size,
_keyboard.key_size * props.width2,
_keyboard.key_size * props.height2
)
_set_bones_from_rect(rect_secondary, _skeleton_secondary)
@ -277,44 +320,70 @@ func _set_bones_from_rect(rect: Rect2, skeleton: Skeleton3D) -> void:
#region animation
func _reset_animations() -> void:
_pos_sod = SecondOrderDynamics.new(
_pos_sod_fzr, _default_position + _star_pop_offset
_pos_sod_fzr, _default_pos + _starting_pop_offset
)
func _animate(delta: float) -> void:
position = _pos_sod.process(delta, _animate_position())
func _animate_position() -> Vector3:
var new_position := _default_position
new_position.y += (
position.y += (
sin(
(
keyboard.anim_time
+ (_default_position.x / keyboard.key_size) * _idle_frequency.x
+ (_default_position.z / keyboard.key_size) * _idle_frequency.y
_keyboard.anim_time
+ (_default_pos.x / _keyboard.key_size) * _idle_frequency.x
+ (_default_pos.z / _keyboard.key_size) * _idle_frequency.y
)
)
* _idle_amplitude
* keyboard.key_size
* _keyboard.key_size
)
new_position.y -= (_press_offset * keyboard.key_size) if _is_pressed else 0.0
rotation = lerp(rotation, _default_rot, delta * _rotation_damping)
func _animate_position() -> Vector3:
var new_position := _default_pos
new_position.y -= (_press_offset * _keyboard.key_size) if _is_pressed else 0.0
return new_position
func _anim_shake(force: float) -> void:
_pos_sod.y = (
_pos_sod.y
+ (
Vector3(
randf_range(-force, force),
randf_range(-force, force),
randf_range(-force, force)
)
* _keyboard.key_size
)
)
func _anim_push(force: Vector3) -> void:
_pos_sod.y = (_pos_sod.y + force * _keyboard.key_size)
func _anim_push_radial(force: float) -> void:
_pos_sod.y = (Vector3(
_pos_sod.y.x * (force + 1), _pos_sod.y.y, _pos_sod.y.z * (force + 1)
))
func _animate_light(delta: float) -> void:
if _is_pressed:
_light_timer = _light_fade_time
_light_timer = _light_fade_duration
if _light_timer <= 0:
_press_light.visible = false
return
_press_light.visible = true
_press_light.light_energy = (_light_timer / _light_fade_time) * _light_energy
_press_light.light_energy = (_light_timer / _light_fade_duration) * _light_energy
_light_timer -= delta
@ -325,4 +394,46 @@ func _animate_light(delta: float) -> void:
func _play_sfx(stream: AudioStream) -> void:
_polyphonic.play_stream(stream)
#endregion
#region event handlers
func _on_keyboard_layout_changed(rect: Rect2) -> void:
_erase_keyboard_pressed_position()
var center := rect.get_center()
_default_pos = _init_pos
_default_pos.x -= center.x
_default_pos.z -= center.y
_set_keyboard_pressed_position(_is_pressed)
if Engine.is_editor_hint():
position = _default_pos
return
if not _pos_sod:
_reset_animations()
func _on_keyboard_keys_queried(query_func: Callable) -> void:
if query_func.call(self):
_keyboard.key_query_respond(self)
func _on_keyboard_configuring_changed(is_configuring: bool) -> void:
if is_configuring:
_anim_push_radial(0.2)
else:
_anim_shake(0.2)
_anim_push_radial(-0.05)
func _on_keyboard_player_current_key_changed(game_key: GameKey) -> void:
_adjacency_light.visible = is_adjacent(game_key)
func _on_keyboard_player_finished_move(game_key: GameKey) -> void:
if game_key == self:
_anim_push(Vector3.DOWN * _hop_land_offset)
#endregion

View File

@ -1,79 +1,113 @@
@tool
class_name GameKeyboard extends Node3D
signal key_press_changed(game_key: GameKey, event: InputEventKey)
#region variables
@export_group("Node references")
signal key_pressed(game_key: GameKey)
signal key_input_event_emitted(game_key: GameKey, event: InputEventKey)
signal layout_changed(rect: Rect2)
signal prompt_page_turned(page: int)
signal keys_queried(query_func: Callable)
signal configuring_changed(is_configuring: bool)
signal player_current_key_changed(game_key: GameKey)
signal player_finished_move(game_key: GameKey)
@export_tool_button("Generate keys")
var generate_editor_keys_btn := _generate_editor_keys
@export_tool_button("Delete keys") var delete_editor_keys_btn := _delete_editor_keys
@export_tool_button("Request keys") var query_keys_btn := _query_editor_keys
@export_group("References")
@export var _keys_holder: Node3D
@export var _sfx_player: AudioStreamPlayer3D
@export var _rect_mesh: MeshInstance3D
@export var _key_scene: PackedScene
@export_group("Key params")
@export_group("Key parameters")
@export var key_size: float = 1
@export var key_gap: float = 0.1
@export_group("Animation")
@export var _rot_sod_fzr: Vector3 = Vector3(3, 0.1, 2)
@export var _time_scale: float = 0.5
@export var _anim_time_scale: float = 0.5
@export var _pressing_lean_deg := Vector2(0.15, 0.7)
@export_group("SFX")
@export var _layout_swap_sfx: AudioStream
var is_configuring: bool
var alt_visual_layout: bool
var layout_rect: Rect2
var pressed_positions: Dictionary[Vector3, bool]
var anim_time: float
var prompt_page: int = 0
var prompt_pages_total: int = 0
var _rot_sod: SecondOrderDynamics
var _queried_keys: Array[GameKey] = []
var _queried_keys_limit: int = 0
var _polyphonic: AudioStreamPlaybackPolyphonic
@onready var _gap_to_size_ratio: float
@onready var _pressing_lean_rad := Vector2(
deg_to_rad(_pressing_lean_deg.x), deg_to_rad(_pressing_lean_deg.y)
)
@onready var _default_rotation: Vector3 = rotation
@onready
var _polyphonic := _sfx_player.get_stream_playback() as AudioStreamPlaybackPolyphonic
@onready var _default_rot: Vector3 = _keys_holder.rotation
#endregion
#region builtins
func _ready() -> void:
_generate_keys(LayoutConfig.layout_rows)
if Engine.is_editor_hint():
_generate_editor_keys()
return
prompt_pages_total = ceili(LayoutConfig.layouts.size() / 9.0)
_generate_game_keys()
_reset_animations()
_sfx_player.play()
_polyphonic = _sfx_player.get_stream_playback()
func _process(delta: float) -> void:
if Engine.is_editor_hint():
return
_animate(delta)
func _unhandled_input(event: InputEvent) -> void:
if Engine.is_editor_hint():
return
if event.is_action_pressed("reset_animations"):
_reset_animations()
return
if event.is_action_pressed("toggle_configuring"):
is_configuring = not is_configuring
print("now configuring: %s" % is_configuring)
configuring_changed.emit(is_configuring)
return
if event is not InputEventKey:
return
var event_key := event as InputEventKey
if event_key.echo:
if event_key.echo or not event_key.is_pressed():
return
if (
event_key.is_pressed()
and LayoutConfig.is_configuring
is_configuring
and event_key.physical_keycode >= KEY_KP_0
and event_key.physical_keycode <= KEY_KP_9
):
_swap_layout(event_key.physical_keycode)
_change_layout(event_key.physical_keycode)
return
var keycode := event_key.get_physical_keycode_with_modifiers()
if (
event_key.is_pressed()
and (keycode == KEY_SHIFT | KEY_MASK_ALT or keycode == KEY_ALT | KEY_MASK_SHIFT)
):
if keycode == KEY_SHIFT | KEY_MASK_ALT or keycode == KEY_ALT | KEY_MASK_SHIFT:
alt_visual_layout = not alt_visual_layout
print("changed visual layout! " + str(alt_visual_layout))
@ -82,54 +116,99 @@ func _unhandled_input(event: InputEvent) -> void:
#region public
func emit_key_press(game_key: GameKey, event: InputEventKey) -> void:
key_press_changed.emit(game_key, event)
func query_keys(query_func: Callable, limit: int = 0) -> Array[GameKey]:
_queried_keys = []
_queried_keys_limit = limit
keys_queried.emit(query_func)
var queried_keys := _queried_keys
_queried_keys = []
_queried_keys_limit = 0
queried_keys.sort_custom(KeyHelper.game_key_sort)
return queried_keys
func query_key_by_keycode(keycode: Key) -> GameKey:
var found_key: GameKey
var result := query_keys(
func(game_key: GameKey) -> bool: return game_key.props.keycode == keycode, 1
)
if result:
found_key = result[0]
return found_key
func query_keys_by_keycodes(keycodes: Array[Key]) -> Array[GameKey]:
return query_keys(
func(game_key: GameKey) -> bool: return game_key.props.keycode in keycodes
)
func key_query_respond(game_key: GameKey) -> void:
if _queried_keys_limit == 0 or _queried_keys.size() < _queried_keys_limit:
_queried_keys.append(game_key)
func emit_key_pressed(game_key: GameKey) -> void:
key_pressed.emit(game_key)
func emit_key_input_event_emitted(game_key: GameKey, event: InputEventKey) -> void:
key_input_event_emitted.emit(game_key, event)
func emit_player_current_key_changed(game_key: GameKey) -> void:
player_current_key_changed.emit(game_key)
func emit_player_finished_move(game_key: GameKey) -> void:
player_finished_move.emit(game_key)
#endregion
#region key generation
func _generate_keys(layout_rows: Array[Array]) -> void:
print("generating keys... '%s'" % LayoutConfig.current_layout.name())
_set_gap_to_size_ratio()
for row: Array[KeyProps] in layout_rows:
_set_row_key_scales_with_gaps(row)
var rows_amount := layout_rows.size()
var row_width := _get_row_width(layout_rows)
var row_offset_z: float = 0
for i in range(rows_amount):
var row := layout_rows[i] as Array[KeyProps]
row_offset_z = _generate_row(row, i, row_width, rows_amount, row_offset_z)
func _generate_game_keys() -> void:
print("generating keys...")
_iterate_key_props_rows(_generate_game_key, LayoutConfig.layout_key_props_rows)
func _generate_row(
row: Array[KeyProps],
row_index: int,
row_width: float,
rows_amount: int,
row_offset_z: float
) -> float:
var key_pos := _get_row_key_starting_pos(
row_index, rows_amount, row_width, row_offset_z
func _generate_game_key(
key_props: KeyProps,
key_pos: Vector3,
angle: float,
_current_keys: Dictionary[Vector2i, Array]
) -> void:
var game_key := GameKey.instantiate_with_props(
key_props, key_pos, angle, self, _key_scene
)
var key_pos_init := key_pos
for key_props in row:
var key_default_pos := _get_row_key_pos_with_offset(key_pos, key_props)
var game_key := GameKey.instantiate_with_props(key_props, key_default_pos, self)
_keys_holder.add_child(game_key, true)
key_pos += _get_row_key_step(key_props)
return row_offset_z + key_pos_init.z - key_pos.z
_keys_holder.add_child(game_key, true)
func _set_row_key_scales_with_gaps(row: Array[KeyProps]) -> void:
for key_props in row:
func _iterate_key_props_rows(
iter_func: Callable,
key_props_rows: Array[Array],
current_keys: Dictionary[Vector2i, Array] = {}
) -> void:
_gap_to_size_ratio = key_gap / key_size
for key_props_row: Array[KeyProps] in key_props_rows:
_set_row_key_scales_with_gaps(key_props_row)
var rect := KeyHelper.iterate_key_props_rows(
iter_func, key_props_rows, key_size, key_gap, current_keys
)
layout_changed.emit(rect)
_rect_mesh.scale = Vector3(rect.size.x, 1, rect.size.y)
_rect_mesh.position.x = rect.position.x + rect.size.x / 2 - rect.get_center().x
_rect_mesh.position.z = rect.position.x + rect.size.y / 2 - rect.get_center().y
func _set_row_key_scales_with_gaps(key_props_row: Array[KeyProps]) -> void:
for key_props in key_props_row:
key_props.width = _get_scale_with_gaps(key_props.width)
key_props.height = _get_scale_with_gaps(key_props.height)
key_props.x = _get_scale_with_gaps(key_props.x) + _gap_to_size_ratio
@ -138,154 +217,99 @@ func _set_row_key_scales_with_gaps(row: Array[KeyProps]) -> void:
key_props.height2 = _get_scale_with_gaps(key_props.height2)
key_props.x2 = _get_scale_with_gaps(key_props.x2) + _gap_to_size_ratio
key_props.y2 = _get_scale_with_gaps(key_props.y2) + _gap_to_size_ratio
if key_props.has_pivot_x():
key_props.pivot_x = (
_get_scale_with_gaps(key_props.pivot_x) + _gap_to_size_ratio
)
if key_props.has_pivot_y():
key_props.pivot_y = (
_get_scale_with_gaps(key_props.pivot_y) + _gap_to_size_ratio
)
func _get_scale_with_gaps(key_scale: float) -> float:
return key_scale + _gap_to_size_ratio * (key_scale - 1)
func _get_row_width(layout_rows: Array[Array]) -> float:
var max_width: float = 0
for row: Array[KeyProps] in layout_rows:
var width: float = 0
for key_props in row:
width += key_size * key_props.width
var width_with_gaps := width + (row.size() - 1) * key_gap
max_width = maxf(max_width, width_with_gaps)
return max_width
func _get_row_key_starting_pos(
row_index: int, rows_amount: int, row_width: float, row_offset_z: float
) -> Vector3:
var offset_z: float = (
((rows_amount - 1) * key_size + (rows_amount - 1) * key_gap) / 2 + row_offset_z
)
var key_pos_x: float = -row_width / 2 + key_size / 2
var key_pos_z: float = row_index * key_size + row_index * key_gap - offset_z
return Vector3(key_pos_x, 0, key_pos_z)
func _get_row_key_pos_with_offset(key_pos: Vector3, key_props: KeyProps) -> Vector3:
key_pos.x += (key_size * key_props.width - key_size) / 2 + key_size * key_props.x
key_pos.z += key_size * key_props.y
return key_pos
func _get_row_key_step(key_props: KeyProps) -> Vector3:
var step_x := key_size * key_props.width + key_gap + key_size * key_props.x
var step_z := key_size * key_props.y
return Vector3(step_x, 0, step_z)
func _set_gap_to_size_ratio() -> void:
_gap_to_size_ratio = key_gap / key_size
#endregion
#region layout swapping
func _swap_layout(kp_key: Key) -> void:
func _change_layout(kp_key: Key) -> void:
if kp_key == KEY_KP_0:
prompt_page = wrapi(prompt_page + 1, 0, prompt_pages_total)
prompt_page_turned.emit(prompt_page)
return
var index := kp_key - KEY_KP_1
if index < 0 or index >= LayoutConfig.layouts.size():
var layout_idx := kp_key - KEY_KP_1 + prompt_page * 9
if layout_idx < 0 or layout_idx >= LayoutConfig.layouts.size():
return
var new_layout := (
(LayoutConfig.layouts.values() as Array[AbstractLayout])[index].name()
var new_layout_name := (
(LayoutConfig.layouts.values() as Array[AbstractLayout])[layout_idx].get_name()
)
if new_layout == LayoutConfig.current_layout.name():
if new_layout_name == LayoutConfig.current_layout.get_name():
return
LayoutConfig.swap_layout(new_layout)
_regenerate_keys(LayoutConfig.layout_rows)
LayoutConfig.change_layout(new_layout_name)
_regenerate_keys(LayoutConfig.layout_key_props_rows)
_play_sfx(_layout_swap_sfx)
func _regenerate_keys(layout_rows: Array[Array]) -> void:
print("REgenerating keys... '%s'" % LayoutConfig.current_layout.name())
func _regenerate_keys(key_props_rows: Array[Array]) -> void:
print("REgenerating keys...")
var current_keys: Dictionary[Vector2i, Array]
for node in _keys_holder.get_children():
if node is not GameKey:
continue
var game_key := node as GameKey
var key_props := game_key.props
var dict_key := Vector2i(key_props.physical_keycode, key_props.location)
var dict_key := Vector2i(key_props.keycode, key_props.location)
if current_keys.has(dict_key):
current_keys[dict_key].append(game_key)
else:
current_keys[dict_key] = [game_key] as Array[GameKey]
_set_gap_to_size_ratio()
for row: Array[KeyProps] in layout_rows:
_set_row_key_scales_with_gaps(row)
var rows_amount := layout_rows.size()
var row_width := _get_row_width(layout_rows)
var row_offset_z: float = 0
for i in range(rows_amount):
var row := layout_rows[i] as Array[KeyProps]
row_offset_z = _regenerate_row(
row, i, row_width, rows_amount, row_offset_z, current_keys
)
_iterate_key_props_rows(_regenerate_key, key_props_rows, current_keys)
for game_keys: Array[GameKey] in current_keys.values():
for game_key in game_keys:
game_key.queue_free()
func _regenerate_row(
row: Array[KeyProps],
row_index: int,
row_width: float,
rows_amount: int,
row_offset_z: float,
current_keys: Dictionary[Vector2i, Array],
) -> float:
var key_pos := _get_row_key_starting_pos(
row_index, rows_amount, row_width, row_offset_z
)
var key_pos_init := key_pos
func _regenerate_key(
key_props: KeyProps,
key_pos: Vector3,
angle: float,
current_keys: Dictionary[Vector2i, Array]
) -> void:
var dict_key := Vector2i(key_props.keycode, key_props.location)
for key_props in row:
var key_default_pos := _get_row_key_pos_with_offset(key_pos, key_props)
var dict_key := Vector2i(key_props.physical_keycode, key_props.location)
if current_keys.has(dict_key):
var game_keys := current_keys[dict_key] as Array[GameKey]
var game_key_idx: int
var min_dist: float = 0
for i in range(game_keys.size()):
var dist := game_keys[i].position.distance_to(key_pos)
if min_dist == 0 or dist <= min_dist:
min_dist = dist
game_key_idx = i
game_keys[game_key_idx].load_props(key_props, key_pos, angle)
if current_keys.has(dict_key):
var game_keys := current_keys[dict_key] as Array[GameKey]
var game_key_idx: int
var min_dist: float = 0
for i in range(game_keys.size()):
var dist := game_keys[i].position.distance_to(key_default_pos)
if min_dist == 0 or dist <= min_dist:
min_dist = dist
game_key_idx = i
game_keys[game_key_idx].load_props(key_props, key_default_pos)
game_keys.remove_at(game_key_idx)
if game_keys.size() == 0:
current_keys.erase(dict_key)
else:
var props_dict := LayoutConfig.get_key_config_dict(
key_props.physical_keycode
)
if props_dict:
key_props.chars_from_dict(props_dict)
game_keys.remove_at(game_key_idx)
if game_keys.size() == 0:
current_keys.erase(dict_key)
else:
var chars_dict := LayoutConfig.get_key_config_dict(key_props.keycode)
if chars_dict and chars_dict is Dictionary:
key_props.chars_from_dict(chars_dict)
var game_key := GameKey.instantiate_with_props(
key_props, key_default_pos, self
)
_keys_holder.add_child(game_key, true)
key_pos += _get_row_key_step(key_props)
return row_offset_z + key_pos_init.z - key_pos.z
var game_key := GameKey.instantiate_with_props(
key_props, key_pos, angle, self, _key_scene
)
_keys_holder.add_child(game_key, true)
#endregion
@ -294,16 +318,16 @@ func _regenerate_row(
#region animation
func _reset_animations() -> void:
anim_time = 0
_rot_sod = SecondOrderDynamics.new(_rot_sod_fzr, _default_rotation)
_rot_sod = SecondOrderDynamics.new(_rot_sod_fzr, _default_rot)
func _animate(delta: float) -> void:
anim_time += delta * _time_scale
rotation = _rot_sod.process(delta, _animate_rotation())
anim_time += delta * _anim_time_scale
_keys_holder.rotation = _rot_sod.process(delta, _animate_rotation())
func _animate_rotation() -> Vector3:
var new_rotation := _default_rotation
var new_rotation := _default_rot
for pos: Vector3 in pressed_positions.keys():
var pos_pressed := pressed_positions[pos]
if not pos_pressed:
@ -320,4 +344,37 @@ func _animate_rotation() -> Vector3:
#region sounds
func _play_sfx(stream: AudioStream) -> void:
_polyphonic.play_stream(stream)
#endregion
#region prompt
func _set_prompt_pages() -> void:
prompt_page = 0
prompt_pages_total = ceili(LayoutConfig.layouts.size() / 9.0)
#endregion
#region editor
func _generate_editor_keys() -> void:
_delete_editor_keys()
_iterate_key_props_rows(_generate_game_key, LayoutANSI.new().get_key_props_rows())
func _delete_editor_keys() -> void:
pressed_positions = {}
var nodes := _keys_holder.get_children()
if not nodes:
return
print("deleting keys...")
for node in nodes:
node.queue_free()
func _query_editor_keys() -> void:
print(query_key_by_keycode(KEY_SHIFT))
#endregion

View File

@ -3,40 +3,32 @@ extends Node
signal layout_swapped(new_layout: AbstractLayout)
const CONFIG_PATH := "user://layout.cfg"
const CUSTOM_LAYOUTS_PATH := "user://custom_layouts"
const CUSTOM_LAYOUTS_README := "res://resources/kle/README.txt"
const SECTION_INFO := "info"
const SECTION_KEYS := "keys"
const PARAM_NAME := "name"
var current_layout: AbstractLayout
var layout_rows: Array[Array]
var is_configuring: bool = false
var layout_key_props_rows: Array[Array]
var layouts: Dictionary[String, AbstractLayout] = {
LayoutANSI.name_static(): LayoutANSI.new(),
LayoutANSIVariant.name_static(): LayoutANSIVariant.new(),
LayoutISO.name_static(): LayoutISO.new(),
LayoutABNT.name_static(): LayoutABNT.new(),
LayoutJIS.name_static(): LayoutJIS.new(),
# LayoutJD40.name_static(): LayoutJD40.new(),
# LayoutPlanck.name_static(): LayoutPlanck.new(),
"ansi-104.json": LayoutKLE.new("res://scripts/layouts/kle/ansi-104.json"),
"ansi-104-big-enter.json":
LayoutKLE.new("res://scripts/layouts/kle/ansi-104-big-enter.json"),
"iso-105.json": LayoutKLE.new("res://scripts/layouts/kle/iso-105.json"),
"keycool-84.json": LayoutKLE.new("res://scripts/layouts/kle/keycool-84.json"),
# LayoutTest.name_static(): LayoutTest.new(),
LayoutANSI.name: LayoutANSI.new(),
LayoutANSIVariant.name: LayoutANSIVariant.new(),
LayoutISO.name: LayoutISO.new(),
LayoutABNT.name: LayoutABNT.new(),
LayoutJIS.name: LayoutJIS.new(),
# LayoutTest.name: LayoutTest.new(),
}
var _config := ConfigFile.new()
func _ready() -> void:
_load_custom_layouts(CUSTOM_LAYOUTS_PATH)
var err := _load_config()
if err != OK:
print("layout config not loaded.....")
if err:
_set_defaults()
# _set_defaults()
is_configuring = true
func _unhandled_input(event: InputEvent) -> void:
@ -45,101 +37,141 @@ func _unhandled_input(event: InputEvent) -> void:
print("layout config saved!")
func get_key_config_dict(keycode: Key) -> Dictionary[KeyProps.Char, String]:
var param_key := str(keycode)
if not _config.has_section_key(SECTION_KEYS, param_key):
func get_key_config_dict(keycode: Key) -> Dictionary:
var config_key := OS.get_keycode_string(keycode)
if not _config.has_section_key(SECTION_KEYS, config_key):
return {}
return (
_config.get_value(SECTION_KEYS, param_key, {})
as Dictionary[KeyProps.Char, String]
)
return _config.get_value(SECTION_KEYS, config_key, {}) as Dictionary
func swap_layout(layout_name: String) -> void:
func change_layout(layout_name: String) -> void:
if layouts.has(layout_name):
_set_layout(layouts[layout_name])
layout_swapped.emit(current_layout)
else:
printerr("no such layout! ", layout_name)
push_error("layout '%s' not found" % layout_name)
func _set_defaults() -> void:
print("setting defaults")
print("\nsetting defaults")
_set_layout(layouts.values()[0] as AbstractLayout)
# _set_layout(layouts[LayoutANSI.name_static()])
func _set_layout(layout: AbstractLayout) -> void:
print("SETTING LAYOUT: '%s'" % layout.name())
print("SETTING LAYOUT: '%s'" % layout.get_name())
current_layout = layout
layout_rows = []
for dict_row: Array[Dictionary] in current_layout.rows():
var row: Array[KeyProps] = []
for props_dict: Dictionary in dict_row:
row.append(KeyProps.new().props_from_dict(props_dict))
layout_rows.append(row)
layout_key_props_rows = current_layout.get_key_props_rows()
func _load_config() -> Error:
print("loading layout config....")
print("\nloading layout config....")
var err := _config.load(CONFIG_PATH)
if err == ERR_FILE_NOT_FOUND:
print("layout config file not found")
return FAILED
if err != OK:
printerr("failed to load layout config file: '%s'" % err)
return FAILED
if err:
push_error("failed to load layout config file: '%s'" % error_string(err))
print()
return err
var layout_name := ""
if _config.has_section_key(SECTION_INFO, PARAM_NAME):
var layout_name := _config.get_value(SECTION_INFO, PARAM_NAME, "") as String
if layouts.has(layout_name):
_set_layout(layouts[layout_name])
else:
printerr("layout '%s' not found" % layout_name)
_set_defaults()
var config_layout_name: Variant = _config.get_value(SECTION_INFO, PARAM_NAME)
if config_layout_name is String:
layout_name = config_layout_name as String
else:
printerr("layout config is missing '%s/%s'" % [SECTION_INFO, PARAM_NAME])
push_error("layout config is missing '%s/%s'" % [SECTION_INFO, PARAM_NAME])
if layout_name != "" and layouts.has(layout_name):
_set_layout(layouts[layout_name])
else:
push_error("layout '%s' not found" % layout_name)
_set_defaults()
for row: Array[KeyProps] in layout_rows:
for key_props in row:
var param_key := str(key_props.physical_keycode)
for key_props_row: Array[KeyProps] in layout_key_props_rows:
for key_props in key_props_row:
var config_key := OS.get_keycode_string(key_props.keycode)
if (
not key_props.is_char()
or not _config.has_section_key(SECTION_KEYS, param_key)
not key_props.is_unicode()
or not _config.has_section_key(SECTION_KEYS, config_key)
):
continue
var dictionary := (
_config.get_value(SECTION_KEYS, param_key, {}) as Dictionary
)
key_props.chars_from_dict(dictionary)
var chars_dict: Variant = _config.get_value(SECTION_KEYS, config_key)
if chars_dict is not Dictionary:
continue
key_props.chars_from_dict(chars_dict as Dictionary)
print("layout config loaded!")
print("layout config loaded!\n")
return OK
func _save_config() -> Error:
_config.set_value(SECTION_INFO, PARAM_NAME, current_layout.name())
_config.set_value(SECTION_INFO, PARAM_NAME, current_layout.get_name())
for row: Array[KeyProps] in layout_rows:
for key_props in row:
if not key_props.is_char():
for key_props_row: Array[KeyProps] in layout_key_props_rows:
for key_props in key_props_row:
if not key_props.is_unicode():
continue
_config.set_value(
SECTION_KEYS, str(key_props.physical_keycode), key_props.chars_to_dict()
SECTION_KEYS,
OS.get_keycode_string(key_props.keycode),
key_props.chars_to_dict()
)
var err := _config.save(CONFIG_PATH)
if err != OK:
printerr("failed to save config file: '%s'" % err)
if err:
push_error("failed to save config file: '%s'" % error_string(err))
return err
return OK
func _load_custom_layouts(path: String) -> void:
if not DirAccess.dir_exists_absolute(path):
var dir_err := DirAccess.make_dir_absolute(path)
if dir_err:
push_error(
"error creating directory '%s': %s" % [path, error_string(dir_err)]
)
print()
return
var file_err := DirAccess.copy_absolute(
CUSTOM_LAYOUTS_README, path.path_join(CUSTOM_LAYOUTS_README.get_file())
)
if file_err:
push_error(
(
"error creating file '%s': %s"
% [CUSTOM_LAYOUTS_README, error_string(file_err)]
)
)
var dir := DirAccess.open(path)
if not dir:
var err := DirAccess.get_open_error()
push_error("error opening directory '%s': %s" % [path, error_string(err)])
print()
return
var file_names := dir.get_files()
if not file_names:
return
print("loading custom layouts..\n")
for file_name in file_names:
if file_name.get_extension().to_lower() != "json":
continue
var layout := LayoutCustom.new(path.path_join(file_name))
if layout.failed:
continue
if layouts.has(layout.get_name()):
push_warning(
(
"layout '%s' already exists and will be replaced by the new one"
% [layout.get_name()]
)
)
layouts[layout.get_name()] = layout

View File

@ -1,24 +1,28 @@
class_name KeyProps
@tool
class_name KeyProps extends Resource
enum Char { MAIN, SHIFT, ALT, ALT_SHIFT }
const CHAR_MAIN = "char_main"
const CHAR_SHIFT = "char_shift"
const CHAR_ALT = "char_alt"
const CHAR_ALT_SHIFT = "char_alt_shift"
const KEY := "key"
const LOC := "location"
const KEY = "key"
const LOC = "loc"
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 PX = "rx"
const PY = "ry"
const NUB = "nub"
const W := "w"
const H := "h"
const X := "x"
const Y := "y"
const SECONDARY_RECT := "secondary_rect"
const W2 := "w2"
const H2 := "h2"
const X2 := "x2"
const Y2 := "y2"
var physical_keycode: Key
var keycode: Key = KEY_NONE
var location: KeyLocation = KEY_LOCATION_UNSPECIFIED
var secondary_rect: bool = false
var width: float = 1
var height: float = 1
@ -26,25 +30,49 @@ var x: float = 0
var y: float = 0
var width2: float = 1
var width_init2: float = 1
var height2: float = 1
var height_init2: float = 1
var x2: float = 0
var y2: float = 0
var angle: float = -INF
var pivot_x: float = -INF
var pivot_y: float = -INF
var homing_nub: bool = false
var main_char: String
var shift_char: String
var alt_char: String
var alt_shift_char: String
func is_char() -> bool:
return OS.is_keycode_unicode(physical_keycode)
func is_unicode() -> bool:
return keycode != KEY_SPACE and OS.is_keycode_unicode(keycode)
func is_player_pos_key() -> bool:
return KeyHelper.ADJACENCY_MAP.has(keycode)
func has_secondary_rect() -> bool:
return width2 != 1 or height2 != 1 or x2 != 0 or y2 != 0
func has_angle() -> bool:
return angle != -INF
func has_pivot_x() -> bool:
return pivot_x != -INF
func has_pivot_y() -> bool:
return pivot_y != -INF
func props_from_dict(dict: Dictionary) -> KeyProps:
if dict.has(KEY):
physical_keycode = dict[KEY]
keycode = dict[KEY]
if dict.has(LOC):
location = dict[LOC]
@ -57,31 +85,37 @@ func props_from_dict(dict: Dictionary) -> KeyProps:
if dict.has(Y):
y = dict[Y]
if dict.has(SECONDARY_RECT):
secondary_rect = dict[SECONDARY_RECT]
if dict.has(W2):
width2 = dict[W2]
width_init2 = dict[W2]
if dict.has(H2):
height2 = dict[H2]
height_init2 = dict[H2]
if dict.has(X2):
x2 = dict[X2]
if dict.has(Y2):
y2 = dict[Y2]
if dict.has(R):
angle = dict[R]
if dict.has(PX):
pivot_x = dict[PX]
if dict.has(PY):
pivot_y = dict[PY]
if dict.has(NUB):
homing_nub = dict[NUB]
return self
func chars_from_dict(dict: Dictionary[Char, String], override: bool = true) -> KeyProps:
if dict.has(Char.MAIN) and (override or not main_char):
main_char = dict[Char.MAIN]
if dict.has(Char.SHIFT) and (override or not shift_char):
shift_char = dict[Char.SHIFT]
if dict.has(Char.ALT) and (override or not alt_char):
alt_char = dict[Char.ALT]
if dict.has(Char.ALT_SHIFT) and (override or not alt_shift_char):
alt_shift_char = dict[Char.ALT_SHIFT]
func chars_from_dict(dict: Dictionary, override: bool = true) -> KeyProps:
if dict.has(CHAR_MAIN) and (override or not main_char):
main_char = dict[CHAR_MAIN]
if dict.has(CHAR_SHIFT) and (override or not shift_char):
shift_char = dict[CHAR_SHIFT]
if dict.has(CHAR_ALT) and (override or not alt_char):
alt_char = dict[CHAR_ALT]
if dict.has(CHAR_ALT_SHIFT) and (override or not alt_shift_char):
alt_shift_char = dict[CHAR_ALT_SHIFT]
if main_char == shift_char:
shift_char = ""
@ -93,15 +127,15 @@ func chars_from_dict(dict: Dictionary[Char, String], override: bool = true) -> K
return self
func chars_to_dict() -> Dictionary[Char, String]:
var dict: Dictionary[Char, String] = {}
func chars_to_dict() -> Dictionary:
var dict: Dictionary = {}
if main_char:
dict[Char.MAIN] = main_char
dict[CHAR_MAIN] = main_char
if shift_char:
dict[Char.SHIFT] = shift_char
dict[CHAR_SHIFT] = shift_char
if alt_char:
dict[Char.ALT] = alt_char
dict[CHAR_ALT] = alt_char
if alt_shift_char:
dict[Char.ALT_SHIFT] = alt_shift_char
dict[CHAR_ALT_SHIFT] = alt_shift_char
return dict

View File

@ -1,13 +1,19 @@
class_name AbstractLayout
static func name_static() -> String:
func get_name() -> String:
return ""
func name() -> String:
return name_static()
func rows() -> Array[Array]:
func get_key_dict_rows() -> Array[Array]:
return []
func get_key_props_rows() -> Array[Array]:
var rows: Array[Array] = []
for dict_row: Array[Dictionary] in get_key_dict_rows():
var row: Array[KeyProps] = []
for props_dict: Dictionary in dict_row:
row.append(KeyProps.new().props_from_dict(props_dict))
rows.append(row)
return rows

View File

@ -0,0 +1,13 @@
class_name AbstractParser
func get_name() -> String:
return ""
func get_rows() -> Array[Array]:
return []
func has_errors() -> bool:
return false

View File

@ -0,0 +1 @@
uid://ce0ominvxrn7v

View File

@ -1,4 +1,26 @@
class_name CommonLayout
class_name CommonKeys
static func big_enter_iso() -> Dictionary:
return {
KeyProps.KEY: KEY_ENTER,
KeyProps.W: 1.25,
KeyProps.H: 2,
KeyProps.X: 0.25,
KeyProps.W2: 1.5,
KeyProps.X2: -0.25,
}
static func big_enter_ansi() -> Dictionary:
return {
KeyProps.KEY: KEY_ENTER,
KeyProps.W: 1.5,
KeyProps.H: 2,
KeyProps.W2: 2.25,
KeyProps.Y2: 1,
KeyProps.X2: -0.75,
}
static func numbers() -> Array:
@ -51,10 +73,10 @@ static func letters_2() -> Array:
{KeyProps.KEY: KEY_A},
{KeyProps.KEY: KEY_S},
{KeyProps.KEY: KEY_D},
{KeyProps.KEY: KEY_F},
{KeyProps.KEY: KEY_F, KeyProps.NUB: true},
{KeyProps.KEY: KEY_G},
{KeyProps.KEY: KEY_H},
{KeyProps.KEY: KEY_J},
{KeyProps.KEY: KEY_J, KeyProps.NUB: true},
{KeyProps.KEY: KEY_K},
{KeyProps.KEY: KEY_L},
]

View File

@ -1,31 +1,23 @@
class_name LayoutABNT extends AbstractLayout
static func name_static() -> String:
return "ABNT"
static var name := "ABNT"
func rows() -> Array[Array]:
func get_name() -> String:
return name
func get_key_dict_rows() -> Array[Array]:
return [
CommonLayout.number_row() + [{KeyProps.KEY: KEY_BACKSPACE, KeyProps.W: 2}],
CommonKeys.number_row() + [{KeyProps.KEY: KEY_BACKSPACE, KeyProps.W: 2}],
(
[{KeyProps.KEY: KEY_TAB, KeyProps.W: 1.5}]
+ CommonLayout.letters_row_1()
+ [
{
KeyProps.KEY: KEY_ENTER,
KeyProps.W: 1.25,
KeyProps.H: 2,
KeyProps.X: 0.25,
KeyProps.SECONDARY_RECT: true,
KeyProps.W2: 1.5,
KeyProps.X2: -0.25,
}
]
+ CommonKeys.letters_row_1()
+ [CommonKeys.big_enter_iso()]
),
(
[{KeyProps.KEY: KEY_CAPSLOCK, KeyProps.W: 1.75}]
+ CommonLayout.letters_row_2()
+ CommonKeys.letters_row_2()
+ [{KeyProps.KEY: KEY_BACKSLASH}]
),
(
@ -37,9 +29,9 @@ func rows() -> Array[Array]:
},
{KeyProps.KEY: KEY_SECTION},
]
+ CommonLayout.letters_row_3()
+ CommonKeys.letters_row_3()
+ [
{KeyProps.KEY: KEY_UNKNOWN},
{KeyProps.KEY: KEY_NONE},
{
KeyProps.KEY: KEY_SHIFT,
KeyProps.W: 1.75,
@ -47,5 +39,5 @@ func rows() -> Array[Array]:
}
]
),
CommonLayout.bottom_row()
CommonKeys.bottom_row()
]

View File

@ -1,21 +1,23 @@
class_name LayoutANSI extends AbstractLayout
static func name_static() -> String:
return "ANSI"
static var name := "ANSI"
func rows() -> Array[Array]:
func get_name() -> String:
return name
func get_key_dict_rows() -> Array[Array]:
return [
CommonLayout.number_row() + [{KeyProps.KEY: KEY_BACKSPACE, KeyProps.W: 2}],
CommonKeys.number_row() + [{KeyProps.KEY: KEY_BACKSPACE, KeyProps.W: 2}],
(
[{KeyProps.KEY: KEY_TAB, KeyProps.W: 1.5}]
+ CommonLayout.letters_row_1()
+ CommonKeys.letters_row_1()
+ [{KeyProps.KEY: KEY_BACKSLASH, KeyProps.W: 1.5}]
),
(
[{KeyProps.KEY: KEY_CAPSLOCK, KeyProps.W: 1.75}]
+ CommonLayout.letters_row_2()
+ CommonKeys.letters_row_2()
+ [{KeyProps.KEY: KEY_ENTER, KeyProps.W: 2.25}]
),
(
@ -26,7 +28,7 @@ func rows() -> Array[Array]:
KeyProps.LOC: KEY_LOCATION_LEFT
}
]
+ CommonLayout.letters_row_3()
+ CommonKeys.letters_row_3()
+ [
{
KeyProps.KEY: KEY_SHIFT,
@ -35,5 +37,5 @@ func rows() -> Array[Array]:
}
]
),
CommonLayout.bottom_row()
CommonKeys.bottom_row()
]

View File

@ -1,32 +1,24 @@
class_name LayoutANSIVariant extends AbstractLayout
static func name_static() -> String:
return "ANSI (big enter)"
static var name := "ANSI (Big-ass Enter)"
func rows() -> Array[Array]:
func get_name() -> String:
return name
func get_key_dict_rows() -> Array[Array]:
return [
(
CommonLayout.number_row()
CommonKeys.number_row()
+ [{KeyProps.KEY: KEY_BACKSLASH}, {KeyProps.KEY: KEY_BACKSPACE}]
),
(
[{KeyProps.KEY: KEY_TAB, KeyProps.W: 1.5}]
+ CommonLayout.letters_row_1()
+ [
{
KeyProps.KEY: KEY_ENTER,
KeyProps.W: 1.5,
KeyProps.H: 2,
KeyProps.SECONDARY_RECT: true,
KeyProps.W2: 2.25,
KeyProps.Y2: 1,
KeyProps.X2: -0.75,
}
]
+ CommonKeys.letters_row_1()
+ [CommonKeys.big_enter_ansi()]
),
[{KeyProps.KEY: KEY_CAPSLOCK, KeyProps.W: 1.75}] + CommonLayout.letters_row_2(),
[{KeyProps.KEY: KEY_CAPSLOCK, KeyProps.W: 1.75}] + CommonKeys.letters_row_2(),
(
[
{
@ -35,7 +27,7 @@ func rows() -> Array[Array]:
KeyProps.LOC: KEY_LOCATION_LEFT
}
]
+ CommonLayout.letters_row_3()
+ CommonKeys.letters_row_3()
+ [
{
KeyProps.KEY: KEY_SHIFT,
@ -44,5 +36,5 @@ func rows() -> Array[Array]:
}
]
),
CommonLayout.bottom_row()
CommonKeys.bottom_row()
]

View File

@ -0,0 +1,80 @@
class_name LayoutCustom extends AbstractLayout
var failed: bool
var _name: String
var _rows: Array[Array]
var _file_name: String
var _has_errors: bool
func _init(path: String) -> void:
_file_name = path.get_file()
_name = _file_name
var data: Variant = _load_json_file(path)
if not data:
failed = true
push_error("%s: COULD NOT PARSE CUSTOM LAYOUT JSON" % _file_name)
print()
return
var layout_rows := _deserialize(data, _file_name)
if not layout_rows:
failed = true
push_error("%s: COULD NOT DESERIALIZE CUSTOM LAYOUT" % _file_name)
print()
return
if _has_errors:
print()
_rows = layout_rows
func get_name() -> String:
return _name
func get_key_dict_rows() -> Array[Array]:
return _rows
func _load_json_file(path: String) -> Variant:
var file := FileAccess.open(path, FileAccess.READ)
if not file:
var file_err := FileAccess.get_open_error()
push_error("%s: error opening file: %s" % [_file_name, error_string(file_err)])
return []
var content := file.get_as_text()
var json := JSON.new()
var err := json.parse(content)
if err:
push_error(
(
"%s: json parse error at line %s: %s"
% [_file_name, json.get_error_line(), json.get_error_message()]
)
)
return []
var data: Variant = json.data
return data
func _deserialize(data: Variant, path: String) -> Array[Array]:
var parser: AbstractParser
if data is Array:
parser = ParserKLE.new(data as Array, path)
elif data is Dictionary:
parser = ParserQMK.new(data as Dictionary, path)
else:
push_error(
"%s: top-level json item is neither an array nor a dictionary" % _file_name
)
return []
if parser.get_name() != "":
_name = parser.get_name()
_has_errors = parser.has_errors()
return parser.get_rows()

View File

@ -1,31 +1,23 @@
class_name LayoutISO extends AbstractLayout
static func name_static() -> String:
return "ISO"
static var name := "ISO"
func rows() -> Array[Array]:
func get_name() -> String:
return name
func get_key_dict_rows() -> Array[Array]:
return [
CommonLayout.number_row() + [{KeyProps.KEY: KEY_BACKSPACE, KeyProps.W: 2}],
CommonKeys.number_row() + [{KeyProps.KEY: KEY_BACKSPACE, KeyProps.W: 2}],
(
[{KeyProps.KEY: KEY_TAB, KeyProps.W: 1.5}]
+ CommonLayout.letters_row_1()
+ [
{
KeyProps.KEY: KEY_ENTER,
KeyProps.W: 1.25,
KeyProps.H: 2,
KeyProps.X: 0.25,
KeyProps.SECONDARY_RECT: true,
KeyProps.W2: 1.5,
KeyProps.X2: -0.25,
}
]
+ CommonKeys.letters_row_1()
+ [CommonKeys.big_enter_iso()]
),
(
[{KeyProps.KEY: KEY_CAPSLOCK, KeyProps.W: 1.75}]
+ CommonLayout.letters_row_2()
+ CommonKeys.letters_row_2()
+ [{KeyProps.KEY: KEY_BACKSLASH}]
),
(
@ -37,7 +29,7 @@ func rows() -> Array[Array]:
},
{KeyProps.KEY: KEY_SECTION},
]
+ CommonLayout.letters_row_3()
+ CommonKeys.letters_row_3()
+ [
{
KeyProps.KEY: KEY_SHIFT,
@ -46,5 +38,5 @@ func rows() -> Array[Array]:
}
]
),
CommonLayout.bottom_row()
CommonKeys.bottom_row()
]

View File

@ -1,55 +0,0 @@
class_name LayoutJD40 extends AbstractLayout
static func name_static() -> String:
return "JD40"
func rows() -> Array[Array]:
return [
(
[{KeyProps.KEY: KEY_ESCAPE}]
+ CommonLayout.letters_1()
+ [{KeyProps.KEY: KEY_BACKSPACE}]
),
(
[{KeyProps.KEY: KEY_TAB, KeyProps.W: 1.5}]
+ CommonLayout.letters_2()
+ [{KeyProps.KEY: KEY_ENTER, KeyProps.W: 1.5}]
),
(
[
{
KeyProps.KEY: KEY_SHIFT,
KeyProps.W: 1.75,
KeyProps.LOC: KEY_LOCATION_LEFT
}
]
+ CommonLayout.letters_3()
+ [
{KeyProps.KEY: KEY_COMMA},
{
KeyProps.KEY: KEY_SHIFT,
KeyProps.W: 1.25,
KeyProps.LOC: KEY_LOCATION_RIGHT
},
{KeyProps.KEY: KEY_PERIOD},
]
),
[
{KeyProps.KEY: KEY_CTRL, KeyProps.W: 1.5, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_SPECIAL, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_ALT, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_SPACE, KeyProps.W: 6},
{
KeyProps.KEY: KEY_SPECIAL,
KeyProps.W: 1.25,
KeyProps.LOC: KEY_LOCATION_RIGHT
},
{
KeyProps.KEY: KEY_CTRL,
KeyProps.W: 1.25,
KeyProps.LOC: KEY_LOCATION_RIGHT
},
]
]

View File

@ -1 +0,0 @@
uid://i3bve82hdaop

View File

@ -1,34 +1,26 @@
class_name LayoutJIS extends AbstractLayout
static func name_static() -> String:
return "JIS"
static var name := "JIS"
func rows() -> Array[Array]:
func get_name() -> String:
return name
func get_key_dict_rows() -> Array[Array]:
return [
(
CommonLayout.number_row()
+ [{KeyProps.KEY: KEY_UNKNOWN}, {KeyProps.KEY: KEY_BACKSPACE}]
CommonKeys.number_row()
+ [{KeyProps.KEY: KEY_NONE}, {KeyProps.KEY: KEY_BACKSPACE}]
),
(
[{KeyProps.KEY: KEY_TAB, KeyProps.W: 1.5}]
+ CommonLayout.letters_row_1()
+ [
{
KeyProps.KEY: KEY_ENTER,
KeyProps.W: 1.25,
KeyProps.H: 2,
KeyProps.X: 0.25,
KeyProps.SECONDARY_RECT: true,
KeyProps.W2: 1.5,
KeyProps.X2: -0.25,
}
]
+ CommonKeys.letters_row_1()
+ [CommonKeys.big_enter_iso()]
),
(
[{KeyProps.KEY: KEY_CAPSLOCK, KeyProps.W: 1.75}]
+ CommonLayout.letters_row_2()
+ CommonKeys.letters_row_2()
+ [{KeyProps.KEY: KEY_BACKSLASH}]
),
(
@ -39,9 +31,9 @@ func rows() -> Array[Array]:
KeyProps.LOC: KEY_LOCATION_LEFT
}
]
+ CommonLayout.letters_row_3()
+ CommonKeys.letters_row_3()
+ [
{KeyProps.KEY: KEY_UNKNOWN},
{KeyProps.KEY: KEY_NONE},
{
KeyProps.KEY: KEY_SHIFT,
KeyProps.W: 1.75,
@ -53,10 +45,10 @@ func rows() -> Array[Array]:
{KeyProps.KEY: KEY_CTRL, KeyProps.W: 1.25, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_META, KeyProps.W: 1.25, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_ALT, KeyProps.W: 1.25, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_SPECIAL, KeyProps.W: 1.25},
{KeyProps.KEY: KEY_NONE, KeyProps.W: 1.25},
{KeyProps.KEY: KEY_SPACE, KeyProps.W: 2.5},
{KeyProps.KEY: KEY_SPECIAL, KeyProps.W: 1.25},
{KeyProps.KEY: KEY_SPECIAL, KeyProps.W: 1.25},
{KeyProps.KEY: KEY_NONE, KeyProps.W: 1.25},
{KeyProps.KEY: KEY_NONE, KeyProps.W: 1.25},
{KeyProps.KEY: KEY_ALT, KeyProps.W: 1.25, KeyProps.LOC: KEY_LOCATION_RIGHT},
{
KeyProps.KEY: KEY_META,

View File

@ -1,276 +0,0 @@
class_name LayoutKLE extends AbstractLayout
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,
"Esc": KEY_ESCAPE,
"Back Space": KEY_BACKSPACE,
"Caps Lock": KEY_CAPSLOCK,
"Win": KEY_META,
"AltGr": KEY_ALT,
"PrtSc": KEY_PRINT,
"Scroll Lock": KEY_SCROLLLOCK,
"PgUp": KEY_PAGEUP,
"Page Up": KEY_PAGEUP,
"PgDn": KEY_PAGEDOWN,
"Page Down": KEY_PAGEDOWN,
"Num Lock": KEY_NUMLOCK,
"Fn": KEY_SPECIAL,
"": KEY_UP,
"": KEY_LEFT,
"": KEY_DOWN,
"": 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 W := "w"
const H := "h"
const X := "x"
const Y := "y"
const W2 := "w2"
const H2 := "h2"
const X2 := "x2"
const Y2 := "y2"
const KEY_DICT := "key_dict"
const POS := "pos"
var has_errors: bool = false
var _name: String
var _rows: Array[Array]
func _init(json_path: String) -> void:
var data := _load_json_file(json_path)
if not data:
has_errors = true
printerr("ERROR LOADING KLE LAYOUT JSON: '%s'" % json_path)
return
var layout_rows := _deserialize(data)
if not layout_rows:
has_errors = true
printerr("ERROR DESERIALIZING KLE LAYOUT: '%s'" % json_path)
return
_rows = layout_rows
func name() -> String:
return _name
func rows() -> Array[Array]:
return _rows
func _load_json_file(path: String) -> Array:
var file := FileAccess.open(path, FileAccess.READ)
if not file:
printerr("error opening file '%s': %s" % [path, FileAccess.get_open_error()])
return []
var content := file.get_as_text()
var json := JSON.new()
var err := json.parse(content)
if err != OK:
printerr(
(
"json parse error in '%s' at line %s: %s"
% [path, json.get_error_line(), json.get_error_message()]
)
)
return []
var data: Variant = json.data
if data is not Array:
return []
_name = path.get_file()
return data
func _deserialize(data: Array) -> Array[Array]:
var layout_rows: Array[Array] = []
var key_pos_dicts: Dictionary[Key, Array] = {}
var row_index: int = 0
for data_row: Variant in data:
if data_row is Array:
var layout_row := _deserialize_row(
data_row as Array, key_pos_dicts, row_index
)
layout_rows.append(layout_row)
row_index += 1
_get_key_locations(key_pos_dicts)
return layout_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 key_data: Variant in data_row:
if key_data is Dictionary:
current_key_data_dict = key_data as Dictionary
if key_data is String:
var legend := (key_data 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 = _cleanup_key_data_dict(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]
key_dict[KeyProps.SECONDARY_RECT] = true
if data_key.has(H2):
key_dict[KeyProps.H2] = data_key[H2]
key_dict[KeyProps.SECONDARY_RECT] = true
if data_key.has(X2):
key_dict[KeyProps.X2] = data_key[X2]
key_dict[KeyProps.SECONDARY_RECT] = true
if data_key.has(Y2):
key_dict[KeyProps.Y2] = data_key[Y2]
key_dict[KeyProps.SECONDARY_RECT] = true
return key_dict
func _cleanup_key_data_dict(data_key: Dictionary) -> Dictionary:
data_key.erase(W)
data_key.erase(H)
data_key.erase(X)
data_key.erase(Y)
return data_key
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:
printerr("%s: could not recognize key label %s" % [_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
continue
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
continue
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

View File

@ -1,34 +0,0 @@
class_name LayoutPlanck extends AbstractLayout
static func name_static() -> String:
return "Planck"
func rows() -> Array[Array]:
return [
(
[{KeyProps.KEY: KEY_TAB}]
+ CommonLayout.letters_1()
+ [{KeyProps.KEY: KEY_BACKSPACE}]
),
[{KeyProps.KEY: KEY_ESCAPE}] + CommonLayout.letters_row_2(),
(
[{KeyProps.KEY: KEY_SHIFT, KeyProps.LOC: KEY_LOCATION_LEFT}]
+ CommonLayout.letters_row_3()
+ [{KeyProps.KEY: KEY_ENTER}]
),
[
{KeyProps.KEY: KEY_SPECIAL},
{KeyProps.KEY: KEY_CTRL, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_ALT, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_META, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_SPECIAL, KeyProps.LOC: KEY_LOCATION_LEFT},
{KeyProps.KEY: KEY_SPACE, KeyProps.W: 2},
{KeyProps.KEY: KEY_SPECIAL, KeyProps.LOC: KEY_LOCATION_RIGHT},
{KeyProps.KEY: KEY_LEFT},
{KeyProps.KEY: KEY_DOWN},
{KeyProps.KEY: KEY_UP},
{KeyProps.KEY: KEY_RIGHT},
]
]

View File

@ -1 +0,0 @@
uid://dqjuvbt87uah0

View File

@ -1,9 +1,14 @@
class_name LayoutTest extends AbstractLayout
static func name_static() -> String:
return "test"
static var name := "test"
func rows() -> Array[Array]:
return [[{KeyProps.KEY: KEY_SPECIAL}]]
func get_name() -> String:
return name
func get_key_dict_rows() -> Array[Array]:
return [
[{KeyProps.KEY: KEY_Q}, {KeyProps.KEY: KEY_W}],
[{KeyProps.KEY: KEY_A}, {KeyProps.KEY: KEY_S}],
]

View File

@ -0,0 +1,326 @@
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,
"": 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
var _has_errors: bool
func _init(data: Array, file_name: String) -> void:
_file_name = file_name
var key_pos_dicts: Dictionary[Key, Array] = {}
var pos: Vector2 = Vector2.ZERO
var pivot := Vector2.ZERO
var angle: float = 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 result := _deserialize_row(
data_row as Array, key_pos_dicts, pos, pivot, angle
)
var layout_row := result[0] as Array[Dictionary]
if layout_row:
_rows.append(layout_row)
pos = result[1]
pos.y += 1
pivot = result[2]
angle = result[3]
_get_key_locations(key_pos_dicts)
func get_name() -> String:
return _name
func get_rows() -> Array[Array]:
return _rows
func has_errors() -> bool:
return _has_errors
func _deserialize_row(
data_row: Array,
key_pos_dicts: Dictionary[Key, Array],
pos: Vector2,
pivot: Vector2,
angle: float,
) -> Array:
var layout_row: Array[Dictionary] = []
pos.x = pivot.x
var data_key: Dictionary = {}
var prev_keycode: Key = KEY_NONE
for item: Variant in data_row:
if item is Dictionary:
data_key = item as Dictionary
if item is String:
var legend := (item as String).split("\n")
var keycode := _get_keycode_from_legend(legend, data_key, prev_keycode)
var key_dict := {KeyProps.KEY: keycode}
key_dict.merge(_deserialize_key(data_key))
layout_row.append(key_dict)
if data_key.has(R):
angle = -deg_to_rad(data_key[R] as float)
if data_key.has(RX):
pivot.x = data_key[RX]
if data_key.has(RY):
pivot.y = data_key[RY]
if data_key.has(RX) or data_key.has(RY):
pos = pivot
pos.x += data_key[X] if data_key.has(X) else 0.0
pos.y += data_key[Y] if data_key.has(Y) else 0.0
var key_pos_rotated := KeyHelper.get_rotated_key_pos(
Vector3(pos.x, 0, pos.y), pivot, angle
)
var key_pos_dict := {
KEY_DICT: key_dict, POS: Vector2(key_pos_rotated.x, key_pos_rotated.z)
}
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]
pos.x += data_key[W] if data_key.has(W) else 1.0
data_key = {}
prev_keycode = keycode
return [layout_row, pos, pivot, angle]
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, prev_keycode: Key
) -> 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, prev_keycode)
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:
push_warning("%s: could not recognize key label %s" % [_file_name, str(legend)])
_has_errors = true
return keycode
func _get_numpad_keycode_from_legend(legend: Array[String], prev_keycode: Key) -> 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]]
for label in legend:
if OS.find_keycode_from_string(label) == KEY_ENTER:
if prev_keycode >= KEY_KP_MULTIPLY and prev_keycode <= KEY_KP_9:
return KEY_KP_ENTER
break
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 keycode == KEY_NONE or 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_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

View File

@ -0,0 +1 @@
uid://dssjdkxq56jl8

View File

@ -0,0 +1,389 @@
class_name ParserQMK extends AbstractParser
const KEYCODE_MAP: Dictionary[String, Key] = {
"ENT": KEY_ENTER,
"ESC": KEY_ESCAPE,
"BSPC": KEY_BACKSPACE,
"SPC": KEY_SPACE,
"MINS": KEY_MINUS,
"EQL": KEY_EQUAL,
"LEFT_BRACKET": KEY_BRACKETLEFT,
"LBRC": KEY_BRACKETLEFT,
"RIGHT_BRACKET": KEY_BRACKETRIGHT,
"RBRC": KEY_BRACKETRIGHT,
"BSLS": KEY_BACKSLASH,
"NONUS_HASH": KEY_BACKSLASH,
"NUHS": KEY_BACKSLASH,
"SCLN": KEY_SEMICOLON,
"QUOTE": KEY_APOSTROPHE,
"QUOT": KEY_APOSTROPHE,
"GRAVE": KEY_QUOTELEFT,
"GRV": KEY_QUOTELEFT,
"COMM": KEY_COMMA,
"DOT": KEY_PERIOD,
"SLSH": KEY_SLASH,
"NONUS_BACKSLASH": KEY_SECTION,
"NUBS": KEY_SECTION,
"CAPS_LOCK": KEY_CAPSLOCK,
"CAPS": KEY_CAPSLOCK,
"SCROLL_LOCK": KEY_SCROLLLOCK,
"SCRL": KEY_SCROLLLOCK,
"BRMD": KEY_SCROLLLOCK,
"NUM_LOCK": KEY_NUMLOCK,
"NUM": KEY_NUMLOCK,
"PRINT_SCREEN": KEY_PRINT,
"PSCR": KEY_PRINT,
"PAUS": KEY_PAUSE,
"BRK": KEY_PAUSE,
"BRMU": KEY_PAUSE,
"INS": KEY_INSERT,
"PAGE_UP": KEY_PAGEUP,
"PGUP": KEY_PAGEUP,
"DEL": KEY_DELETE,
"PAGE_DOWN": KEY_PAGEDOWN,
"PGDN": KEY_PAGEDOWN,
"RGHT": KEY_RIGHT,
"APPLICATION": KEY_MENU,
"APP": KEY_MENU,
"SYSTEM_REQUEST": KEY_SYSREQ,
"SYRQ": KEY_SYSREQ,
"CLR": KEY_CLEAR,
"RETURN": KEY_ENTER,
"RETN": KEY_ENTER,
"AUDIO_MUTE": KEY_VOLUMEMUTE,
"MUTE": KEY_VOLUMEMUTE,
"AUDIO_VOL_UP": KEY_VOLUMEUP,
"VOLU": KEY_VOLUMEUP,
"AUDIO_VOL_DOWN": KEY_VOLUMEDOWN,
"VOLD": KEY_VOLUMEDOWN,
"MEDIA_NEXT_TRACK": KEY_MEDIANEXT,
"MNXT": KEY_MEDIANEXT,
"MEDIA_PREV_TRACK": KEY_MEDIAPREVIOUS,
"MPRV": KEY_MEDIAPREVIOUS,
"MEDIA_STOP": KEY_MEDIASTOP,
"MSTP": KEY_MEDIASTOP,
"MEDIA_PLAY_PAUSE": KEY_MEDIAPLAY,
"MPLY": KEY_MEDIAPLAY,
"MAIL": KEY_LAUNCHMAIL,
"WWW_SEARCH": KEY_SEARCH,
"WSCH": KEY_SEARCH,
"WWW_HOME": KEY_SEARCH,
"WHOM": KEY_SEARCH,
"WWW_BACK": KEY_BACK,
"WBAK": KEY_BACK,
"WWW_FORWARD": KEY_FORWARD,
"WFWD": KEY_FORWARD,
"WWW_STOP": KEY_STOP,
"WSTP": KEY_STOP,
"WWW_REFRESH": KEY_REFRESH,
"WREF": KEY_REFRESH,
"WWW_FAVORITES": KEY_FAVORITES,
"WFAV": KEY_FAVORITES,
"MEDIA_FAST_FORWARD": KEY_MEDIANEXT,
"MFFD": KEY_MEDIANEXT,
"MEDIA_REWIND": KEY_MEDIAPREVIOUS,
"MRWD": KEY_MEDIAPREVIOUS,
"INTERNATIONAL_1": KEY_BACKSLASH,
"INT1": KEY_BACKSLASH,
"INTERNATIONAL_3": KEY_YEN,
"INT3": KEY_YEN,
"KP_SLASH": KEY_KP_DIVIDE,
"PSLS": KEY_KP_DIVIDE,
"KP_ASTERISK": KEY_KP_MULTIPLY,
"PAST": KEY_KP_MULTIPLY,
"KP_MINUS": KEY_KP_SUBTRACT,
"PMNS": KEY_KP_SUBTRACT,
"KP_PLUS": KEY_KP_ADD,
"PPLS": KEY_KP_ADD,
"KP_ENTER": KEY_KP_ENTER,
"PENT": KEY_KP_ENTER,
"KP_1": KEY_KP_1,
"P1": KEY_KP_1,
"KP_2": KEY_KP_2,
"P2": KEY_KP_2,
"KP_3": KEY_KP_3,
"P3": KEY_KP_3,
"KP_4": KEY_KP_4,
"P4": KEY_KP_4,
"KP_5": KEY_KP_5,
"P5": KEY_KP_5,
"KP_6": KEY_KP_6,
"P6": KEY_KP_6,
"KP_7": KEY_KP_7,
"P7": KEY_KP_7,
"KP_8": KEY_KP_8,
"P8": KEY_KP_8,
"KP_9": KEY_KP_9,
"P9": KEY_KP_9,
"KP_0": KEY_KP_0,
"P0": KEY_KP_0,
"KP_DOT": KEY_KP_PERIOD,
"PDOT": KEY_KP_PERIOD,
"KP_COMMA": KEY_KP_PERIOD,
"PCMM": KEY_KP_PERIOD,
}
const KEYCODE_MODIFIER_LEFT_MAP: Dictionary[String, Key] = {
"LEFT_CTRL": KEY_CTRL,
"LCTL": KEY_CTRL,
"LEFT_SHIFT": KEY_SHIFT,
"LSFT": KEY_SHIFT,
"LEFT_ALT": KEY_ALT,
"LALT": KEY_ALT,
"LOPT": KEY_ALT,
"LEFT_GUI": KEY_META,
"LGUI": KEY_META,
"LCMD": KEY_META,
"LWIN": KEY_META,
}
const KEYCODE_MODIFIER_RIGHT_MAP: Dictionary[String, Key] = {
"RIGHT_CTRL": KEY_CTRL,
"RCTL": KEY_CTRL,
"RIGHT_SHIFT": KEY_SHIFT,
"RSFT": KEY_SHIFT,
"RIGHT_ALT": KEY_ALT,
"RALT": KEY_ALT,
"ROPT": KEY_ALT,
"RIGHT_GUI": KEY_META,
"RGUI": KEY_META,
"RCMD": KEY_META,
"RWIN": KEY_META,
}
const KEYBOARD_NAME := "keyboard_name"
const LAYOUTS := "layouts"
const LAYOUT := "layout"
const W := "w"
const H := "h"
const X := "x"
const Y := "y"
const R := "r"
const RX := "rx"
const RY := "ry"
const KEYCODE := "keycode"
var _name: String
var _rows: Array[Array]
var _file_name: String
var _has_errors: bool
func _init(data: Dictionary, file_name: String) -> void:
_file_name = file_name
if data.has(KEYBOARD_NAME) and data[KEYBOARD_NAME] is String:
_name = data[KEYBOARD_NAME]
if (
not data.has(LAYOUTS)
or data[LAYOUTS] is not Dictionary
or (data[LAYOUTS] as Dictionary).size() == 0
):
push_error("%s: '%s' is missing" % [_file_name, LAYOUTS])
_has_errors = true
return
var layout_name := (data[LAYOUTS] as Dictionary).keys()[0] as String
if (
data[LAYOUTS][layout_name] is not Dictionary
or not (data[LAYOUTS][layout_name] as Dictionary).has(LAYOUT)
or data[LAYOUTS][layout_name][LAYOUT] is not Array
):
push_error(
"%s: '%s.%s.%s' is missing" % [_file_name, LAYOUTS, layout_name, LAYOUT]
)
_has_errors = true
return
var data_keys := data[LAYOUTS][layout_name][LAYOUT] as Array
var err := _get_keymap_keys(data_keys, file_name)
if err:
_has_errors = true
return
_rows = _deserialize_keys(data_keys)
func get_name() -> String:
return _name
func get_rows() -> Array[Array]:
return _rows
func has_errors() -> bool:
return _has_errors
func _sort_data_keys(a: Variant, b: Variant) -> bool:
if a is not Dictionary or b is not Dictionary:
return false
var a_dict := a as Dictionary
var b_dict := b as Dictionary
if not a_dict.has(Y) or not b_dict.has(Y):
return false
var a_y := a_dict[Y] as float
var b_y := b_dict[Y] as float
if a_y == b_y and a_dict.has(X) and a_dict.has(X):
return (a_dict[X] as float) < (b_dict[X] as float)
return a_y < b_y
func _deserialize_keys(data_keys: Array) -> Array[Array]:
data_keys.sort_custom(_sort_data_keys)
var layout_rows: Array[Array] = []
var layout_row: Array[Dictionary] = []
var prev_pos_x: float = 0
var prev_pos_y: float = 0
for i in range(data_keys.size()):
if data_keys[i] is not Dictionary:
continue
var data_key := data_keys[i] as Dictionary
if not data_key.has(X) or not data_key.has(Y):
continue
if prev_pos_y != data_key[Y]:
prev_pos_x = 0
prev_pos_y += 1
if layout_row:
layout_rows.append(layout_row)
layout_row = []
var keycode := (
_get_keycode_from_keymap_key(data_key[KEYCODE] as String)
if data_key.has(KEYCODE)
else KEY_NONE
)
var key_dict := {KeyProps.KEY: keycode}
(
key_dict
. merge(
_deserialize_key(
data_key,
data_key[X] as float - prev_pos_x,
data_key[Y] as float - prev_pos_y,
)
)
)
var location := (
_get_key_location(data_key[KEYCODE] as String)
if data_key.has(KEYCODE)
else KEY_LOCATION_UNSPECIFIED
)
if location != KEY_LOCATION_UNSPECIFIED:
key_dict[KeyProps.LOC] = location
layout_row.append(key_dict)
var width: float = data_key[W] if data_key.has(W) else 1.0
prev_pos_x = data_key[X] + width
prev_pos_y = data_key[Y]
if layout_row:
layout_rows.append(layout_row)
return layout_rows
func _deserialize_key(data_key: Dictionary, pos_x: float, pos_y: float) -> 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 pos_x != 0:
key_dict[KeyProps.X] = pos_x
if pos_y != 0:
key_dict[KeyProps.Y] = pos_y
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]
return key_dict
func _get_keymap_keys(data_keys: Array, json_file_name: String) -> Error:
var c_file_name := json_file_name.substr(0, json_file_name.rfind(".") + 1) + "c"
var file := FileAccess.open(
LayoutConfig.CUSTOM_LAYOUTS_PATH.path_join(c_file_name), FileAccess.READ
)
if not file:
var file_err := FileAccess.get_open_error()
push_error(
"%s: error opening file '%s': %s" % [_file_name, c_file_name, error_string(file_err)]
)
return FAILED
var content := file.get_as_text()
var layout_regex := RegEx.new()
layout_regex.compile(
r".*=\s(?<name>LAYOUT.*)\s*\((?<keys>[^()]*(?:\([^()]*\)[^()]*)*)\)"
)
var keys_regex := RegEx.new()
keys_regex.compile(r"\w+(?:\(.*?\))?")
var keymap_keys: Array[String] = []
var layout_match := layout_regex.search(content)
if not layout_match:
push_error(
"%s: no layout keymap definitions found in '%s'" % [_file_name, c_file_name]
)
return FAILED
for key_matches in keys_regex.search_all(layout_match.get_string("keys")):
keymap_keys.append(key_matches.get_string())
for i in range(mini(data_keys.size(), keymap_keys.size())):
data_keys[i][KEYCODE] = keymap_keys[i]
return OK
func _get_keymap_key_unprefixed(keymap_key_prefixed: String) -> String:
return keymap_key_prefixed.substr(3)
func _get_keycode_from_keymap_key(keymap_key_prefixed: String) -> Key:
var keymap_key := _get_keymap_key_unprefixed(keymap_key_prefixed)
var keycode := KEY_NONE
keycode = OS.find_keycode_from_string(keymap_key)
if keycode == KEY_NONE and KEYCODE_MAP.has(keymap_key):
keycode = KEYCODE_MAP[keymap_key]
if keycode == KEY_NONE and KEYCODE_MODIFIER_LEFT_MAP.has(keymap_key):
keycode = KEYCODE_MODIFIER_LEFT_MAP[keymap_key]
if keycode == KEY_NONE and KEYCODE_MODIFIER_RIGHT_MAP.has(keymap_key):
keycode = KEYCODE_MODIFIER_RIGHT_MAP[keymap_key]
if keycode == KEY_NONE:
push_warning(
"%s: could not recognize key label '%s'" % [_file_name, keymap_key_prefixed]
)
_has_errors = true
return keycode
func _get_key_location(keymap_key_prefixed: String) -> KeyLocation:
var keymap_key := _get_keymap_key_unprefixed(keymap_key_prefixed)
if KEYCODE_MODIFIER_LEFT_MAP.has(keymap_key):
return KEY_LOCATION_LEFT
if KEYCODE_MODIFIER_RIGHT_MAP.has(keymap_key):
return KEY_LOCATION_RIGHT
return KEY_LOCATION_UNSPECIFIED

View File

@ -0,0 +1 @@
uid://cieogkcrmre4q

125
scripts/player.gd Normal file
View File

@ -0,0 +1,125 @@
class_name Player extends Node3D
#region variables
@export_group("References")
@export var _keyboard: GameKeyboard
@export var _sfx_player: AudioStreamPlayer3D
@export_group("Animation")
@export_subgroup("Moving")
@export var _move_duration: float = 0.1
@export var _move_hop_height: float = 1
@export_group("SFX")
@export var _hop_sfx: AudioStream
@export var _hop_sfx_volume: float = 0.4
var _current_key: GameKey
var _starting_keycode: Key = KEY_H
var _rotation: Quaternion
var _move_timer: float
var _is_moving: bool
var _prev_pos: Vector3
var _polyphonic: AudioStreamPlaybackPolyphonic
#endregion
#region builtins
func _ready() -> void:
_keyboard.key_pressed.connect(_on_keyboard_key_pressed)
_current_key = _keyboard.query_key_by_keycode(_starting_keycode)
if _current_key:
global_transform = _current_key.player_pos_marker.global_transform
_keyboard.emit_player_current_key_changed(_current_key)
_sfx_player.play()
_polyphonic = _sfx_player.get_stream_playback()
func _process(delta: float) -> void:
_animate(delta)
#endregion
#region movement
func _move(game_key: GameKey) -> void:
var look_from := _current_key.player_pos_marker.transform * _current_key.transform
var look_target := game_key.player_pos_marker.transform * game_key.transform
look_target.origin.y = look_from.origin.y
var looking := look_from.looking_at(look_target.origin, Vector3.UP, true)
_rotation = looking.basis.get_rotation_quaternion()
_prev_pos = _current_key.player_pos_marker.global_position
_move_timer = _move_duration
if _is_moving:
_finish_move(true)
_is_moving = true
_current_key = game_key
_keyboard.emit_player_current_key_changed(game_key)
_play_sfx(_hop_sfx, _hop_sfx_volume)
#endregion
#region animation
func _animate(delta: float) -> void:
if not _current_key:
return
if _move_timer <= 0:
if _is_moving:
_is_moving = false
_finish_move(false)
global_position = _animate_position()
else:
_move_timer -= delta
var lerp_value := 1 - (_move_timer / _move_duration)
global_position = lerp(_prev_pos, _animate_position(), lerp_value)
var hop_value := 1 - absf(lerp_value * 2 - 1)
hop_value = 1 - pow(1 - hop_value, 3)
global_position.y += hop_value * _move_hop_height
global_rotation = _animate_rotation()
func _animate_position() -> Vector3:
return _current_key.player_pos_marker.global_position
func _animate_rotation() -> Vector3:
var rot_translate := (
_current_key.get_default_transform().inverse()
* _current_key.player_pos_marker.global_transform
)
return (_rotation * rot_translate.basis.get_rotation_quaternion()).get_euler()
func _finish_move(_interrupted: bool) -> void:
_keyboard.emit_player_finished_move(_current_key)
#endregion
#region sounds
func _play_sfx(stream: AudioStream, volume_linear: float = 1) -> void:
_polyphonic.play_stream(stream, 0, linear_to_db(volume_linear))
#endregion
#region event handlers
func _on_keyboard_key_pressed(game_key: GameKey) -> void:
if _current_key and _current_key.is_adjacent(game_key):
_move(game_key)
#endregion

1
scripts/player.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://dmfdlb4ae57qv

View File

@ -2,6 +2,8 @@
# https://www.youtube.com/watch?v=KPoeNZZ6H4s
class_name SecondOrderDynamics
var y: Vector3
var _k1: float
var _k2: float
var _k3: float
@ -11,7 +13,6 @@ var _z: float
var _d: float
var _xp: Vector3
var _y: Vector3
var _yd: Vector3
@ -29,7 +30,7 @@ func _init(fzr: Vector3, x0: Vector3) -> void:
_k3 = r * z / _w
_xp = x0
_y = x0
y = x0
_yd = Vector3.ZERO
@ -46,6 +47,7 @@ func process(t: float, x: Vector3, xd: Vector3 = Vector3.INF) -> Vector3:
var beta := t1 * t1
var t2 := t / (1 + beta - alpha)
k2_stable = t * t2
_y = _y + t * _yd
_yd = _yd + t * (x + _k3 * xd - _y - _k1 * _yd) / k2_stable
return _y
y = y + t * _yd
_yd = _yd + t * (x + _k3 * xd - y - _k1 * _yd) / k2_stable
return y

150
scripts/utils/key_helper.gd Normal file
View File

@ -0,0 +1,150 @@
class_name KeyHelper
const ADJACENCY_MAP: Dictionary[Key, Array] = {
KEY_1: [KEY_2, KEY_Q],
KEY_2: [KEY_3, KEY_W, KEY_Q, KEY_1],
KEY_3: [KEY_4, KEY_E, KEY_W, KEY_2],
KEY_4: [KEY_5, KEY_R, KEY_E, KEY_3],
KEY_5: [KEY_6, KEY_T, KEY_R, KEY_4],
KEY_6: [KEY_7, KEY_Y, KEY_T, KEY_5],
KEY_7: [KEY_8, KEY_U, KEY_Y, KEY_6],
KEY_8: [KEY_9, KEY_I, KEY_U, KEY_7],
KEY_9: [KEY_0, KEY_O, KEY_I, KEY_8],
KEY_0: [KEY_MINUS, KEY_P, KEY_O, KEY_9],
KEY_MINUS: [KEY_EQUAL, KEY_BRACKETLEFT, KEY_P, KEY_0],
KEY_EQUAL: [KEY_BRACKETLEFT, KEY_BRACKETRIGHT, KEY_MINUS],
KEY_Q: [KEY_1, KEY_2, KEY_W, KEY_A],
KEY_W: [KEY_2, KEY_3, KEY_E, KEY_S, KEY_A, KEY_Q],
KEY_E: [KEY_3, KEY_4, KEY_R, KEY_D, KEY_S, KEY_W],
KEY_R: [KEY_4, KEY_5, KEY_T, KEY_F, KEY_D, KEY_E],
KEY_T: [KEY_5, KEY_6, KEY_Y, KEY_G, KEY_F, KEY_R],
KEY_Y: [KEY_6, KEY_7, KEY_U, KEY_H, KEY_G, KEY_T],
KEY_U: [KEY_7, KEY_8, KEY_I, KEY_J, KEY_H, KEY_Y],
KEY_I: [KEY_8, KEY_9, KEY_O, KEY_K, KEY_J, KEY_U],
KEY_O: [KEY_9, KEY_0, KEY_P, KEY_L, KEY_K, KEY_I],
KEY_P: [KEY_0, KEY_MINUS, KEY_BRACKETLEFT, KEY_SEMICOLON, KEY_L, KEY_O],
KEY_BRACKETLEFT:
[KEY_MINUS, KEY_EQUAL, KEY_BRACKETRIGHT, KEY_APOSTROPHE, KEY_SEMICOLON, KEY_P],
KEY_BRACKETRIGHT: [KEY_EQUAL, KEY_APOSTROPHE, KEY_BRACKETLEFT],
KEY_A: [KEY_Q, KEY_W, KEY_S, KEY_Z],
KEY_S: [KEY_W, KEY_E, KEY_D, KEY_X, KEY_Z, KEY_A],
KEY_D: [KEY_E, KEY_R, KEY_F, KEY_C, KEY_X, KEY_S],
KEY_F: [KEY_R, KEY_T, KEY_G, KEY_V, KEY_C, KEY_D],
KEY_G: [KEY_T, KEY_Y, KEY_H, KEY_B, KEY_V, KEY_F],
KEY_H: [KEY_Y, KEY_U, KEY_J, KEY_N, KEY_B, KEY_G],
KEY_J: [KEY_U, KEY_I, KEY_K, KEY_M, KEY_N, KEY_H],
KEY_K: [KEY_I, KEY_O, KEY_L, KEY_COMMA, KEY_M, KEY_J],
KEY_L: [KEY_O, KEY_P, KEY_SEMICOLON, KEY_PERIOD, KEY_COMMA, KEY_K],
KEY_SEMICOLON:
[KEY_P, KEY_BRACKETLEFT, KEY_APOSTROPHE, KEY_SLASH, KEY_PERIOD, KEY_L],
KEY_APOSTROPHE: [KEY_BRACKETLEFT, KEY_BRACKETRIGHT, KEY_SLASH, KEY_SEMICOLON],
KEY_Z: [KEY_A, KEY_S, KEY_X],
KEY_X: [KEY_S, KEY_D, KEY_C, KEY_Z],
KEY_C: [KEY_D, KEY_F, KEY_V, KEY_X],
KEY_V: [KEY_F, KEY_G, KEY_B, KEY_C],
KEY_B: [KEY_G, KEY_H, KEY_N, KEY_V],
KEY_N: [KEY_H, KEY_J, KEY_M, KEY_B],
KEY_M: [KEY_J, KEY_K, KEY_COMMA, KEY_N],
KEY_COMMA: [KEY_K, KEY_L, KEY_PERIOD, KEY_M],
KEY_PERIOD: [KEY_L, KEY_SEMICOLON, KEY_SLASH, KEY_COMMA],
KEY_SLASH: [KEY_SEMICOLON, KEY_APOSTROPHE, KEY_PERIOD],
}
static func game_key_sort(a: GameKey, b: GameKey) -> bool:
var a_pos := a.get_default_transform().origin
var b_pos := b.get_default_transform().origin
if a_pos.y == b_pos.y:
return a_pos.x < b_pos.x
return a_pos.y < b_pos.y
static func get_rotated_key_pos(
key_pos: Vector3, pivot: Vector2, angle: float
) -> Vector3:
var pivot_pos := Vector3(pivot.x, 0, pivot.y)
return (key_pos - pivot_pos).rotated(Vector3.UP, angle) + pivot_pos
static func iterate_key_props_rows(
iter_func: Callable,
key_props_rows: Array[Array],
key_size: float = 1,
key_gap: float = 0,
current_keys: Dictionary[Vector2i, Array] = {}
) -> Rect2:
var key_pos: Vector2 = Vector2.ZERO
var pivot := Vector2.ZERO
var angle: float = 0
var rect: Rect2 = Rect2(0, 0, 0, 0)
for key_props_row: Array[KeyProps] in key_props_rows:
var result := _iterate_key_props_row(
iter_func,
key_props_row,
key_pos,
pivot,
angle,
rect,
key_size,
key_gap,
current_keys
)
key_pos = result[0]
key_pos.y += 1 + key_gap
pivot = result[1]
angle = result[2]
rect = result[3]
return rect
static func _iterate_key_props_row(
iter_func: Callable,
key_props_row: Array[KeyProps],
key_pos: Vector2,
pivot: Vector2,
angle: float,
rect: Rect2,
key_size: float,
key_gap: float,
current_keys: Dictionary[Vector2i, Array]
) -> Array[Variant]:
key_pos.x = pivot.x
for key_props in key_props_row:
if key_props.has_angle():
angle = -deg_to_rad(key_props.angle)
if key_props.has_pivot_x():
pivot.x = key_props.pivot_x
if key_props.has_pivot_y():
pivot.y = key_props.pivot_y
if key_props.has_pivot_x() or key_props.has_pivot_y():
key_pos = pivot
var game_key_pos := Vector3(
key_pos.x + key_size / 2, 0, key_pos.y + key_size / 2
)
game_key_pos.x += (
(key_size * key_props.width - key_size) / 2 + key_size * key_props.x
)
game_key_pos.z += key_props.y * key_size
game_key_pos = KeyHelper.get_rotated_key_pos(game_key_pos, pivot, angle)
iter_func.call(key_props, game_key_pos, angle, current_keys)
if key_pos.x < rect.position.x:
rect.position.x = key_pos.x
if key_pos.y < rect.position.y:
rect.position.y = key_pos.y
key_pos.x += key_props.width * key_size + key_props.x * key_size + key_gap
key_pos.y += key_props.y * key_size
if key_pos.x - key_gap > rect.end.x:
rect.end.x = key_pos.x - key_gap
if key_pos.y + key_size * key_props.height > rect.end.y:
rect.end.y = key_pos.y + key_size * key_props.height
return [key_pos, pivot, angle, rect]

View File

@ -0,0 +1 @@
uid://boau8xfetthx8