This commit is contained in:
Moritz Ruth 2024-01-05 21:31:33 +01:00
parent 474ecf4f5e
commit 228848444d
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
13 changed files with 685 additions and 228 deletions

372
Cargo.lock generated
View file

@ -89,6 +89,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "arrayref"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -259,6 +265,36 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "darling"
version = "0.20.3"
@ -298,8 +334,10 @@ dependencies = [
name = "deckster"
version = "0.1.0"
dependencies = [
"bytes",
"clap",
"color-eyre",
"cosmic-text",
"enum-map",
"enum-ordinalize",
"env_logger",
@ -307,13 +345,13 @@ dependencies = [
"humantime-serde",
"log",
"loupedeck_serial",
"piet",
"regex",
"rgb",
"serde",
"serde_regex",
"serde_with",
"thiserror",
"tiny-skia",
"tokio",
"toml",
"walkdir",
@ -429,6 +467,25 @@ dependencies = [
"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]]
name = "flume"
version = "0.11.0"
@ -447,6 +504,29 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "futures-core"
version = "0.3.30"
@ -617,15 +697,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kurbo"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b"
dependencies = [
"arrayvec",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -638,6 +709,12 @@ version = "0.2.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "libudev"
version = "0.3.0"
@ -702,18 +779,21 @@ dependencies = [
"libc",
]
[[package]]
name = "matches"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "memmap2"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
dependencies = [
"libc",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@ -721,6 +801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
"simd-adler32",
]
[[package]]
@ -806,16 +887,6 @@ dependencies = [
"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]]
name = "pin-project-lite"
version = "0.2.13"
@ -828,6 +899,19 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "powerfmt"
version = "0.2.0"
@ -852,6 +936,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rangemap"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991"
[[package]]
name = "redox_syscall"
version = "0.4.1"
@ -899,12 +989,27 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "roxmltree"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302"
dependencies = [
"xmlparser",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.28"
@ -918,6 +1023,23 @@ dependencies = [
"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]]
name = "ryu"
version = "1.0.16"
@ -939,6 +1061,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "self_cell"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba"
[[package]]
name = "serde"
version = "1.0.193"
@ -1046,6 +1174,21 @@ dependencies = [
"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]]
name = "smallvec"
version = "1.11.2"
@ -1061,12 +1204,28 @@ dependencies = [
"lock_api",
]
[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "syn"
version = "2.0.43"
@ -1078,6 +1237,15 @@ dependencies = [
"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]]
name = "termcolor"
version = "1.4.0"
@ -1146,6 +1314,47 @@ dependencies = [
"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]]
name = "tokio"
version = "1.35.1"
@ -1245,6 +1454,18 @@ dependencies = [
"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]]
name = "unescaper"
version = "0.1.3"
@ -1255,55 +1476,22 @@ dependencies = [
]
[[package]]
name = "unic-bidi"
version = "0.9.0"
name = "unicode-bidi"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09"
dependencies = [
"matches",
"unic-ucd-bidi",
]
checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
[[package]]
name = "unic-char-property"
version = "0.9.0"
name = "unicode-bidi-mirroring"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694"
[[package]]
name = "unic-char-range"
version = "0.9.0"
name = "unicode-ccc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[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",
]
checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1"
[[package]]
name = "unicode-ident"
@ -1311,6 +1499,30 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "utf8parse"
version = "0.2.1"
@ -1323,6 +1535,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
@ -1564,3 +1782,21 @@ checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c"
dependencies = [
"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"

View file

@ -4,22 +4,24 @@ version = "0.1.0"
edition = "2021"
[dependencies]
bytes = "1.5.0"
clap = { version = "4.4.12", features = ["derive"] }
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"
log = "0.4.20"
loupedeck_serial = { path = "../loupedeck_serial" }
regex = "1.10.2"
rgb = "0.8.37"
serde = { version = "1.0.193", features = ["derive"] }
serde_regex = "1.1.0"
serde_with = "3.4.0"
thiserror = "1.0.52"
toml = "0.8.8"
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"
tiny-skia = "0.11.3"
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "rt-multi-thread", "sync"]}
flume = "0.11.0"
enum-ordinalize = "4.3.0"
piet = "0.6.2"
toml = "0.8.8"
walkdir = "2.4.0"

View file

@ -1,20 +1,14 @@
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use piet::kurbo::{Rect, Vec2};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use thiserror::Error;
use tiny_skia::IntRect;
#[derive(Debug, Eq, PartialEq, Hash, Clone, SerializeDisplay, DeserializeFromStr)]
pub struct UIntVec2 {
x: u64,
y: u64,
}
impl UIntVec2 {
fn to_kurbo_vec2(&self) -> Vec2 {
Vec2::new(self.x as f64, self.y as f64)
}
pub x: u32,
pub y: u32,
}
#[derive(Debug, Error)]
@ -28,8 +22,8 @@ impl FromStr for UIntVec2 {
let values = s.split_once('x');
if let Some((x, y)) = values {
if let Ok(x) = u64::from_str(x) {
if let Ok(y) = u64::from_str(y) {
if let Ok(x) = u32::from_str(x) {
if let Ok(y) = u32::from_str(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('+') {
(pairs, false)
} 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(());
};
let first_vec = UIntVec2::from_str(pairs.0).map_err(|_| ())?.to_kurbo_vec2();
let second_vec = UIntVec2::from_str(pairs.1).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(|_| ())?;
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 {
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 {
f.write_fmt(format_args!("{}x{}-{}x{}", rect.x0, rect.x1, rect.y0, rect.y1))
pub fn fmt_positive_rect(rect: &IntRect, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}x{}+{}x{}", rect.x(), rect.y(), rect.width(), rect.height()))
}

View file

@ -1,20 +1,20 @@
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use piet::kurbo::Rect;
use rgb::RGB8;
use serde_with::{DeserializeFromStr, SerializeDisplay};
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};
#[derive(Debug, PartialEq, Clone, SerializeDisplay, DeserializeFromStr)]
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 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 alpha: f32,
pub blur: f32,
@ -68,8 +68,8 @@ impl FromStr for ImageFilter {
type Err = ImageFilterFromStringError;
fn from_str<'a>(s: &str) -> Result<Self, Self::Err> {
fn parse_rect_filter_value(filter_name: &str, raw_value: String) -> Result<Rect, ImageFilterFromStringError> {
parse_positive_rect_from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable {
fn parse_rect_filter_value(filter_name: &str, raw_value: String) -> Result<IntRect, ImageFilterFromStringError> {
parse_positive_int_rect_from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable {
filter_name: filter_name.to_string(),
raw_value,
})

View file

@ -19,9 +19,10 @@ pub mod knob_page;
pub mod rgb;
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, SerializeDisplay, DeserializeFromStr)]
/// One-based coordinates of a specific virtual (not physical) key.
pub struct KeyPosition {
x: u16,
y: u16,
pub x: u16,
pub y: u16,
}
#[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)]
#[serde(rename_all = "kebab-case")]
pub enum KnobPosition {
@ -63,6 +70,12 @@ pub enum KnobPosition {
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)]
pub enum ButtonPosition {
#[serde(rename = "0")]

View file

@ -60,7 +60,7 @@ impl FromStr for SerializableRGB8 {
type Err = RGBParsingError;
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;
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;
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)
}
}

View 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
}

View file

@ -7,19 +7,21 @@ use color_eyre::Result;
use enum_map::EnumMap;
use enum_ordinalize::Ordinalize;
use flume::{Receiver, Sender};
use log::{debug, info, trace};
use log::{debug, trace};
use rgb::RGB8;
use loupedeck_serial::characteristics::LoupedeckButton;
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics};
use loupedeck_serial::commands::VibrationPattern;
use loupedeck_serial::device::LoupedeckDevice;
use loupedeck_serial::events::LoupedeckEvent;
use crate::model;
use crate::model::icon_descriptor::IconDescriptor;
use crate::model::ButtonPosition;
use crate::runner::state::{State, StateChangeCommand};
use crate::model::{ButtonPosition, KeyPath, KeyPosition, KnobPath};
use crate::runner::graphics::render_key;
use crate::runner::state::{Key, State, StateChangeCommand};
mod graphics;
mod state;
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 (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_commands_sender = commands_sender.clone();
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))
.wrap_err("Could not spawn the worker thread")?;
commands_sender.send(StateChangeCommand::RefreshButtonColors).unwrap();
io_worker_thread.join().unwrap();
Ok(())
@ -58,12 +65,15 @@ fn create_state(config: &model::config::Config) -> State {
keys_by_position: p
.keys
.iter()
.map(|(position, k)| state::Key {
position: *position,
.map(|(position, k)| Key {
path: KeyPath {
page_id: p.id.clone(),
position: *position,
},
label: k.label.clone().unwrap_or_default(),
icon: IconDescriptor::default(),
})
.map(|k| (k.position, k))
.map(|k| (k.path.position, k))
.collect(),
})
.map(|p| (p.id.clone(), p))
@ -78,7 +88,10 @@ fn create_state(config: &model::config::Config) -> State {
let knob_config = &p.knobs[position];
state::Knob {
position,
path: KnobPath {
page_id: p.id.clone(),
position,
},
label: knob_config.label.clone(),
icon: knob_config.icon.0.clone(),
value: 0.0,
@ -118,7 +131,7 @@ fn do_io_work(
match a {
IoWork::Event(event) => {
if !handle_event(&config, &mut state, &commands_sender, event) {
if !handle_event(&config, &state, &device, &commands_sender, event) {
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);
match event {
@ -135,23 +154,43 @@ fn handle_event(config: &model::config::Config, state: &mut State, commands_send
LoupedeckEvent::ButtonDown { button } => {
let position = ButtonPosition::of(&button);
let button_config = &config.buttons[position];
let mut did_change = false;
if let Some(key_page) = &button_config.key_page {
did_change = true;
info!("Switching to key page: {}", key_page);
commands_sender
.send(StateChangeCommand::SetActivePages {
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 {
did_change = true;
info!("Switching to knob page: {}", knob_page);
state.active_knob_page_id = knob_page.clone();
}
let path = KeyPath {
page_id: state.active_key_page_id.clone(),
position,
};
if did_change {
commands_sender.send(StateChangeCommand::RefreshButtonColors).unwrap()
let value = if state.get_key(&path).label.is_empty() {
"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);
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 {
let position = ButtonPosition::of(button);
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(
"SetKeyLabel",
&page_id,
&key_position,
&path,
Box::new(|k| {
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(
"SetKeyIcon",
&page_id,
&key_position,
&path,
Box::new(|k| {
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
}
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);
}
}

View file

@ -5,7 +5,7 @@ use log::error;
use serde::{Deserialize, Serialize};
use crate::model::icon_descriptor::IconDescriptor;
use crate::model::{KeyPosition, KnobPosition};
use crate::model::{KeyPath, KeyPosition, KnobPath, KnobPosition};
#[derive(Debug)]
pub struct State {
@ -16,23 +16,29 @@ pub struct State {
}
impl State {
pub fn mutate_key_for_command<R>(
&mut self,
command_name: &'static str,
page_id: &String,
key_position: &KeyPosition,
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),
pub fn mutate_key_for_command<R>(&mut self, command_name: &'static str, path: &KeyPath, mutator: Box<dyn FnOnce(&mut Key) -> R>) -> Option<R> {
match self.key_pages_by_id.get_mut(&path.page_id) {
None => error!("Received {} command with invalid path.page_id: {}", command_name, &path.page_id),
Some(key_page) => match key_page.keys_by_position.get_mut(&path.position) {
None => error!("Received {} command with invalid path.position: {}", command_name, &path.position),
Some(key) => return Some(mutator(key)),
},
}
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)]
@ -49,30 +55,23 @@ pub struct KnobPage {
#[derive(Debug)]
pub struct Key {
pub position: KeyPosition,
pub path: KeyPath,
pub icon: IconDescriptor,
pub label: String,
}
#[derive(Debug)]
pub struct Knob {
pub position: KnobPosition,
pub path: KnobPath,
pub icon: IconDescriptor,
pub label: String,
pub value: f32,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[allow(clippy::enum_variant_names)]
pub enum StateChangeCommand {
RefreshButtonColors,
SetKeyLabel {
page_id: String,
key_position: KeyPosition,
value: String,
},
SetKeyIcon {
page_id: String,
key_position: KeyPosition,
value: IconDescriptor,
},
SetActivePages { key_page_id: String, knob_page_id: String },
SetKeyLabel { path: KeyPath, value: String },
SetKeyIcon { path: KeyPath, value: IconDescriptor },
}

View file

@ -41,6 +41,57 @@ pub struct LoupedeckDeviceDisplayConfiguration {
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)]
#[non_exhaustive]
pub struct LoupedeckDeviceCharacteristics {
@ -49,21 +100,11 @@ pub struct LoupedeckDeviceCharacteristics {
pub name: &'static str,
pub available_knobs: EnumSet<LoupedeckKnob>,
pub available_buttons: EnumSet<LoupedeckButton>,
pub key_grid_rows: u8,
pub key_grid_columns: u8,
pub key_grid_display: LoupedeckDeviceDisplayConfiguration,
pub key_grid: LoupedeckDeviceKeyGridCharacteristics,
pub additional_displays: &'static [LoupedeckDeviceDisplayConfiguration],
}
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> {
let check = |display: &&LoupedeckDeviceDisplayConfiguration| {
x >= display.global_offset_x
@ -72,32 +113,12 @@ impl LoupedeckDeviceCharacteristics {
&& y <= display.global_offset_y + display.height
};
if check(&&self.key_grid_display) {
Some(&self.key_grid_display)
if check(&&self.key_grid.display) {
Some(&self.key_grid.display)
} else {
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 {
@ -122,18 +143,20 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
| LoupedeckButton::N6
| LoupedeckButton::N7
),
key_grid_rows: 3,
key_grid_columns: 4,
key_grid_display: LoupedeckDeviceDisplayConfiguration {
id: 0x4d,
name: "center",
width: 360,
height: 270,
local_offset_x: 60,
local_offset_y: 0,
global_offset_x: 60,
global_offset_y: 0,
endianness: Endianness::LittleEndian,
key_grid: LoupedeckDeviceKeyGridCharacteristics {
rows: 3,
columns: 4,
display: LoupedeckDeviceDisplayConfiguration {
id: 0x4d,
name: "center",
width: 360,
height: 270,
local_offset_x: 60,
local_offset_y: 0,
global_offset_x: 60,
global_offset_y: 0,
endianness: Endianness::LittleEndian,
},
},
additional_displays: &[
LoupedeckDeviceDisplayConfiguration {

View file

@ -123,7 +123,7 @@ impl LoupedeckDevice {
///
/// `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(
&self,
display: &LoupedeckDeviceDisplayConfiguration,
@ -133,14 +133,7 @@ impl LoupedeckDevice {
height: u16,
buffer: &[RGB8],
) -> Result<(), ReplaceFramebufferAreaError> {
if !std::ptr::eq(display, &self.characteristics.key_grid_display) && !self.characteristics.additional_displays.iter().any(|d| std::ptr::eq(display, d))
{
return Err(ReplaceFramebufferAreaError::UnknownDisplay);
}
if x + width > display.width || y + height > display.height {
return Err(ReplaceFramebufferAreaError::OutOfBounds);
}
self.check_replace_framebuffer_area_parameters(display, x, y, width, height)?;
let expected_buffer_size = (height * width) as usize;
if buffer.len() != expected_buffer_size {
@ -154,12 +147,67 @@ impl LoupedeckDevice {
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.
// I dont 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_y = display.local_offset_y + y;
@ -170,16 +218,13 @@ impl LoupedeckDevice {
y: local_y,
width,
height,
buffer: converted_buffer.freeze(),
buffer,
})
.unwrap();
Ok(())
}
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);
}

View file

@ -3,4 +3,4 @@ pub mod commands;
pub mod device;
pub mod events;
mod messages;
mod util;
pub mod util;

View file

@ -1,6 +1,6 @@
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 excess_bytes = original.len() % 3;