commit
This commit is contained in:
parent
474ecf4f5e
commit
228848444d
13 changed files with 685 additions and 228 deletions
372
Cargo.lock
generated
372
Cargo.lock
generated
|
@ -89,6 +89,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
@ -259,6 +265,36 @@ version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cosmic-text"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75acbfb314aeb4f5210d379af45ed1ec2c98c7f1790bf57b8a4c562ac0c51b71"
|
||||||
|
dependencies = [
|
||||||
|
"fontdb",
|
||||||
|
"libm",
|
||||||
|
"log",
|
||||||
|
"rangemap",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustybuzz",
|
||||||
|
"self_cell",
|
||||||
|
"swash",
|
||||||
|
"sys-locale",
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-linebreak",
|
||||||
|
"unicode-script",
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.3"
|
version = "0.20.3"
|
||||||
|
@ -298,8 +334,10 @@ dependencies = [
|
||||||
name = "deckster"
|
name = "deckster"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
|
"cosmic-text",
|
||||||
"enum-map",
|
"enum-map",
|
||||||
"enum-ordinalize",
|
"enum-ordinalize",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -307,13 +345,13 @@ dependencies = [
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"log",
|
"log",
|
||||||
"loupedeck_serial",
|
"loupedeck_serial",
|
||||||
"piet",
|
|
||||||
"regex",
|
"regex",
|
||||||
"rgb",
|
"rgb",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_regex",
|
"serde_regex",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tiny-skia",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
@ -429,6 +467,25 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -447,6 +504,29 @@ version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fontconfig-parser"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "674e258f4b5d2dcd63888c01c68413c51f565e8af99d2f7701c7b81d79ef41c4"
|
||||||
|
dependencies = [
|
||||||
|
"roxmltree",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fontdb"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "020e203f177c0fb250fb19455a252e838d2bbbce1f80f25ecc42402aafa8cd38"
|
||||||
|
dependencies = [
|
||||||
|
"fontconfig-parser",
|
||||||
|
"log",
|
||||||
|
"memmap2",
|
||||||
|
"slotmap",
|
||||||
|
"tinyvec",
|
||||||
|
"ttf-parser 0.19.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
@ -617,15 +697,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "kurbo"
|
|
||||||
version = "0.9.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b"
|
|
||||||
dependencies = [
|
|
||||||
"arrayvec",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -638,6 +709,12 @@ version = "0.2.151"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libudev"
|
name = "libudev"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -702,18 +779,21 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "matches"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.6.4"
|
version = "2.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memmap2"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -721,6 +801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -806,16 +887,6 @@ dependencies = [
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "piet"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e381186490a3e2017a506d62b759ea8eaf4be14666b13ed53973e8ae193451b1"
|
|
||||||
dependencies = [
|
|
||||||
"kurbo",
|
|
||||||
"unic-bidi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
|
@ -828,6 +899,19 @@ version = "0.3.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
|
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.17.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -852,6 +936,12 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rangemap"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -899,12 +989,27 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roxmltree"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302"
|
||||||
|
dependencies = [
|
||||||
|
"xmlparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.28"
|
version = "0.38.28"
|
||||||
|
@ -918,6 +1023,23 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustybuzz"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ee8fe2a8461a0854a37101fe7a1b13998d0cfa987e43248e81d2a5f4570f6fa"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"bytemuck",
|
||||||
|
"libm",
|
||||||
|
"smallvec",
|
||||||
|
"ttf-parser 0.20.0",
|
||||||
|
"unicode-bidi-mirroring",
|
||||||
|
"unicode-ccc",
|
||||||
|
"unicode-properties",
|
||||||
|
"unicode-script",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
|
@ -939,6 +1061,12 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "self_cell"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.193"
|
version = "1.0.193"
|
||||||
|
@ -1046,6 +1174,21 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slotmap"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.11.2"
|
version = "1.11.2"
|
||||||
|
@ -1061,12 +1204,28 @@ dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strict-num"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swash"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b7c73c813353c347272919aa1af2885068b05e625e5532b43049e4f641ae77f"
|
||||||
|
dependencies = [
|
||||||
|
"yazi",
|
||||||
|
"zeno",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.43"
|
version = "2.0.43"
|
||||||
|
@ -1078,6 +1237,15 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sys-locale"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1146,6 +1314,47 @@ dependencies = [
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6a067b809476893fce6a254cf285850ff69c847e6cfbade6a20b655b6c7e80d"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"bytemuck",
|
||||||
|
"cfg-if",
|
||||||
|
"log",
|
||||||
|
"png",
|
||||||
|
"tiny-skia-path",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia-path"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5de35e8a90052baaaf61f171680ac2f8e925a1e43ea9d2e3a00514772250e541"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"bytemuck",
|
||||||
|
"strict-num",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.35.1"
|
version = "1.35.1"
|
||||||
|
@ -1245,6 +1454,18 @@ dependencies = [
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ttf-parser"
|
||||||
|
version = "0.19.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ttf-parser"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unescaper"
|
name = "unescaper"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -1255,55 +1476,22 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unic-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.9.0"
|
version = "0.3.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09"
|
checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
|
||||||
dependencies = [
|
|
||||||
"matches",
|
|
||||||
"unic-ucd-bidi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unic-char-property"
|
name = "unicode-bidi-mirroring"
|
||||||
version = "0.9.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
|
checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694"
|
||||||
dependencies = [
|
|
||||||
"unic-char-range",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unic-char-range"
|
name = "unicode-ccc"
|
||||||
version = "0.9.0"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
|
checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-common"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-ucd-bidi"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c"
|
|
||||||
dependencies = [
|
|
||||||
"unic-char-property",
|
|
||||||
"unic-char-range",
|
|
||||||
"unic-ucd-version",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unic-ucd-version"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
|
|
||||||
dependencies = [
|
|
||||||
"unic-common",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
|
@ -1311,6 +1499,30 @@ version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-linebreak"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-properties"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7f91c8b21fbbaa18853c3d0801c78f4fc94cdb976699bb03e832e75f7fd22f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-script"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -1323,6 +1535,12 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
|
@ -1564,3 +1782,21 @@ checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xmlparser"
|
||||||
|
version = "0.13.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yazi"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeno"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
|
||||||
|
|
|
@ -4,22 +4,24 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bytes = "1.5.0"
|
||||||
|
clap = { version = "4.4.12", features = ["derive"] }
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
|
cosmic-text = "0.10.0"
|
||||||
|
enum-map = "3.0.0-beta.2"
|
||||||
|
enum-ordinalize = "4.3.0"
|
||||||
|
env_logger = "0.10.1"
|
||||||
|
flume = "0.11.0"
|
||||||
humantime-serde = "1.1.1"
|
humantime-serde = "1.1.1"
|
||||||
|
log = "0.4.20"
|
||||||
loupedeck_serial = { path = "../loupedeck_serial" }
|
loupedeck_serial = { path = "../loupedeck_serial" }
|
||||||
|
regex = "1.10.2"
|
||||||
rgb = "0.8.37"
|
rgb = "0.8.37"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
serde_regex = "1.1.0"
|
serde_regex = "1.1.0"
|
||||||
serde_with = "3.4.0"
|
serde_with = "3.4.0"
|
||||||
thiserror = "1.0.52"
|
thiserror = "1.0.52"
|
||||||
toml = "0.8.8"
|
tiny-skia = "0.11.3"
|
||||||
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"
|
|
||||||
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "rt-multi-thread", "sync"]}
|
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "rt-multi-thread", "sync"]}
|
||||||
flume = "0.11.0"
|
toml = "0.8.8"
|
||||||
enum-ordinalize = "4.3.0"
|
walkdir = "2.4.0"
|
||||||
piet = "0.6.2"
|
|
|
@ -1,20 +1,14 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use piet::kurbo::{Rect, Vec2};
|
|
||||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use tiny_skia::IntRect;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, SerializeDisplay, DeserializeFromStr)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, SerializeDisplay, DeserializeFromStr)]
|
||||||
pub struct UIntVec2 {
|
pub struct UIntVec2 {
|
||||||
x: u64,
|
pub x: u32,
|
||||||
y: u64,
|
pub y: u32,
|
||||||
}
|
|
||||||
|
|
||||||
impl UIntVec2 {
|
|
||||||
fn to_kurbo_vec2(&self) -> Vec2 {
|
|
||||||
Vec2::new(self.x as f64, self.y as f64)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -28,8 +22,8 @@ impl FromStr for UIntVec2 {
|
||||||
let values = s.split_once('x');
|
let values = s.split_once('x');
|
||||||
|
|
||||||
if let Some((x, y)) = values {
|
if let Some((x, y)) = values {
|
||||||
if let Ok(x) = u64::from_str(x) {
|
if let Ok(x) = u32::from_str(x) {
|
||||||
if let Ok(y) = u64::from_str(y) {
|
if let Ok(y) = u32::from_str(y) {
|
||||||
return Ok(UIntVec2 { x, y });
|
return Ok(UIntVec2 { x, y });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +39,7 @@ impl Display for UIntVec2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_positive_rect_from_str(s: &str) -> Result<Rect, ()> {
|
pub fn parse_positive_int_rect_from_str(s: &str) -> Result<IntRect, ()> {
|
||||||
let (pairs, is_corner_points_mode) = if let Some(pairs) = s.split_once('+') {
|
let (pairs, is_corner_points_mode) = if let Some(pairs) = s.split_once('+') {
|
||||||
(pairs, false)
|
(pairs, false)
|
||||||
} else if let Some(pairs) = s.split_once('-') {
|
} else if let Some(pairs) = s.split_once('-') {
|
||||||
|
@ -54,16 +48,16 @@ pub fn parse_positive_rect_from_str(s: &str) -> Result<Rect, ()> {
|
||||||
return Err(());
|
return Err(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let first_vec = UIntVec2::from_str(pairs.0).map_err(|_| ())?.to_kurbo_vec2();
|
let first_vec = UIntVec2::from_str(pairs.0).map_err(|_| ())?;
|
||||||
let second_vec = UIntVec2::from_str(pairs.1).map_err(|_| ())?.to_kurbo_vec2();
|
let second_vec = UIntVec2::from_str(pairs.1).map_err(|_| ())?;
|
||||||
|
|
||||||
Ok(if is_corner_points_mode {
|
Ok(if is_corner_points_mode {
|
||||||
Rect::from_points(first_vec.to_point(), second_vec.to_point())
|
IntRect::from_ltrb(first_vec.x as i32, first_vec.y as i32, second_vec.x as i32, second_vec.y as i32).unwrap()
|
||||||
} else {
|
} else {
|
||||||
Rect::from_origin_size(first_vec.to_point(), second_vec.to_size())
|
IntRect::from_xywh(first_vec.x as i32, first_vec.y as i32, second_vec.x, second_vec.y).unwrap()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_positive_rect(rect: &Rect, f: &mut Formatter<'_>) -> std::fmt::Result {
|
pub fn fmt_positive_rect(rect: &IntRect, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_fmt(format_args!("{}x{}-{}x{}", rect.x0, rect.x1, rect.y0, rect.y1))
|
f.write_fmt(format_args!("{}x{}+{}x{}", rect.x(), rect.y(), rect.width(), rect.height()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use piet::kurbo::Rect;
|
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use tiny_skia::IntRect;
|
||||||
|
|
||||||
use crate::model::geometry::{fmt_positive_rect, parse_positive_rect_from_str};
|
use crate::model::geometry::{fmt_positive_rect, parse_positive_int_rect_from_str};
|
||||||
use crate::model::rgb::{fmt_rgb8_as_hex_string, parse_rgb8_from_hex_str};
|
use crate::model::rgb::{fmt_rgb8_as_hex_string, parse_rgb8_from_hex_str};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, SerializeDisplay, DeserializeFromStr)]
|
#[derive(Debug, PartialEq, Clone, SerializeDisplay, DeserializeFromStr)]
|
||||||
pub struct ImageFilter {
|
pub struct ImageFilter {
|
||||||
pub crop_original: Option<Rect>, // applied before scale and rotate
|
pub crop_original: Option<IntRect>, // applied before scale and rotate
|
||||||
pub scale: f32,
|
pub scale: f32,
|
||||||
pub clockwise_quarter_rotations: u8,
|
pub clockwise_quarter_rotations: u8,
|
||||||
pub crop: Option<Rect>, // applied after scale and rotate
|
pub crop: Option<IntRect>, // applied after scale and rotate
|
||||||
pub color: Option<RGB8>,
|
pub color: Option<RGB8>,
|
||||||
pub alpha: f32,
|
pub alpha: f32,
|
||||||
pub blur: f32,
|
pub blur: f32,
|
||||||
|
@ -68,8 +68,8 @@ impl FromStr for ImageFilter {
|
||||||
type Err = ImageFilterFromStringError;
|
type Err = ImageFilterFromStringError;
|
||||||
|
|
||||||
fn from_str<'a>(s: &str) -> Result<Self, Self::Err> {
|
fn from_str<'a>(s: &str) -> Result<Self, Self::Err> {
|
||||||
fn parse_rect_filter_value(filter_name: &str, raw_value: String) -> Result<Rect, ImageFilterFromStringError> {
|
fn parse_rect_filter_value(filter_name: &str, raw_value: String) -> Result<IntRect, ImageFilterFromStringError> {
|
||||||
parse_positive_rect_from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable {
|
parse_positive_int_rect_from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable {
|
||||||
filter_name: filter_name.to_string(),
|
filter_name: filter_name.to_string(),
|
||||||
raw_value,
|
raw_value,
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,9 +19,10 @@ pub mod knob_page;
|
||||||
pub mod rgb;
|
pub mod rgb;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, SerializeDisplay, DeserializeFromStr)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, SerializeDisplay, DeserializeFromStr)]
|
||||||
|
/// One-based coordinates of a specific virtual (not physical) key.
|
||||||
pub struct KeyPosition {
|
pub struct KeyPosition {
|
||||||
x: u16,
|
pub x: u16,
|
||||||
y: u16,
|
pub y: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -52,6 +53,12 @@ impl Display for KeyPosition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct KeyPath {
|
||||||
|
pub page_id: String,
|
||||||
|
pub position: KeyPosition,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize, Enum)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize, Enum)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum KnobPosition {
|
pub enum KnobPosition {
|
||||||
|
@ -63,6 +70,12 @@ pub enum KnobPosition {
|
||||||
RightBottom,
|
RightBottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct KnobPath {
|
||||||
|
pub page_id: String,
|
||||||
|
pub position: KnobPosition,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize, Enum)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize, Enum)]
|
||||||
pub enum ButtonPosition {
|
pub enum ButtonPosition {
|
||||||
#[serde(rename = "0")]
|
#[serde(rename = "0")]
|
||||||
|
|
|
@ -60,7 +60,7 @@ impl FromStr for SerializableRGB8 {
|
||||||
type Err = RGBParsingError;
|
type Err = RGBParsingError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
parse_rgb8_from_hex_str(s).map(|v| SerializableRGB8(v))
|
parse_rgb8_from_hex_str(s).map(SerializableRGB8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ impl FromStr for SerializableRGBA8 {
|
||||||
type Err = RGBParsingError;
|
type Err = RGBParsingError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
parse_rgba8_from_hex_str(s).map(|v| SerializableRGBA8(v))
|
parse_rgba8_from_hex_str(s).map(SerializableRGBA8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ impl FromStr for SerializableRGB8WithOptionalAlpha {
|
||||||
type Err = RGBParsingError;
|
type Err = RGBParsingError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
parse_rgb8_with_optional_alpha_from_hex_str(s, 0xff).map(|v| SerializableRGB8WithOptionalAlpha(v))
|
parse_rgb8_with_optional_alpha_from_hex_str(s, 0xff).map(SerializableRGB8WithOptionalAlpha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
deckster/src/runner/graphics.rs
Normal file
42
deckster/src/runner/graphics.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use tiny_skia::{Color, Pixmap, PremultipliedColorU8};
|
||||||
|
|
||||||
|
use loupedeck_serial::util::Endianness;
|
||||||
|
|
||||||
|
use crate::runner::state::Key;
|
||||||
|
|
||||||
|
pub fn render_key(width: u16, height: u16, buffer_endianness: Endianness, state: Option<&Key>) -> Bytes {
|
||||||
|
let mut canvas = Pixmap::new(width as u32, height as u32).unwrap();
|
||||||
|
|
||||||
|
if let Some(state) = state {
|
||||||
|
canvas.fill(if state.label.is_empty() {
|
||||||
|
Color::WHITE
|
||||||
|
} else {
|
||||||
|
Color::from_rgba8(0, 255, 0, 255)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
canvas.fill(Color::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_pixels_to_rgb565(canvas.pixels(), buffer_endianness).freeze()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_pixels_to_rgb565(pixels: &[PremultipliedColorU8], endianness: Endianness) -> BytesMut {
|
||||||
|
let pixel_count = pixels.len();
|
||||||
|
|
||||||
|
let mut result = BytesMut::with_capacity(pixel_count * 2);
|
||||||
|
|
||||||
|
for pixel in pixels {
|
||||||
|
let red = pixel.red() as u16;
|
||||||
|
let green = pixel.green() as u16;
|
||||||
|
let blue = pixel.blue() as u16;
|
||||||
|
let color = ((red & 0b11111000) << 8) | ((green & 0b11111100) << 3) | (blue.wrapping_shr(3));
|
||||||
|
|
||||||
|
match endianness {
|
||||||
|
Endianness::LittleEndian => result.put_u16_le(color),
|
||||||
|
Endianness::BigEndian => result.put_u16(color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
|
@ -7,19 +7,21 @@ use color_eyre::Result;
|
||||||
use enum_map::EnumMap;
|
use enum_map::EnumMap;
|
||||||
use enum_ordinalize::Ordinalize;
|
use enum_ordinalize::Ordinalize;
|
||||||
use flume::{Receiver, Sender};
|
use flume::{Receiver, Sender};
|
||||||
use log::{debug, info, trace};
|
use log::{debug, trace};
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
|
|
||||||
use loupedeck_serial::characteristics::LoupedeckButton;
|
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics};
|
||||||
use loupedeck_serial::commands::VibrationPattern;
|
use loupedeck_serial::commands::VibrationPattern;
|
||||||
use loupedeck_serial::device::LoupedeckDevice;
|
use loupedeck_serial::device::LoupedeckDevice;
|
||||||
use loupedeck_serial::events::LoupedeckEvent;
|
use loupedeck_serial::events::LoupedeckEvent;
|
||||||
|
|
||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::model::icon_descriptor::IconDescriptor;
|
use crate::model::icon_descriptor::IconDescriptor;
|
||||||
use crate::model::ButtonPosition;
|
use crate::model::{ButtonPosition, KeyPath, KeyPosition, KnobPath};
|
||||||
use crate::runner::state::{State, StateChangeCommand};
|
use crate::runner::graphics::render_key;
|
||||||
|
use crate::runner::state::{Key, State, StateChangeCommand};
|
||||||
|
|
||||||
|
mod graphics;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
pub async fn start(config: model::config::Config) -> Result<()> {
|
pub async fn start(config: model::config::Config) -> Result<()> {
|
||||||
|
@ -35,6 +37,13 @@ pub async fn start(config: model::config::Config) -> Result<()> {
|
||||||
let events_receiver = device.events();
|
let events_receiver = device.events();
|
||||||
let (commands_sender, commands_receiver) = flume::bounded::<StateChangeCommand>(20);
|
let (commands_sender, commands_receiver) = flume::bounded::<StateChangeCommand>(20);
|
||||||
|
|
||||||
|
commands_sender
|
||||||
|
.send(StateChangeCommand::SetActivePages {
|
||||||
|
knob_page_id: config.initial.knob_page.clone(),
|
||||||
|
key_page_id: config.initial.key_page.clone(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let cloned_config = Arc::clone(&config);
|
let cloned_config = Arc::clone(&config);
|
||||||
let cloned_commands_sender = commands_sender.clone();
|
let cloned_commands_sender = commands_sender.clone();
|
||||||
let io_worker_thread = thread::Builder::new()
|
let io_worker_thread = thread::Builder::new()
|
||||||
|
@ -42,8 +51,6 @@ pub async fn start(config: model::config::Config) -> Result<()> {
|
||||||
.spawn(move || do_io_work(cloned_config, device, events_receiver, cloned_commands_sender, commands_receiver))
|
.spawn(move || do_io_work(cloned_config, device, events_receiver, cloned_commands_sender, commands_receiver))
|
||||||
.wrap_err("Could not spawn the worker thread")?;
|
.wrap_err("Could not spawn the worker thread")?;
|
||||||
|
|
||||||
commands_sender.send(StateChangeCommand::RefreshButtonColors).unwrap();
|
|
||||||
|
|
||||||
io_worker_thread.join().unwrap();
|
io_worker_thread.join().unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -58,12 +65,15 @@ fn create_state(config: &model::config::Config) -> State {
|
||||||
keys_by_position: p
|
keys_by_position: p
|
||||||
.keys
|
.keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(position, k)| state::Key {
|
.map(|(position, k)| Key {
|
||||||
position: *position,
|
path: KeyPath {
|
||||||
|
page_id: p.id.clone(),
|
||||||
|
position: *position,
|
||||||
|
},
|
||||||
label: k.label.clone().unwrap_or_default(),
|
label: k.label.clone().unwrap_or_default(),
|
||||||
icon: IconDescriptor::default(),
|
icon: IconDescriptor::default(),
|
||||||
})
|
})
|
||||||
.map(|k| (k.position, k))
|
.map(|k| (k.path.position, k))
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
})
|
||||||
.map(|p| (p.id.clone(), p))
|
.map(|p| (p.id.clone(), p))
|
||||||
|
@ -78,7 +88,10 @@ fn create_state(config: &model::config::Config) -> State {
|
||||||
let knob_config = &p.knobs[position];
|
let knob_config = &p.knobs[position];
|
||||||
|
|
||||||
state::Knob {
|
state::Knob {
|
||||||
position,
|
path: KnobPath {
|
||||||
|
page_id: p.id.clone(),
|
||||||
|
position,
|
||||||
|
},
|
||||||
label: knob_config.label.clone(),
|
label: knob_config.label.clone(),
|
||||||
icon: knob_config.icon.0.clone(),
|
icon: knob_config.icon.0.clone(),
|
||||||
value: 0.0,
|
value: 0.0,
|
||||||
|
@ -118,7 +131,7 @@ fn do_io_work(
|
||||||
|
|
||||||
match a {
|
match a {
|
||||||
IoWork::Event(event) => {
|
IoWork::Event(event) => {
|
||||||
if !handle_event(&config, &mut state, &commands_sender, event) {
|
if !handle_event(&config, &state, &device, &commands_sender, event) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +140,13 @@ fn do_io_work(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(config: &model::config::Config, state: &mut State, commands_sender: &Sender<StateChangeCommand>, event: LoupedeckEvent) -> bool {
|
fn handle_event(
|
||||||
|
config: &model::config::Config,
|
||||||
|
state: &State,
|
||||||
|
device: &LoupedeckDevice,
|
||||||
|
commands_sender: &Sender<StateChangeCommand>,
|
||||||
|
event: LoupedeckEvent,
|
||||||
|
) -> bool {
|
||||||
trace!("Handling event: {:?}", &event);
|
trace!("Handling event: {:?}", &event);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
@ -135,23 +154,43 @@ fn handle_event(config: &model::config::Config, state: &mut State, commands_send
|
||||||
LoupedeckEvent::ButtonDown { button } => {
|
LoupedeckEvent::ButtonDown { button } => {
|
||||||
let position = ButtonPosition::of(&button);
|
let position = ButtonPosition::of(&button);
|
||||||
let button_config = &config.buttons[position];
|
let button_config = &config.buttons[position];
|
||||||
let mut did_change = false;
|
|
||||||
|
|
||||||
if let Some(key_page) = &button_config.key_page {
|
commands_sender
|
||||||
did_change = true;
|
.send(StateChangeCommand::SetActivePages {
|
||||||
info!("Switching to key page: {}", key_page);
|
key_page_id: button_config.key_page.as_ref().unwrap_or(&state.active_key_page_id).clone(),
|
||||||
|
knob_page_id: button_config.knob_page.as_ref().unwrap_or(&state.active_knob_page_id).clone(),
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
LoupedeckEvent::Touch { x, y, is_end, .. } => {
|
||||||
|
if is_end {
|
||||||
|
let characteristics = device.characteristics();
|
||||||
|
let display = characteristics.get_display_at_coordinates(x, y);
|
||||||
|
|
||||||
state.active_key_page_id = key_page.clone();
|
if let Some(display) = display {
|
||||||
}
|
if display.name == characteristics.key_grid.display.name {
|
||||||
|
let key_index = characteristics.key_grid.get_key_at_global_coordinates(x, y);
|
||||||
|
if let Some(key_index) = key_index {
|
||||||
|
let position = KeyPosition {
|
||||||
|
x: (key_index % characteristics.key_grid.columns) as u16 + 1,
|
||||||
|
y: (key_index / characteristics.key_grid.columns) as u16 + 1,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(knob_page) = &button_config.knob_page {
|
let path = KeyPath {
|
||||||
did_change = true;
|
page_id: state.active_key_page_id.clone(),
|
||||||
info!("Switching to knob page: {}", knob_page);
|
position,
|
||||||
state.active_knob_page_id = knob_page.clone();
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if did_change {
|
let value = if state.get_key(&path).label.is_empty() {
|
||||||
commands_sender.send(StateChangeCommand::RefreshButtonColors).unwrap()
|
"a".to_owned()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
commands_sender.send(StateChangeCommand::SetKeyLabel { path, value }).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -164,32 +203,46 @@ fn handle_command(config: &model::config::Config, state: &mut State, device: &Lo
|
||||||
debug!("Handling command: {:?}", &command);
|
debug!("Handling command: {:?}", &command);
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
StateChangeCommand::RefreshButtonColors => {
|
StateChangeCommand::SetActivePages { key_page_id, knob_page_id } => {
|
||||||
|
state.active_key_page_id = key_page_id;
|
||||||
|
state.active_knob_page_id = knob_page_id;
|
||||||
|
|
||||||
for button in LoupedeckButton::VARIANTS {
|
for button in LoupedeckButton::VARIANTS {
|
||||||
let position = ButtonPosition::of(button);
|
let position = ButtonPosition::of(button);
|
||||||
|
|
||||||
device.set_button_color(*button, get_correct_button_color(config, state, position)).unwrap();
|
device.set_button_color(*button, get_correct_button_color(config, state, position)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let key_grid = &device.characteristics().key_grid;
|
||||||
|
for index in 0..(key_grid.rows * key_grid.columns) {
|
||||||
|
draw_key_at_index(device, state, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
device.refresh_display(&key_grid.display).unwrap();
|
||||||
}
|
}
|
||||||
StateChangeCommand::SetKeyLabel { page_id, key_position, value } => {
|
StateChangeCommand::SetKeyLabel { path, value } => {
|
||||||
state.mutate_key_for_command(
|
state.mutate_key_for_command(
|
||||||
"SetKeyLabel",
|
"SetKeyLabel",
|
||||||
&page_id,
|
&path,
|
||||||
&key_position,
|
|
||||||
Box::new(|k| {
|
Box::new(|k| {
|
||||||
k.label = value;
|
k.label = value;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
draw_key_at_path_if_visible(device, state, path);
|
||||||
|
device.refresh_display(&device.characteristics().key_grid.display).unwrap();
|
||||||
}
|
}
|
||||||
StateChangeCommand::SetKeyIcon { page_id, key_position, value } => {
|
StateChangeCommand::SetKeyIcon { path, value } => {
|
||||||
state.mutate_key_for_command(
|
state.mutate_key_for_command(
|
||||||
"SetKeyIcon",
|
"SetKeyIcon",
|
||||||
&page_id,
|
&path,
|
||||||
&key_position,
|
|
||||||
Box::new(|k| {
|
Box::new(|k| {
|
||||||
k.icon = value;
|
k.icon = value;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
draw_key_at_path_if_visible(device, state, path);
|
||||||
|
device.refresh_display(&device.characteristics().key_grid.display).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,3 +267,53 @@ fn get_correct_button_color(config: &model::config::Config, state: &mut State, b
|
||||||
|
|
||||||
config.inactive_button_color.0
|
config.inactive_button_color.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_key_index_for_position(key_grid: &LoupedeckDeviceKeyGridCharacteristics, position: KeyPosition) -> Option<u8> {
|
||||||
|
if (position.x - 1) >= key_grid.columns as u16 || (position.y - 1) >= key_grid.rows as u16 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let x = (position.x - 1) as u8;
|
||||||
|
let y = (position.y - 1) as u8;
|
||||||
|
Some(y * key_grid.columns + x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key_position_for_index(key_grid: &LoupedeckDeviceKeyGridCharacteristics, index: u8) -> KeyPosition {
|
||||||
|
let x = index % key_grid.columns;
|
||||||
|
let y = index / key_grid.columns;
|
||||||
|
|
||||||
|
KeyPosition {
|
||||||
|
x: (x + 1) as u16,
|
||||||
|
y: (y + 1) as u16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_key(device: &LoupedeckDevice, key_grid: &LoupedeckDeviceKeyGridCharacteristics, index: u8, key: Option<&Key>) {
|
||||||
|
let (x, y, w, h) = key_grid.get_local_key_rect_xywh(index).unwrap();
|
||||||
|
|
||||||
|
device
|
||||||
|
.replace_framebuffer_area_raw(&key_grid.display, x, y, w, h, render_key(w, h, key_grid.display.endianness, key))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_key_at_index(device: &LoupedeckDevice, state: &State, index: u8) {
|
||||||
|
let key_grid = &device.characteristics().key_grid;
|
||||||
|
let position = get_key_position_for_index(key_grid, index);
|
||||||
|
|
||||||
|
draw_key(device, key_grid, index, state.active_key_page().keys_by_position.get(&position));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_key_at_position_if_visible(device: &LoupedeckDevice, state: &State, position: KeyPosition) {
|
||||||
|
let key_grid = &device.characteristics().key_grid;
|
||||||
|
let index = get_key_index_for_position(key_grid, position);
|
||||||
|
|
||||||
|
if let Some(index) = index {
|
||||||
|
draw_key(device, key_grid, index, state.active_key_page().keys_by_position.get(&position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_key_at_path_if_visible(device: &LoupedeckDevice, state: &State, path: KeyPath) {
|
||||||
|
if state.active_key_page_id == path.page_id {
|
||||||
|
draw_key_at_position_if_visible(device, state, path.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use log::error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::model::icon_descriptor::IconDescriptor;
|
use crate::model::icon_descriptor::IconDescriptor;
|
||||||
use crate::model::{KeyPosition, KnobPosition};
|
use crate::model::{KeyPath, KeyPosition, KnobPath, KnobPosition};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
@ -16,23 +16,29 @@ pub struct State {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn mutate_key_for_command<R>(
|
pub fn mutate_key_for_command<R>(&mut self, command_name: &'static str, path: &KeyPath, mutator: Box<dyn FnOnce(&mut Key) -> R>) -> Option<R> {
|
||||||
&mut self,
|
match self.key_pages_by_id.get_mut(&path.page_id) {
|
||||||
command_name: &'static str,
|
None => error!("Received {} command with invalid path.page_id: {}", command_name, &path.page_id),
|
||||||
page_id: &String,
|
Some(key_page) => match key_page.keys_by_position.get_mut(&path.position) {
|
||||||
key_position: &KeyPosition,
|
None => error!("Received {} command with invalid path.position: {}", command_name, &path.position),
|
||||||
mutator: Box<dyn FnOnce(&mut Key) -> R>,
|
|
||||||
) -> Option<R> {
|
|
||||||
match self.key_pages_by_id.get_mut(page_id) {
|
|
||||||
None => error!("Received {} command with invalid page_id: {}", command_name, page_id),
|
|
||||||
Some(key_page) => match key_page.keys_by_position.get_mut(&key_position) {
|
|
||||||
None => error!("Received {} command with invalid key_position: {}", command_name, key_position),
|
|
||||||
Some(key) => return Some(mutator(key)),
|
Some(key) => return Some(mutator(key)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_key(&self, path: &KeyPath) -> &Key {
|
||||||
|
&self.key_pages_by_id[&path.page_id].keys_by_position[&path.position]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_key_page(&self) -> &KeyPage {
|
||||||
|
&self.key_pages_by_id[&self.active_key_page_id]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_knob_page(&self) -> &KnobPage {
|
||||||
|
&self.knob_pages_by_id[&self.active_knob_page_id]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -49,30 +55,23 @@ pub struct KnobPage {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Key {
|
pub struct Key {
|
||||||
pub position: KeyPosition,
|
pub path: KeyPath,
|
||||||
pub icon: IconDescriptor,
|
pub icon: IconDescriptor,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Knob {
|
pub struct Knob {
|
||||||
pub position: KnobPosition,
|
pub path: KnobPath,
|
||||||
pub icon: IconDescriptor,
|
pub icon: IconDescriptor,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub value: f32,
|
pub value: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum StateChangeCommand {
|
pub enum StateChangeCommand {
|
||||||
RefreshButtonColors,
|
SetActivePages { key_page_id: String, knob_page_id: String },
|
||||||
SetKeyLabel {
|
SetKeyLabel { path: KeyPath, value: String },
|
||||||
page_id: String,
|
SetKeyIcon { path: KeyPath, value: IconDescriptor },
|
||||||
key_position: KeyPosition,
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
SetKeyIcon {
|
|
||||||
page_id: String,
|
|
||||||
key_position: KeyPosition,
|
|
||||||
value: IconDescriptor,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,57 @@ pub struct LoupedeckDeviceDisplayConfiguration {
|
||||||
pub endianness: Endianness,
|
pub endianness: Endianness,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct LoupedeckDeviceKeyGridCharacteristics {
|
||||||
|
pub rows: u8,
|
||||||
|
pub columns: u8,
|
||||||
|
pub display: LoupedeckDeviceDisplayConfiguration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoupedeckDeviceKeyGridCharacteristics {
|
||||||
|
pub fn key_size(&self) -> (u16, u16) {
|
||||||
|
// Assuming the sizes are integers
|
||||||
|
(self.display.width / self.columns as u16, self.display.height / self.rows as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_key_at_local_coordinates(&self, x: u16, y: u16) -> Option<u8> {
|
||||||
|
let (column_width, row_height) = self.key_size();
|
||||||
|
|
||||||
|
if x >= self.display.width || y >= self.display.height {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let column = (x / column_width) as u8;
|
||||||
|
let row = (y / row_height) as u8;
|
||||||
|
|
||||||
|
Some(row * self.columns + column)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_key_at_global_coordinates(&self, x: u16, y: u16) -> Option<u8> {
|
||||||
|
if x < self.display.global_offset_x || y < self.display.global_offset_y {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_x = x - self.display.global_offset_x;
|
||||||
|
let local_y = y - self.display.global_offset_y;
|
||||||
|
|
||||||
|
self.get_key_at_local_coordinates(local_x, local_y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_local_key_rect_xywh(&self, key_index: u8) -> Option<(u16, u16, u16, u16)> {
|
||||||
|
if key_index >= self.rows * self.columns {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (column_width, row_height) = self.key_size();
|
||||||
|
let row = (key_index / self.columns) as u16;
|
||||||
|
let column = (key_index % self.columns) as u16;
|
||||||
|
|
||||||
|
Some((column * column_width, row * row_height, column_width, row_height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct LoupedeckDeviceCharacteristics {
|
pub struct LoupedeckDeviceCharacteristics {
|
||||||
|
@ -49,21 +100,11 @@ pub struct LoupedeckDeviceCharacteristics {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub available_knobs: EnumSet<LoupedeckKnob>,
|
pub available_knobs: EnumSet<LoupedeckKnob>,
|
||||||
pub available_buttons: EnumSet<LoupedeckButton>,
|
pub available_buttons: EnumSet<LoupedeckButton>,
|
||||||
pub key_grid_rows: u8,
|
pub key_grid: LoupedeckDeviceKeyGridCharacteristics,
|
||||||
pub key_grid_columns: u8,
|
|
||||||
pub key_grid_display: LoupedeckDeviceDisplayConfiguration,
|
|
||||||
pub additional_displays: &'static [LoupedeckDeviceDisplayConfiguration],
|
pub additional_displays: &'static [LoupedeckDeviceDisplayConfiguration],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoupedeckDeviceCharacteristics {
|
impl LoupedeckDeviceCharacteristics {
|
||||||
pub fn key_size(&self) -> (u16, u16) {
|
|
||||||
// Assuming the sizes are integers
|
|
||||||
(
|
|
||||||
self.key_grid_display.width / self.key_grid_columns as u16,
|
|
||||||
self.key_grid_display.height / self.key_grid_rows as u16,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_display_at_coordinates(&self, x: u16, y: u16) -> Option<&LoupedeckDeviceDisplayConfiguration> {
|
pub fn get_display_at_coordinates(&self, x: u16, y: u16) -> Option<&LoupedeckDeviceDisplayConfiguration> {
|
||||||
let check = |display: &&LoupedeckDeviceDisplayConfiguration| {
|
let check = |display: &&LoupedeckDeviceDisplayConfiguration| {
|
||||||
x >= display.global_offset_x
|
x >= display.global_offset_x
|
||||||
|
@ -72,32 +113,12 @@ impl LoupedeckDeviceCharacteristics {
|
||||||
&& y <= display.global_offset_y + display.height
|
&& y <= display.global_offset_y + display.height
|
||||||
};
|
};
|
||||||
|
|
||||||
if check(&&self.key_grid_display) {
|
if check(&&self.key_grid.display) {
|
||||||
Some(&self.key_grid_display)
|
Some(&self.key_grid.display)
|
||||||
} else {
|
} else {
|
||||||
self.additional_displays.iter().find(check)
|
self.additional_displays.iter().find(check)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_key_at_coordinates(&self, x: u16, y: u16) -> Option<u8> {
|
|
||||||
let (column_width, row_height) = self.key_size();
|
|
||||||
|
|
||||||
if x < self.key_grid_display.global_offset_x || y < self.key_grid_display.global_offset_y {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let local_x = x - self.key_grid_display.global_offset_x;
|
|
||||||
let local_y = y - self.key_grid_display.global_offset_y;
|
|
||||||
|
|
||||||
if local_x >= self.key_grid_display.width || local_y >= self.key_grid_display.height {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let column = (local_x / column_width) as u8;
|
|
||||||
let row = (local_y / row_height) as u8;
|
|
||||||
|
|
||||||
Some(row * self.key_grid_columns + column)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = LoupedeckDeviceCharacteristics {
|
static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = LoupedeckDeviceCharacteristics {
|
||||||
|
@ -122,18 +143,20 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
||||||
| LoupedeckButton::N6
|
| LoupedeckButton::N6
|
||||||
| LoupedeckButton::N7
|
| LoupedeckButton::N7
|
||||||
),
|
),
|
||||||
key_grid_rows: 3,
|
key_grid: LoupedeckDeviceKeyGridCharacteristics {
|
||||||
key_grid_columns: 4,
|
rows: 3,
|
||||||
key_grid_display: LoupedeckDeviceDisplayConfiguration {
|
columns: 4,
|
||||||
id: 0x4d,
|
display: LoupedeckDeviceDisplayConfiguration {
|
||||||
name: "center",
|
id: 0x4d,
|
||||||
width: 360,
|
name: "center",
|
||||||
height: 270,
|
width: 360,
|
||||||
local_offset_x: 60,
|
height: 270,
|
||||||
local_offset_y: 0,
|
local_offset_x: 60,
|
||||||
global_offset_x: 60,
|
local_offset_y: 0,
|
||||||
global_offset_y: 0,
|
global_offset_x: 60,
|
||||||
endianness: Endianness::LittleEndian,
|
global_offset_y: 0,
|
||||||
|
endianness: Endianness::LittleEndian,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
additional_displays: &[
|
additional_displays: &[
|
||||||
LoupedeckDeviceDisplayConfiguration {
|
LoupedeckDeviceDisplayConfiguration {
|
||||||
|
|
|
@ -123,7 +123,7 @@ impl LoupedeckDevice {
|
||||||
///
|
///
|
||||||
/// `buffer` must contain exactly as many pixels as required.
|
/// `buffer` must contain exactly as many pixels as required.
|
||||||
///
|
///
|
||||||
/// Please note that the internal color format of all currently known devices is RGB565 (16 bits in total).
|
/// Please note that the _internal_ color format of all currently known devices is RGB565 (16 bits in total).
|
||||||
pub fn replace_framebuffer_area(
|
pub fn replace_framebuffer_area(
|
||||||
&self,
|
&self,
|
||||||
display: &LoupedeckDeviceDisplayConfiguration,
|
display: &LoupedeckDeviceDisplayConfiguration,
|
||||||
|
@ -133,14 +133,7 @@ impl LoupedeckDevice {
|
||||||
height: u16,
|
height: u16,
|
||||||
buffer: &[RGB8],
|
buffer: &[RGB8],
|
||||||
) -> Result<(), ReplaceFramebufferAreaError> {
|
) -> Result<(), ReplaceFramebufferAreaError> {
|
||||||
if !std::ptr::eq(display, &self.characteristics.key_grid_display) && !self.characteristics.additional_displays.iter().any(|d| std::ptr::eq(display, d))
|
self.check_replace_framebuffer_area_parameters(display, x, y, width, height)?;
|
||||||
{
|
|
||||||
return Err(ReplaceFramebufferAreaError::UnknownDisplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if x + width > display.width || y + height > display.height {
|
|
||||||
return Err(ReplaceFramebufferAreaError::OutOfBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
let expected_buffer_size = (height * width) as usize;
|
let expected_buffer_size = (height * width) as usize;
|
||||||
if buffer.len() != expected_buffer_size {
|
if buffer.len() != expected_buffer_size {
|
||||||
|
@ -154,12 +147,67 @@ impl LoupedeckDevice {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let buffer = Bytes::copy_from_slice(buffer.as_slice());
|
|
||||||
|
|
||||||
// For some color values x, the pixel brightness is lower than for x - 1.
|
// For some color values x, the pixel brightness is lower than for x - 1.
|
||||||
// I don’t understand why and what is the pattern of these values.
|
// I don’t understand why and what is the pattern of these values.
|
||||||
let converted_buffer = convert_rgb888_to_rgb565(buffer, display.endianness);
|
let converted_buffer = convert_rgb888_to_rgb565(Bytes::copy_from_slice(buffer.as_slice()), display.endianness);
|
||||||
|
|
||||||
|
self.unchecked_replace_framebuffer_area_raw(display, x, y, width, height, converted_buffer.freeze());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces the specified framebuffer area of the display with `buffer`.
|
||||||
|
///
|
||||||
|
/// `buffer` must contain exactly (`width * height`) RGB565 (16 bit) values, LE or BE depending on `display.endianness`.
|
||||||
|
///
|
||||||
|
/// If you have a buffer of `rgb::RGB8` values, you should use [replace_framebuffer_area](LoupedeckDevice::replace_framebuffer_area) instead.
|
||||||
|
pub fn replace_framebuffer_area_raw(
|
||||||
|
&self,
|
||||||
|
display: &LoupedeckDeviceDisplayConfiguration,
|
||||||
|
x: u16,
|
||||||
|
y: u16,
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
buffer: Bytes,
|
||||||
|
) -> Result<(), ReplaceFramebufferAreaError> {
|
||||||
|
self.check_replace_framebuffer_area_parameters(display, x, y, width, height)?;
|
||||||
|
|
||||||
|
let expected_buffer_size = (height * width * 2) as usize;
|
||||||
|
if buffer.len() != expected_buffer_size {
|
||||||
|
return Err(ReplaceFramebufferAreaError::WrongBufferSize {
|
||||||
|
expected: expected_buffer_size,
|
||||||
|
actual: buffer.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.unchecked_replace_framebuffer_area_raw(display, x, y, width, height, buffer);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_replace_framebuffer_area_parameters(
|
||||||
|
&self,
|
||||||
|
display: &LoupedeckDeviceDisplayConfiguration,
|
||||||
|
x: u16,
|
||||||
|
y: u16,
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
) -> Result<(), ReplaceFramebufferAreaError> {
|
||||||
|
if display.name != self.characteristics.key_grid.display.name && !self.characteristics.additional_displays.iter().any(|d| display.name == d.name) {
|
||||||
|
return Err(ReplaceFramebufferAreaError::UnknownDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if x + width > display.width || y + height > display.height {
|
||||||
|
return Err(ReplaceFramebufferAreaError::OutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unchecked_replace_framebuffer_area_raw(&self, display: &LoupedeckDeviceDisplayConfiguration, x: u16, y: u16, width: u16, height: u16, buffer: Bytes) {
|
||||||
let local_x = display.local_offset_x + x;
|
let local_x = display.local_offset_x + x;
|
||||||
let local_y = display.local_offset_y + y;
|
let local_y = display.local_offset_y + y;
|
||||||
|
|
||||||
|
@ -170,16 +218,13 @@ impl LoupedeckDevice {
|
||||||
y: local_y,
|
y: local_y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
buffer: converted_buffer.freeze(),
|
buffer,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_display(&self, display: &LoupedeckDeviceDisplayConfiguration) -> Result<(), RefreshDisplayError> {
|
pub fn refresh_display(&self, display: &LoupedeckDeviceDisplayConfiguration) -> Result<(), RefreshDisplayError> {
|
||||||
if !std::ptr::eq(display, &self.characteristics.key_grid_display) && !self.characteristics.additional_displays.iter().any(|d| std::ptr::eq(display, d))
|
if display.name != self.characteristics.key_grid.display.name && !self.characteristics.additional_displays.iter().any(|d| display.name == d.name) {
|
||||||
{
|
|
||||||
return Err(RefreshDisplayError::UnknownDisplay);
|
return Err(RefreshDisplayError::UnknownDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,4 @@ pub mod commands;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
mod messages;
|
mod messages;
|
||||||
mod util;
|
pub mod util;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
pub(crate) fn convert_rgb888_to_rgb565(original: Bytes, endianness: Endianness) -> BytesMut {
|
pub fn convert_rgb888_to_rgb565(original: Bytes, endianness: Endianness) -> BytesMut {
|
||||||
let pixel_count = original.len() / 3;
|
let pixel_count = original.len() / 3;
|
||||||
let excess_bytes = original.len() % 3;
|
let excess_bytes = original.len() % 3;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue