From 228848444da8727105734bb4405bf394b3ef8656 Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Fri, 5 Jan 2024 21:31:33 +0100 Subject: [PATCH] commit --- Cargo.lock | 372 +++++++++++++++++++----- deckster/Cargo.toml | 22 +- deckster/src/model/geometry.rs | 30 +- deckster/src/model/image_filter.rs | 12 +- deckster/src/model/mod.rs | 17 +- deckster/src/model/rgb.rs | 6 +- deckster/src/runner/graphics.rs | 42 +++ deckster/src/runner/mod.rs | 167 +++++++++-- deckster/src/runner/state.rs | 49 ++-- loupedeck_serial/src/characteristics.rs | 113 ++++--- loupedeck_serial/src/device.rs | 79 +++-- loupedeck_serial/src/lib.rs | 2 +- loupedeck_serial/src/util.rs | 2 +- 13 files changed, 685 insertions(+), 228 deletions(-) create mode 100644 deckster/src/runner/graphics.rs diff --git a/Cargo.lock b/Cargo.lock index cfb1efe..6f29796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/deckster/Cargo.toml b/deckster/Cargo.toml index ab8d8a3..a489e64 100644 --- a/deckster/Cargo.toml +++ b/deckster/Cargo.toml @@ -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" \ No newline at end of file +toml = "0.8.8" +walkdir = "2.4.0" \ No newline at end of file diff --git a/deckster/src/model/geometry.rs b/deckster/src/model/geometry.rs index 85858c3..7c9d0fb 100644 --- a/deckster/src/model/geometry.rs +++ b/deckster/src/model/geometry.rs @@ -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 { +pub fn parse_positive_int_rect_from_str(s: &str) -> Result { 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 { 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())) } diff --git a/deckster/src/model/image_filter.rs b/deckster/src/model/image_filter.rs index b8af4fc..7010227 100644 --- a/deckster/src/model/image_filter.rs +++ b/deckster/src/model/image_filter.rs @@ -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, // applied before scale and rotate + pub crop_original: Option, // applied before scale and rotate pub scale: f32, pub clockwise_quarter_rotations: u8, - pub crop: Option, // applied after scale and rotate + pub crop: Option, // applied after scale and rotate pub color: Option, pub alpha: f32, pub blur: f32, @@ -68,8 +68,8 @@ impl FromStr for ImageFilter { type Err = ImageFilterFromStringError; fn from_str<'a>(s: &str) -> Result { - fn parse_rect_filter_value(filter_name: &str, raw_value: String) -> Result { - parse_positive_rect_from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable { + fn parse_rect_filter_value(filter_name: &str, raw_value: String) -> Result { + parse_positive_int_rect_from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable { filter_name: filter_name.to_string(), raw_value, }) diff --git a/deckster/src/model/mod.rs b/deckster/src/model/mod.rs index 7a3fca1..5320226 100644 --- a/deckster/src/model/mod.rs +++ b/deckster/src/model/mod.rs @@ -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")] diff --git a/deckster/src/model/rgb.rs b/deckster/src/model/rgb.rs index c4c2cca..b20fdb9 100644 --- a/deckster/src/model/rgb.rs +++ b/deckster/src/model/rgb.rs @@ -60,7 +60,7 @@ impl FromStr for SerializableRGB8 { type Err = RGBParsingError; fn from_str(s: &str) -> Result { - 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 { - 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 { - 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) } } diff --git a/deckster/src/runner/graphics.rs b/deckster/src/runner/graphics.rs new file mode 100644 index 0000000..f48ccb7 --- /dev/null +++ b/deckster/src/runner/graphics.rs @@ -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 +} diff --git a/deckster/src/runner/mod.rs b/deckster/src/runner/mod.rs index 3a29710..b84f54d 100644 --- a/deckster/src/runner/mod.rs +++ b/deckster/src/runner/mod.rs @@ -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::(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, event: LoupedeckEvent) -> bool { +fn handle_event( + config: &model::config::Config, + state: &State, + device: &LoupedeckDevice, + commands_sender: &Sender, + 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 { + 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); + } +} diff --git a/deckster/src/runner/state.rs b/deckster/src/runner/state.rs index 56d71e9..20d23e3 100644 --- a/deckster/src/runner/state.rs +++ b/deckster/src/runner/state.rs @@ -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( - &mut self, - command_name: &'static str, - page_id: &String, - key_position: &KeyPosition, - mutator: Box R>, - ) -> Option { - 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(&mut self, command_name: &'static str, path: &KeyPath, mutator: Box R>) -> Option { + 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 }, } diff --git a/loupedeck_serial/src/characteristics.rs b/loupedeck_serial/src/characteristics.rs index e8fe0de..03a9ab4 100644 --- a/loupedeck_serial/src/characteristics.rs +++ b/loupedeck_serial/src/characteristics.rs @@ -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 { + 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 { + 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, pub available_buttons: EnumSet, - 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 { - 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 { diff --git a/loupedeck_serial/src/device.rs b/loupedeck_serial/src/device.rs index a8b27f8..101fc12 100644 --- a/loupedeck_serial/src/device.rs +++ b/loupedeck_serial/src/device.rs @@ -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 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_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); } diff --git a/loupedeck_serial/src/lib.rs b/loupedeck_serial/src/lib.rs index bc37191..a97fe58 100644 --- a/loupedeck_serial/src/lib.rs +++ b/loupedeck_serial/src/lib.rs @@ -3,4 +3,4 @@ pub mod commands; pub mod device; pub mod events; mod messages; -mod util; +pub mod util; diff --git a/loupedeck_serial/src/util.rs b/loupedeck_serial/src/util.rs index 3f3364f..75d53b5 100644 --- a/loupedeck_serial/src/util.rs +++ b/loupedeck_serial/src/util.rs @@ -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;