commit
This commit is contained in:
parent
634a1aa547
commit
82077a6e1e
17 changed files with 656 additions and 130 deletions
311
Cargo.lock
generated
311
Cargo.lock
generated
|
@ -41,6 +41,54 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
|
@ -129,9 +177,49 @@ dependencies = [
|
|||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"windows-targets",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.2"
|
||||
|
@ -159,6 +247,12 @@ dependencies = [
|
|||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
|
@ -223,8 +317,12 @@ dependencies = [
|
|||
name = "deckster"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"enum-map",
|
||||
"env_logger",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"loupedeck_serial",
|
||||
"piet",
|
||||
"regex",
|
||||
|
@ -234,6 +332,7 @@ dependencies = [
|
|||
"serde_with",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -246,6 +345,26 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-map"
|
||||
version = "3.0.0-beta.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb2a23ad36148a32085addb3ef1aa39805d044d4532ff258360d523a4eff38e5"
|
||||
dependencies = [
|
||||
"enum-map-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-map-derive"
|
||||
version = "1.0.0-beta.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44600091ce205df4f8b661e98617d49c37b2dd609e449ec82b0fb5d7b33e2eeb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-ordinalize"
|
||||
version = "4.3.0"
|
||||
|
@ -287,12 +406,35 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.11"
|
||||
|
@ -327,6 +469,18 @@ version = "0.14.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
|
@ -416,6 +570,17 @@ dependencies = [
|
|||
"mach2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.10"
|
||||
|
@ -472,6 +637,12 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
|
@ -652,12 +823,34 @@ version = "0.1.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
|
@ -788,6 +981,15 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.52"
|
||||
|
@ -988,12 +1190,28 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.89"
|
||||
|
@ -1064,6 +1282,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -1076,7 +1303,16 @@ version = "0.51.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1085,13 +1321,28 @@ version = "0.48.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1100,42 +1351,84 @@ version = "0.48.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.31"
|
||||
|
|
|
@ -14,4 +14,9 @@ serde_regex = "1.1.0"
|
|||
serde_with = "3.4.0"
|
||||
thiserror = "1.0.52"
|
||||
toml = "0.8.8"
|
||||
regex = "1.10.2"
|
||||
regex = "1.10.2"
|
||||
log = "0.4.20"
|
||||
env_logger = "0.10.1"
|
||||
clap = { version = "4.4.12", features = ["derive"] }
|
||||
enum-map = "3.0.0-beta.2"
|
||||
walkdir = "2.4.0"
|
|
@ -7,7 +7,7 @@ mode.media__play_pause.icon.playing = "@ph/pause"
|
|||
[keys.1x2]
|
||||
icon = "@fad/shuffle"
|
||||
mode.vibrate.pattern = "low"
|
||||
mode.spotify__shuffle.icon.on = "@fad/shuffle[color=#58fc11]"
|
||||
mode.spotify__shuffle.icon.active = "@fad/shuffle[color=#58fc11]"
|
||||
|
||||
[keys.2x1]
|
||||
icon = "@ph/timer"
|
||||
|
|
|
@ -22,14 +22,14 @@ mode.keyboard.key = "5"
|
|||
label = "4"
|
||||
mode.keyboard.key = "4"
|
||||
|
||||
[keys.1x4]
|
||||
[keys.3x4]
|
||||
label = "3"
|
||||
mode.keyboard.key = "3"
|
||||
|
||||
[keys.1x3]
|
||||
[keys.3x3]
|
||||
label = "2"
|
||||
mode.keyboard.key = "2"
|
||||
|
||||
[keys.1x2]
|
||||
[keys.3x2]
|
||||
label = "1"
|
||||
mode.keyboard.key = "1"
|
|
@ -1,29 +1,32 @@
|
|||
[knobs.left-top]
|
||||
icon = "@ph/microphone-light"
|
||||
indicator.circle.color = "#ffffff"
|
||||
indicator.circle.width = 2
|
||||
indicator.circle.radius = 40
|
||||
|
||||
mode.audio_volume.direction = "input"
|
||||
mode.audio_volume.regex = "Microphone"
|
||||
mode.audio_volume.label.muted = "Muted"
|
||||
mode.audio_volume.icon.inactive = "@ph/microphone-slash-light[alpha=90|color=#fc4646]"
|
||||
mode.audio_volume.circle_indicator.color = "#ffffff"
|
||||
mode.audio_volume.circle_indicator.width = 2
|
||||
mode.audio_volume.circle_indicator.radius = 40
|
||||
mode.audio_volume.icon.inactive = "@ph/microphone-slash-light[alpha=0.9|color=#fc4646]"
|
||||
mode.audio_volume.disable_press_to_unmute = true
|
||||
mode.audio_volume.muted_turn_action = "unmute-at-zero"
|
||||
|
||||
[knobs.left-center]
|
||||
[knobs.left-middle]
|
||||
icon = "@apps/discord"
|
||||
indicator.bar.color = "#ffffff"
|
||||
|
||||
mode.audio_volume.regex = "Discord"
|
||||
mode.audio_volume.label.inactive = ""
|
||||
mode.audio_volume.label.active = "{percentage}%"
|
||||
mode.audio_volume.label.muted = "Muted"
|
||||
mode.audio_volume.icon.inactive = "@apps/discord[grayscale,opacity=90]"
|
||||
mode.audio_volume.bar_indicator.color = "#ffffff"
|
||||
mode.audio_volume.icon.inactive = "@apps/discord[grayscale|alpha=0.9]"
|
||||
|
||||
[knobs.left-bottom]
|
||||
icon = "@apps/spotify"
|
||||
indicator.bar.color = "#ffffff"
|
||||
|
||||
mode.audio_volume.regex = "Spotify"
|
||||
mode.audio_volume.label.inactive = ""
|
||||
mode.audio_volume.label.active = "{percentage}%"
|
||||
mode.audio_volume.label.muted = "Muted"
|
||||
mode.audio_volume.icon.inactive = "@apps/spotify[grayscale,opacity=90]"
|
||||
mode.audio_volume.bar_indicator.color = "#ffffff"
|
||||
mode.audio_volume.icon.inactive = "@apps/spotify[grayscale|alpha=0.9]"
|
|
@ -1,83 +1,109 @@
|
|||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use clap::{Parser, Subcommand};
|
||||
use color_eyre::eyre::{OptionExt, WrapErr};
|
||||
use color_eyre::Result;
|
||||
use rgb::RGB8;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use loupedeck_serial::commands::VibrationPattern;
|
||||
use loupedeck_serial::device::LoupedeckDevice;
|
||||
use loupedeck_serial::events::LoupedeckEvent;
|
||||
use crate::model::config::WithFallbackId;
|
||||
|
||||
mod model;
|
||||
mod runner;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let available_devices = LoupedeckDevice::discover()?;
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "deckster")]
|
||||
#[command(about = "Use Loupedeck device under Linux.")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
let device = available_devices.first().wrap_err("at least one device should be connected")?.connect()?;
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
Run {
|
||||
#[arg(long, required = true)]
|
||||
config: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
println!("Version: {}\nSerial number: {}", device.firmware_version(), device.serial_number());
|
||||
device.set_brightness(1.0);
|
||||
pub fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
let cli = Cli::parse();
|
||||
|
||||
// run_vibrations(&device)?;
|
||||
run_rainbow(&device)?;
|
||||
match cli.command {
|
||||
Command::Run { config: config_path } => {
|
||||
let deckster_file = read_and_deserialize::<model::config::File>(config_path.join("./deckster.toml").as_path())?;
|
||||
let config_path = config_path.canonicalize()?;
|
||||
|
||||
let key_pages_by_id: HashMap<String, model::key_page::Page> =
|
||||
read_and_deserialize_from_directory::<model::key_page::File>(config_path.join("./key-pages").as_path())?
|
||||
.into_iter()
|
||||
.map(|p| model::key_page::Page {
|
||||
id: p.inner.id.clone().unwrap_or(p.fallback_id),
|
||||
keys: p.inner.keys,
|
||||
scrolling: p.inner.scrolling,
|
||||
})
|
||||
.map(|p| (p.id.clone(), p))
|
||||
.collect();
|
||||
|
||||
let knob_pages_by_id: HashMap<String, model::knob_page::Page> =
|
||||
read_and_deserialize_from_directory::<model::knob_page::File>(config_path.join("./knob-pages").as_path())?
|
||||
.into_iter()
|
||||
.map(|p| model::knob_page::Page {
|
||||
id: p.inner.id.clone().unwrap_or(p.fallback_id),
|
||||
knobs: p.inner.knobs.into_iter().collect(),
|
||||
})
|
||||
.map(|p| (p.id.clone(), p))
|
||||
.collect();
|
||||
|
||||
let config = model::config::Config {
|
||||
key_pages_by_id,
|
||||
knob_pages_by_id,
|
||||
buttons: deckster_file.buttons.into_iter().collect(),
|
||||
icon_packs: deckster_file.icon_packs,
|
||||
initial: deckster_file.initial,
|
||||
active_button_color: deckster_file.active_button_color,
|
||||
inactive_button_color: deckster_file.inactive_button_color,
|
||||
};
|
||||
|
||||
runner::start(config)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_rainbow(device: &LoupedeckDevice) -> Result<()> {
|
||||
let interval = Duration::from_millis(50);
|
||||
let start = Instant::now();
|
||||
let mut iteration = 0;
|
||||
fn read_and_deserialize<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
|
||||
let content = fs::read_to_string(path).wrap_err_with(|| format!("Reading of {} failed.", path.to_string_lossy()))?;
|
||||
|
||||
let buttons = device
|
||||
.characteristics()
|
||||
.available_buttons
|
||||
.iter()
|
||||
.filter(|b| b.supports_color())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
'outer: loop {
|
||||
let ms = start.elapsed().as_millis() as u64;
|
||||
|
||||
if !device.events_channel().is_empty() {
|
||||
for event in device.events_channel() {
|
||||
if let LoupedeckEvent::Disconnected = event {
|
||||
break 'outer;
|
||||
}
|
||||
|
||||
if device.events_channel().is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (index, button) in buttons.iter().enumerate() {
|
||||
let t = (ms + (index * 100) as u64) as f32;
|
||||
|
||||
device.set_button_color(
|
||||
*button,
|
||||
RGB8::new(
|
||||
(((t / 1000.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||
(((t / 500.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||
(((t / 250.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
|
||||
sleep((iteration + 1) * interval - start.elapsed());
|
||||
iteration += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
toml::from_str::<T>(&content).wrap_err_with(|| format!("Parsing of {} failed.", path.to_string_lossy()))
|
||||
}
|
||||
|
||||
fn run_vibrations(device: &LoupedeckDevice) -> Result<()> {
|
||||
for event in device.events_channel() {
|
||||
if let LoupedeckEvent::Touch { is_end: false, .. } = event {
|
||||
device.vibrate(VibrationPattern::Low)
|
||||
fn read_and_deserialize_from_directory<T: serde::de::DeserializeOwned>(base: &Path) -> Result<Vec<WithFallbackId<T>>> {
|
||||
let mut result: Vec<WithFallbackId<T>> = Vec::new();
|
||||
|
||||
for entry in WalkDir::new(base).follow_links(true) {
|
||||
let entry = entry?;
|
||||
if !entry.file_type().is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = entry.path();
|
||||
if let Some(fallback_id) = path
|
||||
.strip_prefix(base)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.expect("Paths must be valid UTF-8")
|
||||
.strip_suffix(".toml")
|
||||
{
|
||||
result.push(WithFallbackId {
|
||||
fallback_id: fallback_id.to_owned(),
|
||||
inner: read_and_deserialize::<T>(path)?,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,39 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use enum_map::EnumMap;
|
||||
use rgb::RGB8;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::model;
|
||||
use crate::model::image_filter::ImageFilter;
|
||||
use crate::model::rgb::DeserializableRGB8;
|
||||
use crate::model::ButtonPosition;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct File {
|
||||
pub key_pages: Vec<String>,
|
||||
pub knob_pages: Vec<String>,
|
||||
pub icon_packs: Vec<IconPack>,
|
||||
#[serde(default = "inactive_button_color_default")]
|
||||
pub inactive_button_color: DeserializableRGB8,
|
||||
#[serde(default = "active_button_color_default")]
|
||||
pub active_button_color: DeserializableRGB8,
|
||||
pub buttons: [Option<ButtonConfig>; 8],
|
||||
pub buttons: HashMap<ButtonPosition, ButtonConfig>, // EnumMap
|
||||
pub initial: InitialConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WithFallbackId<T> {
|
||||
pub fallback_id: String,
|
||||
pub inner: T,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub key_pages_by_id: HashMap<String, model::key_page::Page>,
|
||||
pub knob_pages_by_id: HashMap<String, model::knob_page::Page>,
|
||||
pub icon_packs: Vec<IconPack>,
|
||||
pub inactive_button_color: DeserializableRGB8,
|
||||
pub active_button_color: DeserializableRGB8,
|
||||
pub buttons: EnumMap<ButtonPosition, ButtonConfig>,
|
||||
pub initial: InitialConfig,
|
||||
}
|
||||
|
||||
|
@ -25,7 +45,7 @@ fn active_button_color_default() -> DeserializableRGB8 {
|
|||
DeserializableRGB8(RGB8::new(0, 255, 0))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct ButtonConfig {
|
||||
pub key_page: Option<String>,
|
||||
pub knob_page: Option<String>,
|
|
@ -1,22 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::model::icon_descriptor::IconDescriptor;
|
||||
use crate::model::{knob_modes, KnobPosition};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct File {
|
||||
pub knobs: HashMap<KnobPosition, Knob>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Knob {
|
||||
pub icon: IconDescriptor,
|
||||
pub mode: KnobModes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct KnobModes {
|
||||
pub audio_volume: knob_modes::audio_volume::Config,
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub mod deckster;
|
||||
pub mod key_page;
|
||||
pub mod knob_page;
|
|
@ -6,7 +6,7 @@ use thiserror::Error;
|
|||
|
||||
use crate::model::image_filter::{ImageFilter, ImageFilterFromStringError};
|
||||
|
||||
#[derive(Debug, DeserializeFromStr)]
|
||||
#[derive(Debug, Default, Clone, DeserializeFromStr)]
|
||||
pub struct IconDescriptor {
|
||||
pub source: IconDescriptorSource,
|
||||
pub filter: Option<ImageFilter>,
|
||||
|
@ -30,7 +30,7 @@ impl FromStr for IconDescriptor {
|
|||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (raw_source, raw_filter) = s.split_once('[').unwrap_or((s, ""));
|
||||
|
||||
let source = if raw_source.starts_with('@') {
|
||||
let source = if let Some(raw_source) = raw_source.strip_prefix('@') {
|
||||
let (pack_id, icon_id) = raw_source
|
||||
.split_once('/')
|
||||
.ok_or_else(|| IconDescriptorFromStrError::InvalidIconPackSource(raw_source.to_string()))?;
|
||||
|
@ -57,8 +57,13 @@ impl FromStr for IconDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum IconDescriptorSource {
|
||||
IconPack { pack_id: String, icon_id: String },
|
||||
#[default]
|
||||
None,
|
||||
IconPack {
|
||||
pack_id: String,
|
||||
icon_id: String,
|
||||
},
|
||||
Path(PathBuf),
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use thiserror::Error;
|
|||
use crate::model::geometry::parse_positive_rect_from_str;
|
||||
use crate::model::rgb::parse_rgb8_from_hex_str;
|
||||
|
||||
#[derive(Debug, DeserializeFromStr)]
|
||||
#[derive(Debug, Clone, DeserializeFromStr)]
|
||||
pub struct ImageFilter {
|
||||
pub crop_original: Option<Rect>, // applied before scale and rotate
|
||||
pub scale: f32,
|
||||
|
|
|
@ -8,7 +8,15 @@ use crate::model::{key_modes, KnobPosition};
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct File {
|
||||
pub scrolling: ScrollingConfig,
|
||||
pub id: Option<String>,
|
||||
pub scrolling: Option<ScrollingConfig>,
|
||||
pub keys: HashMap<UIntVec2, KeyConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Page {
|
||||
pub id: String,
|
||||
pub scrolling: Option<ScrollingConfig>,
|
||||
pub keys: HashMap<UIntVec2, KeyConfig>,
|
||||
}
|
||||
|
||||
|
@ -27,7 +35,8 @@ pub struct ScrollingKeysConfig {
|
|||
pub next: UIntVec2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[derive(Debug, Default, Eq, PartialEq, Hash, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ScrollingConfigAxis {
|
||||
#[default]
|
||||
Vertical,
|
55
deckster/src/model/knob_page.rs
Normal file
55
deckster/src/model/knob_page.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use enum_map::EnumMap;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::model::icon_descriptor::IconDescriptor;
|
||||
use crate::model::rgb::DeserializableRGB8WithOptionalAlpha;
|
||||
use crate::model::{knob_modes, KnobPosition};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct File {
|
||||
pub id: Option<String>,
|
||||
pub knobs: HashMap<KnobPosition, Knob>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Page {
|
||||
pub id: String,
|
||||
pub knobs: EnumMap<KnobPosition, Knob>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct Knob {
|
||||
#[serde(default)]
|
||||
pub label: String,
|
||||
#[serde(default)]
|
||||
pub icon: IconDescriptor,
|
||||
#[serde(default)]
|
||||
pub indicator: KnobIndicators,
|
||||
#[serde(default)]
|
||||
pub mode: KnobModes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct KnobIndicators {
|
||||
pub bar: Option<KnobIndicatorBarConfig>,
|
||||
pub circle: Option<KnobIndicatorCircleConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct KnobIndicatorBarConfig {
|
||||
pub color: DeserializableRGB8WithOptionalAlpha,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct KnobIndicatorCircleConfig {
|
||||
pub color: DeserializableRGB8WithOptionalAlpha,
|
||||
pub width: u8,
|
||||
pub radius: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct KnobModes {
|
||||
pub audio_volume: Option<knob_modes::audio_volume::Config>,
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
use enum_map::Enum;
|
||||
use serde::Deserialize;
|
||||
|
||||
mod files;
|
||||
mod geometry;
|
||||
mod icon_descriptor;
|
||||
mod image_filter;
|
||||
mod key_modes;
|
||||
mod knob_modes;
|
||||
mod rgb;
|
||||
pub mod config;
|
||||
pub mod geometry;
|
||||
pub mod icon_descriptor;
|
||||
pub mod image_filter;
|
||||
pub mod key_modes;
|
||||
pub mod key_page;
|
||||
pub mod knob_modes;
|
||||
pub mod knob_page;
|
||||
pub mod rgb;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize, Enum)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum KnobPosition {
|
||||
LeftTop,
|
||||
|
@ -18,3 +21,23 @@ pub enum KnobPosition {
|
|||
RightMiddle,
|
||||
RightBottom,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize, Enum)]
|
||||
pub enum ButtonPosition {
|
||||
#[serde(rename = "0")]
|
||||
N0,
|
||||
#[serde(rename = "1")]
|
||||
N1,
|
||||
#[serde(rename = "2")]
|
||||
N2,
|
||||
#[serde(rename = "3")]
|
||||
N3,
|
||||
#[serde(rename = "4")]
|
||||
N4,
|
||||
#[serde(rename = "5")]
|
||||
N5,
|
||||
#[serde(rename = "6")]
|
||||
N6,
|
||||
#[serde(rename = "7")]
|
||||
N7,
|
||||
}
|
||||
|
|
6
deckster/src/runner/command.rs
Normal file
6
deckster/src/runner/command.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum RendererCommand {
|
||||
SetButton,
|
||||
}
|
72
deckster/src/runner/mod.rs
Normal file
72
deckster/src/runner/mod.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use color_eyre::eyre::{ContextCompat, WrapErr};
|
||||
use color_eyre::Result;
|
||||
|
||||
use loupedeck_serial::commands::VibrationPattern;
|
||||
use loupedeck_serial::device::LoupedeckDevice;
|
||||
use state::State;
|
||||
|
||||
use crate::model;
|
||||
|
||||
mod command;
|
||||
mod state;
|
||||
|
||||
pub fn start(config: model::config::Config) -> Result<()> {
|
||||
let state = create_state(&config)?;
|
||||
|
||||
let device = LoupedeckDevice::discover()?
|
||||
.first()
|
||||
.wrap_err("No device connected.")?
|
||||
.connect()
|
||||
.wrap_err("Connecting to the device failed.")?;
|
||||
|
||||
device.vibrate(VibrationPattern::RiseFall);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_state(config: &model::config::Config) -> Result<State> {
|
||||
let key_pages_by_id: HashMap<_, _> = config
|
||||
.key_pages_by_id
|
||||
.iter()
|
||||
.map(|(id, p)| state::KeyPage { id: id.clone() })
|
||||
.map(|p| (p.id.clone(), Rc::new(p)))
|
||||
.collect();
|
||||
|
||||
let knob_pages_by_id: HashMap<_, _> = config
|
||||
.knob_pages_by_id
|
||||
.iter()
|
||||
.map(|(id, p)| state::KnobPage {
|
||||
id: id.clone(),
|
||||
knobs_by_position: p
|
||||
.knobs
|
||||
.iter()
|
||||
.map(|(p, k)| state::Knob {
|
||||
id: p,
|
||||
label: k.label.clone(),
|
||||
icon: k.icon.clone(),
|
||||
value: 0.0,
|
||||
})
|
||||
.map(|k| (k.id, Some(k)))
|
||||
.collect(),
|
||||
})
|
||||
.map(|p| (p.id.clone(), Rc::new(p)))
|
||||
.collect();
|
||||
|
||||
Ok(State {
|
||||
active_key_page: Rc::clone(
|
||||
key_pages_by_id
|
||||
.get(&config.initial.key_page)
|
||||
.wrap_err_with(|| format!("There is no key page with the ID specified at initial.key_page: {}", &config.initial.key_page))?,
|
||||
),
|
||||
active_knob_page: Rc::clone(
|
||||
knob_pages_by_id
|
||||
.get(&config.initial.knob_page)
|
||||
.wrap_err_with(|| format!("There is no key page with the ID specified at initial.knob_page: {}", &config.initial.key_page))?,
|
||||
),
|
||||
key_pages_by_id,
|
||||
knob_pages_by_id,
|
||||
})
|
||||
}
|
34
deckster/src/runner/state/mod.rs
Normal file
34
deckster/src/runner/state/mod.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use enum_map::EnumMap;
|
||||
|
||||
use crate::model::icon_descriptor::IconDescriptor;
|
||||
use crate::model::KnobPosition;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
pub active_key_page: Rc<KeyPage>,
|
||||
pub active_knob_page: Rc<KnobPage>,
|
||||
pub key_pages_by_id: HashMap<String, Rc<KeyPage>>,
|
||||
pub knob_pages_by_id: HashMap<String, Rc<KnobPage>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KeyPage {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KnobPage {
|
||||
pub id: String,
|
||||
pub knobs_by_position: EnumMap<KnobPosition, Option<Knob>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Knob {
|
||||
pub id: KnobPosition,
|
||||
pub icon: IconDescriptor,
|
||||
pub label: String,
|
||||
pub value: f32,
|
||||
}
|
Loading…
Add table
Reference in a new issue