From 78291b75b7ecb35626d36b1fc6678e7078c36fdc Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Sat, 6 Jan 2024 18:39:33 +0100 Subject: [PATCH] commit --- Cargo.lock | 244 ++++-------------- Cargo.toml | 5 +- deckster/Cargo.toml | 3 +- deckster/examples/full/deckster.toml | 15 +- deckster/src/icons/filter.rs | 14 + deckster/src/icons/mod.rs | 121 +++++---- deckster/src/main.rs | 10 +- deckster/src/model/config.rs | 23 +- deckster/src/model/geometry.rs | 49 ++-- deckster/src/model/icon_descriptor.rs | 8 +- deckster/src/model/image_filter.rs | 59 ++--- deckster/src/model/knob_modes/audio_volume.rs | 6 +- deckster/src/model/knob_page.rs | 6 +- deckster/src/model/rgb.rs | 71 ++--- deckster/src/runner/mod.rs | 4 +- 15 files changed, 258 insertions(+), 380 deletions(-) create mode 100644 deckster/src/icons/filter.rs diff --git a/Cargo.lock b/Cargo.lock index 176f217..239188a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,12 +128,6 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - [[package]] name = "bitflags" version = "1.3.2" @@ -158,12 +152,6 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.5.0" @@ -229,7 +217,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -277,6 +265,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -313,43 +307,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "darling" version = "0.20.3" @@ -371,7 +328,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.43", ] [[package]] @@ -382,7 +339,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -395,17 +352,16 @@ checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" name = "deckster" version = "0.1.0" dependencies = [ - "bytemuck", "bytes", "clap", "color-eyre", "cosmic-text", + "derive_more", "enum-map", "enum-ordinalize", "env_logger", "flume", "humantime-serde", - "image", "log", "loupedeck_serial", "regex", @@ -432,10 +388,17 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.9.0" +name = "derive_more" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] [[package]] name = "enum-map" @@ -454,7 +417,7 @@ checksum = "44600091ce205df4f8b661e98617d49c37b2dd609e449ec82b0fb5d7b33e2eeb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -474,7 +437,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -495,7 +458,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -527,22 +490,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "exr" -version = "1.71.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - [[package]] name = "eyre" version = "0.6.11" @@ -674,15 +621,6 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -[[package]] -name = "half" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" -dependencies = [ - "crunchy", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -758,25 +696,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "image" -version = "0.24.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "exr", - "gif", - "jpeg-decoder", - "num-rational", - "num-traits", - "png", - "qoi", - "tiff", -] - [[package]] name = "imagesize" version = "0.12.0" @@ -843,9 +762,6 @@ name = "jpeg-decoder" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -871,12 +787,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - [[package]] name = "libc" version = "0.2.151" @@ -1007,27 +917,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.17" @@ -1137,15 +1026,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - [[package]] name = "quote" version = "1.0.33" @@ -1161,26 +1041,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991" -[[package]] -name = "rayon" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "rctree" version = "0.5.0" @@ -1278,6 +1138,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.28" @@ -1351,6 +1220,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + [[package]] name = "serde" version = "1.0.193" @@ -1368,7 +1243,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -1427,7 +1302,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -1538,6 +1413,17 @@ dependencies = [ "zeno", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.43" @@ -1584,7 +1470,7 @@ checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -1597,17 +1483,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tiff" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - [[package]] name = "time" version = "0.3.31" @@ -1699,7 +1574,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -1968,7 +1843,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.43", "wasm-bindgen-shared", ] @@ -1990,7 +1865,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2202,12 +2077,3 @@ name = "zeno" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] diff --git a/Cargo.toml b/Cargo.toml index efde2fd..40b06b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,7 @@ members = [ "loupedeck_serial" ] -resolver = "2" \ No newline at end of file +resolver = "2" + +[profile.release] +strip = true # Automatically strip symbols from the binary. \ No newline at end of file diff --git a/deckster/Cargo.toml b/deckster/Cargo.toml index 7e510ba..2f14f85 100644 --- a/deckster/Cargo.toml +++ b/deckster/Cargo.toml @@ -5,16 +5,15 @@ edition = "2021" [dependencies] bytes = "1.5.0" -bytemuck = "1.14.0" clap = { version = "4.4.12", features = ["derive"] } color-eyre = "0.6.2" cosmic-text = "0.10.0" +derive_more = "0.99.17" 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" -image = "0.24.7" log = "0.4.20" loupedeck_serial = { path = "../loupedeck_serial" } regex = "1.10.2" diff --git a/deckster/examples/full/deckster.toml b/deckster/examples/full/deckster.toml index 78c82cd..ccd0cc6 100644 --- a/deckster/examples/full/deckster.toml +++ b/deckster/examples/full/deckster.toml @@ -1,6 +1,3 @@ -key_pages = ["./key-pages/*"] -knob_pages = ["./knob-pages/*"] - inactive_button_color = "#000060" active_button_color = "#eeffff" @@ -24,16 +21,16 @@ knob_page = "default" key_page = "default" knob_page = "default" -[[icon_packs]] -id = "apps" +[icon_packs.apps] path = "./apps" +format = "svg" -[[icon_packs]] -id = "fad" +[icon_packs.fad] path = "./fad" +format = "svg" global_filter = "invert" -[[icon_packs]] -id = "ph" +[icon_packs.ph] path = "./ph" +format = "svg" global_filter = "invert" \ No newline at end of file diff --git a/deckster/src/icons/filter.rs b/deckster/src/icons/filter.rs new file mode 100644 index 0000000..61457ca --- /dev/null +++ b/deckster/src/icons/filter.rs @@ -0,0 +1,14 @@ +use color_eyre::{eyre::ContextCompat, Result}; +use tiny_skia::Pixmap; + +use crate::model::image_filter::ImageFilter; + +pub fn apply_filter(original: &Pixmap, filter: &ImageFilter) -> Result { + let mut result = if let Some(rect) = filter.crop { + original.clone_rect(*rect).wrap_err_with(|| format!("Invalid crop rect: {}", rect))? + } else { + original.clone() + }; + + Ok(result) +} diff --git a/deckster/src/icons/mod.rs b/deckster/src/icons/mod.rs index 38e7676..86ddc3c 100644 --- a/deckster/src/icons/mod.rs +++ b/deckster/src/icons/mod.rs @@ -4,22 +4,42 @@ use std::path::Path; use color_eyre::eyre::{eyre, ContextCompat, WrapErr}; use color_eyre::Result; -use image::RgbaImage; use resvg::usvg::{TextRendering, TreeParsing, TreeTextToPath}; -use tiny_skia::{Pixmap, PixmapMut, Transform}; +use tiny_skia::{Pixmap, Transform}; -use crate::model::config::{IconFormat, IconPack}; +use crate::icons::filter::apply_filter; +use crate::model::config::{Config, IconFormat, IconPack}; use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource}; -use crate::model::image_filter::ImageFilter; + +mod filter; + +pub struct LoadedIcon { + pixmap: Pixmap, + scale: f32, +} + +pub fn get_used_icon_descriptors(config: &Config) -> HashSet { + let mut result: HashSet = HashSet::new(); + + for page in config.key_pages_by_id.values() { + for key in page.keys.values() { + if let Some(d) = &key.icon { + result.insert(d.0); + } + } + } + + result +} pub fn load_icons( icon_packs_by_id: HashMap, descriptors: HashSet, key_size: (u16, u16), dpi: f32, -) -> Result> { - let mut original_images_by_source: HashMap = HashMap::new(); - let mut icons_by_descriptor: HashMap = HashMap::new(); +) -> Result> { + let mut unfiltered_pixmap_by_source: HashMap = HashMap::new(); + let mut icons_by_descriptor: HashMap = HashMap::new(); let mut fonts_db = resvg::usvg::fontdb::Database::new(); fonts_db.load_system_fonts(); @@ -28,12 +48,15 @@ pub fn load_icons( continue; } - let image = match original_images_by_source.entry(descriptor.source.clone()) { + let original_image = match unfiltered_pixmap_by_source.entry(descriptor.source.clone()) { Entry::Occupied(o) => o.into_mut(), - Entry::Vacant(v) => v.insert(read_image(&icon_packs_by_id, dpi, &fonts_db, descriptor.source)?), + Entry::Vacant(v) => v.insert(read_image(&icon_packs_by_id, dpi, &fonts_db, descriptor.source.clone())?), }; - dbg!(image.height()); + let pixmap = apply_filter(original_image, &descriptor.filter)?; + let scale = descriptor.filter.scale; + + icons_by_descriptor.insert(descriptor, LoadedIcon { pixmap, scale }); } Ok(icons_by_descriptor) @@ -44,16 +67,16 @@ fn read_image( dpi: f32, fonts_db: &resvg::usvg::fontdb::Database, source: IconDescriptorSource, -) -> Result { +) -> Result { let path = match source { - IconDescriptorSource::None => unreachable!(), + IconDescriptorSource::None => return Ok(Pixmap::new(1, 1).unwrap()), IconDescriptorSource::Path(path) => path, IconDescriptorSource::IconPack { pack_id, icon_id } => { let pack = icon_packs_by_id.get(&pack_id).wrap_err_with(|| format!("Unknown icon pack: @{}", pack_id))?; let extension = match pack.format { - IconFormat::PNG => "png", - IconFormat::SVG => "svg", + IconFormat::Png => "png", + IconFormat::Svg => "svg", }; pack.path.join(icon_id + "." + extension) @@ -63,9 +86,7 @@ fn read_image( Ok(match path.extension() { None => return Err(eyre!("Invalid icon path: {:?}", path)), Some(extension) => match extension.to_string_lossy().as_ref() { - "png" => image::open(path.clone()) - .wrap_err_with(|| format!("Failed to open or decode the PNG file at {}", path.to_string_lossy()))? - .into_rgba8(), + "png" => Pixmap::load_png(&path).wrap_err_with(|| format!("Failed to open or decode the PNG file at {}", path.to_string_lossy()))?, "svg" => { read_image_from_svg(&path, dpi, fonts_db).wrap_err_with(|| format!("Failed to open or decode the SVG file at {}", path.to_string_lossy()))? } @@ -74,52 +95,30 @@ fn read_image( }) } -fn read_image_from_svg(path: &Path, dpi: f32, font_db: &resvg::usvg::fontdb::Database) -> Result { - let data = std::fs::read(path)?; - let mut tree = resvg::usvg::Tree::from_data( - &data, - &resvg::usvg::Options { - dpi, - font_family: "Inter".to_owned(), - font_size: 11.0, - text_rendering: TextRendering::OptimizeLegibility, - ..resvg::usvg::Options::default() - }, - )?; +fn read_image_from_svg(path: &Path, dpi: f32, font_db: &resvg::usvg::fontdb::Database) -> Result { + let raw_data = std::fs::read(path)?; - tree.convert_text(font_db); + let tree = { + let mut tree = resvg::usvg::Tree::from_data( + &raw_data, + &resvg::usvg::Options { + dpi, + font_family: "Inter".to_owned(), + font_size: 11.0, + text_rendering: TextRendering::OptimizeLegibility, + ..resvg::usvg::Options::default() + }, + )?; - let render_tree = resvg::Tree::from_usvg(&tree); - let width = render_tree.size.width().ceil() as u32; - let height = render_tree.size.height().ceil() as u32; - let mut pixmap = Pixmap::new(width, height).unwrap(); - let mut pixmap_mut: PixmapMut = pixmap.as_mut(); + tree.convert_text(font_db); - render_tree.render(Transform::identity(), &mut pixmap_mut); + resvg::Tree::from_usvg(&tree) + }; - Ok(RgbaImage::from_vec( - width, - height, - pixmap - .pixels_mut() - .iter() - .flat_map(|p| { - let p = p.demultiply(); - [p.red(), p.green(), p.blue(), p.alpha()] - }) - .collect(), - ) - .unwrap()) -} - -fn apply_filter(original: &RgbaImage, filter: &ImageFilter) -> RgbaImage { - let mut result = original.clone(); - - if filter.alpha != 1.0 { - for pixel in result.pixels_mut() { - pixel.0[3] = (pixel.0[3] as f32 * filter.alpha).floor() as u8; - } - } - - result + let size = tree.size.to_int_size(); + let mut pixmap = Pixmap::new(size.width(), size.height()).unwrap(); + + tree.render(Transform::default(), &mut pixmap.as_mut()); + + Ok(pixmap) } diff --git a/deckster/src/main.rs b/deckster/src/main.rs index 7c48ffb..daf30da 100644 --- a/deckster/src/main.rs +++ b/deckster/src/main.rs @@ -15,7 +15,7 @@ mod runner; #[derive(Debug, Parser)] #[command(name = "deckster")] -#[command(about = "Use Loupedeck device under Linux.")] +#[command(about = "Use Loupedeck devices under Linux.")] struct Cli { #[command(subcommand)] command: Command, @@ -36,11 +36,11 @@ pub async fn main() -> Result<()> { match cli.command { Command::Run { config: config_path } => { - let deckster_file = read_and_deserialize::(config_path.join("./deckster.toml").as_path())?; + let deckster_file = read_and_deserialize::(config_path.join("deckster.toml").as_path())?; let config_path = config_path.canonicalize()?; let key_pages_by_id: HashMap = - read_and_deserialize_from_directory::(config_path.join("./key-pages").as_path())? + read_and_deserialize_from_directory::(config_path.join("key-pages").as_path())? .into_iter() .map(|p| model::key_page::Page { id: p.inner.id.clone().unwrap_or(p.fallback_id), @@ -51,7 +51,7 @@ pub async fn main() -> Result<()> { .collect(); let knob_pages_by_id: HashMap = - read_and_deserialize_from_directory::(config_path.join("./knob-pages").as_path())? + read_and_deserialize_from_directory::(config_path.join("knob-pages").as_path())? .into_iter() .map(|p| model::knob_page::Page { id: p.inner.id.clone().unwrap_or(p.fallback_id), @@ -79,7 +79,7 @@ pub async fn main() -> Result<()> { } fn read_and_deserialize(path: &Path) -> Result { - let content = fs::read_to_string(path).wrap_err_with(|| format!("Reading of {} failed.", path.to_string_lossy()))?; + let content = fs::read_to_string(&path).wrap_err_with(|| format!("Reading of {} failed.", path.to_string_lossy()))?; toml::from_str::(&content).wrap_err_with(|| format!("Parsing of {} failed.", path.to_string_lossy())) } diff --git a/deckster/src/model/config.rs b/deckster/src/model/config.rs index 7318e6c..0db97a6 100644 --- a/deckster/src/model/config.rs +++ b/deckster/src/model/config.rs @@ -8,16 +8,16 @@ use serde::{Deserialize, Serialize}; use crate::model; use crate::model::image_filter::ImageFilter; -use crate::model::rgb::SerializableRGB8; +use crate::model::rgb::RGB8Wrapper; use crate::model::ButtonPosition; #[derive(Debug, Deserialize)] pub struct File { pub icon_packs: HashMap, #[serde(default = "inactive_button_color_default")] - pub inactive_button_color: SerializableRGB8, + pub inactive_button_color: RGB8Wrapper, #[serde(default = "active_button_color_default")] - pub active_button_color: SerializableRGB8, + pub active_button_color: RGB8Wrapper, pub buttons: HashMap, // EnumMap pub initial: InitialConfig, } @@ -33,18 +33,18 @@ pub struct Config { pub key_pages_by_id: HashMap, pub knob_pages_by_id: HashMap, pub icon_packs: HashMap, - pub inactive_button_color: SerializableRGB8, - pub active_button_color: SerializableRGB8, + pub inactive_button_color: RGB8Wrapper, + pub active_button_color: RGB8Wrapper, pub buttons: EnumMap, pub initial: InitialConfig, } -fn inactive_button_color_default() -> SerializableRGB8 { - SerializableRGB8(RGB8::new(128, 128, 128)) +fn inactive_button_color_default() -> RGB8Wrapper { + RGB8::new(128, 128, 128).into() } -fn active_button_color_default() -> SerializableRGB8 { - SerializableRGB8(RGB8::new(0, 255, 0)) +fn active_button_color_default() -> RGB8Wrapper { + RGB8::new(0, 255, 0).into() } #[derive(Debug, Deserialize, Default)] @@ -62,13 +62,12 @@ pub struct InitialConfig { #[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum IconFormat { - PNG, - SVG, + Png, + Svg, } #[derive(Debug, Deserialize)] pub struct IconPack { - pub id: String, pub path: PathBuf, pub format: IconFormat, pub global_filter: Option, diff --git a/deckster/src/model/geometry.rs b/deckster/src/model/geometry.rs index 7c9d0fb..e068dbe 100644 --- a/deckster/src/model/geometry.rs +++ b/deckster/src/model/geometry.rs @@ -1,6 +1,7 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; +use derive_more::{Deref, From, Into}; use serde_with::{DeserializeFromStr, SerializeDisplay}; use thiserror::Error; use tiny_skia::IntRect; @@ -39,25 +40,39 @@ impl Display for UIntVec2 { } } -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('-') { - (pairs, true) - } else { - return Err(()); - }; +#[derive(Debug, Error)] +#[error("The input value does not match the required format.")] +pub struct ParsingError {} - let first_vec = UIntVec2::from_str(pairs.0).map_err(|_| ())?; - let second_vec = UIntVec2::from_str(pairs.1).map_err(|_| ())?; +#[derive(Debug, Copy, Clone, PartialEq, Deref, From, Into, SerializeDisplay, DeserializeFromStr)] +#[repr(transparent)] +pub struct IntRectWrapper(IntRect); - Ok(if is_corner_points_mode { - 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 { - IntRect::from_xywh(first_vec.x as i32, first_vec.y as i32, second_vec.x, second_vec.y).unwrap() - }) +impl FromStr for IntRectWrapper { + type Err = ParsingError; + + fn 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('-') { + (pairs, true) + } else { + return Err(ParsingError {}); + }; + + let first_vec = UIntVec2::from_str(pairs.0).map_err(|_| ParsingError {})?; + let second_vec = UIntVec2::from_str(pairs.1).map_err(|_| ParsingError {})?; + + Ok(IntRectWrapper(if is_corner_points_mode { + 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 { + 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: &IntRect, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}x{}+{}x{}", rect.x(), rect.y(), rect.width(), rect.height())) +impl Display for IntRectWrapper { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}x{}+{}x{}", self.x(), self.y(), self.width(), self.height())) + } } diff --git a/deckster/src/model/icon_descriptor.rs b/deckster/src/model/icon_descriptor.rs index 62cc68d..b45114c 100644 --- a/deckster/src/model/icon_descriptor.rs +++ b/deckster/src/model/icon_descriptor.rs @@ -13,7 +13,7 @@ pub struct IconDescriptorString(pub IconDescriptor); #[derive(Debug, Default, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)] pub struct IconDescriptor { pub source: IconDescriptorSource, - pub filter: Option, + pub filter: ImageFilter, } #[derive(Debug, Error)] @@ -46,15 +46,15 @@ impl FromStr for IconDescriptorString { IconDescriptorSource::Path(PathBuf::from(raw_source)) }; - let filter: Option = if raw_filter.is_empty() { - None + let filter: ImageFilter = if raw_filter.is_empty() { + ImageFilter::default() } else { let mut raw_filter = raw_filter.to_owned(); if raw_filter.pop().expect("emptiness was eliminated a few lines earlier") != ']' { return Err(IconDescriptorFromStrError::MissingImageFilterClosingBracket); } - Some(ImageFilter::from_str(&raw_filter).map_err(IconDescriptorFromStrError::InvalidImageFilter)?) + ImageFilter::from_str(&raw_filter).map_err(IconDescriptorFromStrError::InvalidImageFilter)? }; Ok(IconDescriptorString(IconDescriptor { source, filter })) diff --git a/deckster/src/model/image_filter.rs b/deckster/src/model/image_filter.rs index 036a127..7a1a366 100644 --- a/deckster/src/model/image_filter.rs +++ b/deckster/src/model/image_filter.rs @@ -1,22 +1,19 @@ -use std::fmt::{Display, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::str::FromStr; -use rgb::RGB8; use serde_with::{DeserializeFromStr, SerializeDisplay}; use thiserror::Error; -use tiny_skia::IntRect; -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::geometry::IntRectWrapper; +use crate::model::rgb::RGB8Wrapper; #[derive(Debug, PartialEq, Clone, SerializeDisplay, DeserializeFromStr)] pub struct ImageFilter { - pub crop_original: Option, // applied before scale and rotate + pub crop: Option, pub scale: f32, pub clockwise_quarter_rotations: u8, - pub crop: Option, // applied after scale and rotate - pub color: Option, + pub color: Option, pub alpha: f32, pub blur: f32, pub grayscale: bool, @@ -34,7 +31,6 @@ impl Hash for ImageFilter { } const DEFAULT_IMAGE_FILTER: ImageFilter = ImageFilter { - crop_original: None, scale: 1.0, clockwise_quarter_rotations: 0, crop: None, @@ -79,8 +75,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_int_rect_from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable { + fn parse_rect_filter_value(filter_name: &str, raw_value: String) -> Result { + IntRectWrapper::from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable { filter_name: filter_name.to_string(), raw_value, }) @@ -145,7 +141,7 @@ impl FromStr for ImageFilter { }; match filter_name.as_str() { - "crop_original" => result.crop_original = Some(parse_rect_filter_value(&filter_name, use_raw_value()?)?), + "crop" => result.crop = Some(parse_rect_filter_value(&filter_name, use_raw_value()?)?), "scale" => result.scale = parse_f32_filter_value(&filter_name, use_raw_value()?, "0..=100", Box::new(|v| (0.0..=100.0).contains(v)))?, "rotate" => { let raw_value = use_raw_value()?; @@ -162,12 +158,11 @@ impl FromStr for ImageFilter { _ => return Err(ImageFilterFromStringError::RotationNotAllowed), }; } - "crop" => result.crop = Some(parse_rect_filter_value(&filter_name, use_raw_value()?)?), "color" => { let raw_value = use_raw_value()?; result.color = Some( - parse_rgb8_from_hex_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable { + RGB8Wrapper::from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable { filter_name: filter_name.to_string(), raw_value, })?, @@ -188,12 +183,12 @@ impl FromStr for ImageFilter { impl Display for ImageFilter { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut is_first = true; - if let Some(rect) = self.crop_original { - if !is_first { - f.write_str("|")? - } - f.write_str("crop_original=")?; - fmt_positive_rect(&rect, f)?; + + if let Some(rect) = &self.crop { + // if !is_first { + // f.write_str("|")? + // } + f.write_fmt(format_args!("crop={}", rect))?; is_first = false; } @@ -201,8 +196,7 @@ impl Display for ImageFilter { if !is_first { f.write_str("|")? } - f.write_str("scale=")?; - self.scale.fmt(f)?; + f.write_fmt(format_args!("scale={}", self.scale))?; is_first = false; } @@ -210,17 +204,8 @@ impl Display for ImageFilter { if !is_first { f.write_str("|")? } - f.write_str("rotate=")?; - (self.clockwise_quarter_rotations * 90).fmt(f)?; - is_first = false; - } - if let Some(rect) = self.crop { - if !is_first { - f.write_str("|")? - } - f.write_str("crop=")?; - fmt_positive_rect(&rect, f)?; + f.write_fmt(format_args!("rotate={}", self.clockwise_quarter_rotations * 90))?; is_first = false; } @@ -228,8 +213,8 @@ impl Display for ImageFilter { if !is_first { f.write_str("|")? } - f.write_str("color=")?; - fmt_rgb8_as_hex_string(&color, f)?; + + f.write_fmt(format_args!("color={}", color))?; is_first = false; } @@ -237,8 +222,7 @@ impl Display for ImageFilter { if !is_first { f.write_str("|")? } - f.write_str("alpha=")?; - self.alpha.fmt(f)?; + f.write_fmt(format_args!("alpha={}", self.alpha))?; is_first = false; } @@ -246,8 +230,7 @@ impl Display for ImageFilter { if !is_first { f.write_str("|")? } - f.write_str("blur=")?; - self.blur.fmt(f)?; + f.write_fmt(format_args!("blur={}", self.blur))?; is_first = false; } diff --git a/deckster/src/model/knob_modes/audio_volume.rs b/deckster/src/model/knob_modes/audio_volume.rs index 7291981..2e905ee 100644 --- a/deckster/src/model/knob_modes/audio_volume.rs +++ b/deckster/src/model/knob_modes/audio_volume.rs @@ -5,7 +5,7 @@ use regex::Regex; use serde::Deserialize; use crate::model::icon_descriptor::IconDescriptorString; -use crate::model::rgb::SerializableRGB8WithOptionalAlpha; +use crate::model::rgb::RGB8WithOptionalA; #[derive(Debug, Deserialize)] pub struct Config { @@ -54,12 +54,12 @@ pub enum State { #[derive(Debug, Deserialize)] pub struct CircleIndicatorConfig { - pub color: SerializableRGB8WithOptionalAlpha, + pub color: RGB8WithOptionalA, pub width: NonZeroU8, pub radius: u8, } #[derive(Debug, Deserialize)] pub struct BarIndicatorConfig { - pub color: SerializableRGB8WithOptionalAlpha, + pub color: RGB8WithOptionalA, } diff --git a/deckster/src/model/knob_page.rs b/deckster/src/model/knob_page.rs index 7104e65..5d7a6f1 100644 --- a/deckster/src/model/knob_page.rs +++ b/deckster/src/model/knob_page.rs @@ -4,7 +4,7 @@ use enum_map::EnumMap; use serde::Deserialize; use crate::model::icon_descriptor::IconDescriptorString; -use crate::model::rgb::SerializableRGB8WithOptionalAlpha; +use crate::model::rgb::RGB8WithOptionalA; use crate::model::{knob_modes, KnobPosition}; #[derive(Debug, Deserialize)] @@ -39,12 +39,12 @@ pub struct KnobIndicators { #[derive(Debug, Deserialize)] pub struct KnobIndicatorBarConfig { - pub color: SerializableRGB8WithOptionalAlpha, + pub color: RGB8WithOptionalA, } #[derive(Debug, Deserialize)] pub struct KnobIndicatorCircleConfig { - pub color: SerializableRGB8WithOptionalAlpha, + pub color: RGB8WithOptionalA, pub width: u8, pub radius: u8, } diff --git a/deckster/src/model/rgb.rs b/deckster/src/model/rgb.rs index b20fdb9..f1bf4b5 100644 --- a/deckster/src/model/rgb.rs +++ b/deckster/src/model/rgb.rs @@ -1,104 +1,107 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; +use derive_more::{Deref, From, Into}; use rgb::{RGB8, RGBA8}; use serde_with::{DeserializeFromStr, SerializeDisplay}; use thiserror::Error; #[derive(Debug, Error)] #[error("The input value does not match the required format.")] -pub struct RGBParsingError {} +pub struct ParsingError {} -pub fn parse_rgb8_from_hex_str(s: &str) -> Result { +fn parse_rgb8_from_hex_str(s: &str) -> Result { let first_index = if s.starts_with('#') { 1 } else { 0 }; if s.len() - first_index == 6 { - let r = u8::from_str_radix(&s[first_index..(first_index + 2)], 16).map_err(|_| RGBParsingError {})?; - let g = u8::from_str_radix(&s[(first_index + 2)..(first_index + 4)], 16).map_err(|_| RGBParsingError {})?; - let b = u8::from_str_radix(&s[(first_index + 4)..(first_index + 6)], 16).map_err(|_| RGBParsingError {})?; + let r = u8::from_str_radix(&s[first_index..(first_index + 2)], 16).map_err(|_| ParsingError {})?; + let g = u8::from_str_radix(&s[(first_index + 2)..(first_index + 4)], 16).map_err(|_| ParsingError {})?; + let b = u8::from_str_radix(&s[(first_index + 4)..(first_index + 6)], 16).map_err(|_| ParsingError {})?; return Ok(RGB8::new(r, g, b)); } - Err(RGBParsingError {}) + Err(ParsingError {}) } -pub fn fmt_rgb8_as_hex_string(v: &RGB8, f: &mut Formatter<'_>) -> std::fmt::Result { +fn fmt_rgb8_as_hex_string(v: &RGB8, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{:#04x}{:#04x}{:#04x}", v.r, v.g, v.b)) } -pub fn parse_rgba8_from_hex_str(s: &str) -> Result { +fn parse_rgba8_from_hex_str(s: &str) -> Result { let first_index = if s.starts_with('#') { 1 } else { 0 }; if s.len() - first_index == 8 { - let r = u8::from_str_radix(&s[first_index..(first_index + 2)], 16).map_err(|_| RGBParsingError {})?; - let g = u8::from_str_radix(&s[(first_index + 2)..(first_index + 4)], 16).map_err(|_| RGBParsingError {})?; - let b = u8::from_str_radix(&s[(first_index + 4)..(first_index + 6)], 16).map_err(|_| RGBParsingError {})?; - let a = u8::from_str_radix(&s[(first_index + 6)..(first_index + 8)], 16).map_err(|_| RGBParsingError {})?; + let r = u8::from_str_radix(&s[first_index..(first_index + 2)], 16).map_err(|_| ParsingError {})?; + let g = u8::from_str_radix(&s[(first_index + 2)..(first_index + 4)], 16).map_err(|_| ParsingError {})?; + let b = u8::from_str_radix(&s[(first_index + 4)..(first_index + 6)], 16).map_err(|_| ParsingError {})?; + let a = u8::from_str_radix(&s[(first_index + 6)..(first_index + 8)], 16).map_err(|_| ParsingError {})?; return Ok(RGBA8::new(r, g, b, a)); } - Err(RGBParsingError {}) + Err(ParsingError {}) } -pub fn fmt_rgba8_as_hex_string(v: &RGBA8, f: &mut Formatter<'_>) -> std::fmt::Result { +fn fmt_rgba8_as_hex_string(v: &RGBA8, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{:#04x}{:#04x}{:#04x}{:#04x}", v.r, v.g, v.b, v.a)) } -pub fn parse_rgb8_with_optional_alpha_from_hex_str(s: &str, fallback_alpha: u8) -> Result { +fn parse_rgb8_with_optional_alpha_from_hex_str(s: &str, fallback_alpha: u8) -> Result { // optionally +1 for the '#' match s.len() { 6 | 7 => Ok(parse_rgb8_from_hex_str(s)?.alpha(fallback_alpha)), 8 | 9 => Ok(parse_rgba8_from_hex_str(s)?), - _ => Err(RGBParsingError {}), + _ => Err(ParsingError {}), } } -#[derive(Debug, SerializeDisplay, DeserializeFromStr)] -pub struct SerializableRGB8(pub RGB8); +#[derive(Debug, Copy, Clone, PartialEq, Hash, Deref, From, Into, SerializeDisplay, DeserializeFromStr)] +pub struct RGB8Wrapper(RGB8); -impl FromStr for SerializableRGB8 { - type Err = RGBParsingError; +impl FromStr for RGB8Wrapper { + type Err = ParsingError; fn from_str(s: &str) -> Result { - parse_rgb8_from_hex_str(s).map(SerializableRGB8) + parse_rgb8_from_hex_str(s).map(RGB8Wrapper) } } -impl Display for SerializableRGB8 { +impl Display for RGB8Wrapper { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fmt_rgb8_as_hex_string(&self.0, f) } } -#[derive(Debug, SerializeDisplay, DeserializeFromStr)] -pub struct SerializableRGBA8(pub RGBA8); +#[derive(Debug, Copy, Clone, PartialEq, Hash, Deref, From, Into, SerializeDisplay, DeserializeFromStr)] +#[repr(transparent)] +pub struct RGBA8Wrapper(pub RGBA8); -impl FromStr for SerializableRGBA8 { - type Err = RGBParsingError; +impl FromStr for RGBA8Wrapper { + type Err = ParsingError; fn from_str(s: &str) -> Result { - parse_rgba8_from_hex_str(s).map(SerializableRGBA8) + parse_rgba8_from_hex_str(s).map(RGBA8Wrapper) } } -impl Display for SerializableRGBA8 { +impl Display for RGBA8Wrapper { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fmt_rgba8_as_hex_string(&self.0, f) } } -#[derive(Debug, SerializeDisplay, DeserializeFromStr)] -pub struct SerializableRGB8WithOptionalAlpha(pub RGBA8); +#[derive(Debug, Copy, Clone, PartialEq, Hash, Deref, From, Into, SerializeDisplay, DeserializeFromStr)] +#[repr(transparent)] +pub struct RGB8WithOptionalA(RGBA8); -impl FromStr for SerializableRGB8WithOptionalAlpha { - type Err = RGBParsingError; +impl FromStr for RGB8WithOptionalA { + type Err = ParsingError; fn from_str(s: &str) -> Result { - parse_rgb8_with_optional_alpha_from_hex_str(s, 0xff).map(SerializableRGB8WithOptionalAlpha) + parse_rgb8_with_optional_alpha_from_hex_str(s, 0xff).map(RGB8WithOptionalA) } } -impl Display for SerializableRGB8WithOptionalAlpha { +impl Display for RGB8WithOptionalA { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fmt_rgba8_as_hex_string(&self.0, f) } diff --git a/deckster/src/runner/mod.rs b/deckster/src/runner/mod.rs index f2a6207..13c07e3 100644 --- a/deckster/src/runner/mod.rs +++ b/deckster/src/runner/mod.rs @@ -258,7 +258,7 @@ fn get_correct_button_color(config: &model::config::Config, state: &mut State, b if key_page == &state.active_key_page_id { if let Some(knob_page) = &button_config.knob_page { if knob_page == &state.active_knob_page_id { - return config.active_button_color.0; + return config.active_button_color.into(); } } } @@ -266,7 +266,7 @@ fn get_correct_button_color(config: &model::config::Config, state: &mut State, b return RGB8::new(0, 0, 0); } - config.inactive_button_color.0 + config.inactive_button_color.into() } fn get_key_index_for_position(key_grid: &LoupedeckDeviceKeyGridCharacteristics, position: KeyPosition) -> Option {