commit
This commit is contained in:
parent
026a524c20
commit
102de5504b
19 changed files with 822 additions and 271 deletions
241
Cargo.lock
generated
241
Cargo.lock
generated
|
@ -76,7 +76,7 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -86,9 +86,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
|
@ -128,6 +134,26 @@ version = "0.21.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.66.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -167,6 +193,25 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.15.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6100bc57b6209840798d95cb2775684849d332f7bd788db2a8c8caf7ef82a41a"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -186,6 +231,17 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.13"
|
||||
|
@ -271,6 +327,21 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie-factory"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
|
@ -366,6 +437,7 @@ dependencies = [
|
|||
"log",
|
||||
"loupedeck_serial",
|
||||
"once_cell",
|
||||
"pipewire",
|
||||
"regex",
|
||||
"resvg",
|
||||
"rgb",
|
||||
|
@ -395,7 +467,7 @@ version = "0.99.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"convert_case 0.4.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
|
@ -495,7 +567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -629,6 +701,12 @@ version = "0.28.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
|
@ -756,7 +834,7 @@ checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -795,18 +873,62 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0434617020ddca18b86067912970c55410ca654cdafd775480322f50b857a8c4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cc",
|
||||
"convert_case 0.6.0",
|
||||
"cookie-factory",
|
||||
"libc",
|
||||
"libspa-sys",
|
||||
"nix",
|
||||
"nom",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libspa-sys"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e70ca3f3e70f858ef363046d06178c427b4e0b63d210c95fd87d752679d345"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev"
|
||||
version = "0.3.0"
|
||||
|
@ -896,6 +1018,21 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
|
@ -924,6 +1061,18 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -989,6 +1138,12 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
|
@ -1001,6 +1156,40 @@ version = "0.2.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pipewire"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d009c8dd65e890b515a71950f7e4c801523b8894ff33863a40830bf762e9e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.1",
|
||||
"libc",
|
||||
"libspa",
|
||||
"libspa-sys",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"pipewire-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pipewire-sys"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "890c084e7b737246cb4799c86b71a0e4da536031ff7473dd639eba9f95039f64"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libspa-sys",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.28"
|
||||
|
@ -1166,7 +1355,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1342,6 +1531,12 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
|
@ -1453,6 +1648,25 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.0"
|
||||
|
@ -1809,6 +2023,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -1931,6 +2151,15 @@ dependencies = [
|
|||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
|
@ -29,3 +29,4 @@ tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "rt-mul
|
|||
toml = "0.8.8"
|
||||
walkdir = "2.4.0"
|
||||
once_cell = "1.19.0"
|
||||
pipewire = "0.7.2"
|
|
@ -1,32 +1,32 @@
|
|||
[knobs.left-top]
|
||||
icon = "@ph/microphone-light"
|
||||
[knobs.right-top]
|
||||
icon = "@ph/microphone-light[scale=0.9]"
|
||||
indicator.circle.color = "#ffffff"
|
||||
indicator.circle.width = 2
|
||||
indicator.circle.radius = 40
|
||||
|
||||
mode.audio_volume.direction = "input"
|
||||
mode.audio_volume.regex = "Microphone"
|
||||
mode.audio_volume.label.muted = "Muted"
|
||||
mode.audio_volume.icon.inactive = "@ph/microphone-slash-light[alpha=0.9|color=#fc4646]"
|
||||
mode.audio_volume.disable_press_to_unmute = true
|
||||
mode.audio_volume.muted_turn_action = "unmute-at-zero"
|
||||
mode.audio_volume.style.muted.label = "Muted"
|
||||
mode.audio_volume.style.inactive.icon = "@ph/microphone-slash-light[alpha=0.9|color=#fc4646]"
|
||||
|
||||
[knobs.left-middle]
|
||||
icon = "@apps/discord"
|
||||
[knobs.right-middle]
|
||||
icon = "@apps/discord[scale=0.25]"
|
||||
indicator.bar.color = "#ffffff"
|
||||
|
||||
mode.audio_volume.regex = "Discord"
|
||||
mode.audio_volume.label.inactive = ""
|
||||
mode.audio_volume.label.active = "{percentage}%"
|
||||
mode.audio_volume.label.muted = "Muted"
|
||||
mode.audio_volume.icon.inactive = "@apps/discord[grayscale|alpha=0.9]"
|
||||
mode.audio_volume.style.active.label = "{percentage}%"
|
||||
mode.audio_volume.style.muted.label = "Muted"
|
||||
mode.audio_volume.style.inactive.label = ""
|
||||
mode.audio_volume.style.inactive.icon = "@apps/discord[grayscale|alpha=0.9]"
|
||||
|
||||
[knobs.left-bottom]
|
||||
icon = "@apps/spotify"
|
||||
[knobs.right-bottom]
|
||||
icon = "@apps/spotify[scale=1.1]"
|
||||
indicator.bar.color = "#ffffff"
|
||||
|
||||
mode.audio_volume.regex = "Spotify"
|
||||
mode.audio_volume.label.inactive = ""
|
||||
mode.audio_volume.label.active = "{percentage}%"
|
||||
mode.audio_volume.label.muted = "Muted"
|
||||
mode.audio_volume.icon.inactive = "@apps/spotify[grayscale|alpha=0.9]"
|
||||
mode.audio_volume.style.active.label = "{percentage}%"
|
||||
mode.audio_volume.style.muted.label = "Muted"
|
||||
mode.audio_volume.style.inactive.label = ""
|
||||
mode.audio_volume.style.inactive.icon = "@apps/spotify[grayscale|alpha=0.9]"
|
|
@ -4,12 +4,13 @@ use std::path::Path;
|
|||
|
||||
use color_eyre::eyre::{eyre, ContextCompat, WrapErr};
|
||||
use color_eyre::Result;
|
||||
use resvg::usvg::tiny_skia_path::IntSize;
|
||||
use resvg::usvg::{TextRendering, TreeParsing, TreeTextToPath};
|
||||
use tiny_skia::{Pixmap, Transform};
|
||||
use tiny_skia::{BlendMode, FilterQuality, Pixmap, PixmapPaint, Transform};
|
||||
|
||||
use crate::model::config::{Config, IconFormat, IconPack};
|
||||
use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource};
|
||||
use crate::model::image_filter::ImageFilterDestructive;
|
||||
use crate::model::image_filter::{ImageFilter, ImageFilterDestructive};
|
||||
use crate::model::{key_page, knob_page};
|
||||
|
||||
mod destructive_filter;
|
||||
|
@ -225,3 +226,37 @@ fn read_image_from_svg(path: &Path, dpi: f32, font_db: &resvg::usvg::fontdb::Dat
|
|||
|
||||
Ok(pixmap)
|
||||
}
|
||||
|
||||
pub fn render_icon_in(pixmap: &mut Pixmap, global_icon_filter_by_pack_id: &HashMap<String, ImageFilter>, loaded_icons: &LoadedIconsMap, icon: &IconDescriptor) {
|
||||
let filter = if let Some(global_filter) = icon.source.pack_id().and_then(|i| global_icon_filter_by_pack_id.get(i)) {
|
||||
icon.filter.merge_over(global_filter)
|
||||
} else {
|
||||
icon.filter.clone()
|
||||
};
|
||||
|
||||
let loaded_icon = &loaded_icons[&(icon.source.clone(), filter.destructive)];
|
||||
|
||||
let scale = filter.transform.scale / loaded_icon.pre_scale;
|
||||
|
||||
let scaled_size = IntSize::from_wh(loaded_icon.pixmap.width(), loaded_icon.pixmap.height())
|
||||
.unwrap()
|
||||
.scale_by(scale)
|
||||
.unwrap();
|
||||
|
||||
pixmap.draw_pixmap(
|
||||
(((pixmap.width() as i32 - scaled_size.width() as i32) / 2) as f32 / scale).round() as i32,
|
||||
(((pixmap.height() as i32 - scaled_size.height() as i32) / 2) as f32 / scale).round() as i32,
|
||||
loaded_icon.pixmap.as_ref(),
|
||||
&PixmapPaint {
|
||||
opacity: filter.transform.alpha,
|
||||
blend_mode: BlendMode::SourceOver,
|
||||
quality: FilterQuality::Bicubic,
|
||||
},
|
||||
Transform::from_scale(scale, scale).post_rotate_at(
|
||||
(filter.transform.clockwise_quarter_rotations as f32) * 90.0,
|
||||
pixmap.width() as f32 / 2.0,
|
||||
pixmap.height() as f32 / 2.0,
|
||||
),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ pub async fn main() -> Result<()> {
|
|||
.into_iter()
|
||||
.map(|p| model::knob_page::Page {
|
||||
id: p.inner.id.clone().unwrap_or(p.fallback_id),
|
||||
knobs: p.inner.knobs.into_iter().collect(),
|
||||
knobs: p.inner.knobs.into_iter().map(|(p, k)| (p, Arc::new(k))).collect(),
|
||||
})
|
||||
.map(|p| (p.id.clone(), p))
|
||||
.collect();
|
||||
|
|
|
@ -13,14 +13,14 @@ use crate::modes;
|
|||
pub struct File {
|
||||
pub id: Option<String>,
|
||||
pub scrolling: Option<ScrollingConfig>,
|
||||
pub keys: HashMap<KeyPosition, KeyConfig>,
|
||||
pub keys: HashMap<KeyPosition, Key>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Page {
|
||||
pub id: String,
|
||||
pub scrolling: Option<ScrollingConfig>,
|
||||
pub keys: HashMap<KeyPosition, Arc<KeyConfig>>,
|
||||
pub keys: HashMap<KeyPosition, Arc<Key>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -64,7 +64,7 @@ impl KeyStyle {
|
|||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct KeyConfig {
|
||||
pub struct Key {
|
||||
#[serde(default, flatten)]
|
||||
pub base_style: KeyStyle,
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use enum_map::EnumMap;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::icon_descriptor::IconDescriptor;
|
||||
use crate::model::position::KnobPosition;
|
||||
|
@ -17,7 +18,7 @@ pub struct File {
|
|||
#[derive(Debug)]
|
||||
pub struct Page {
|
||||
pub id: String,
|
||||
pub knobs: EnumMap<KnobPosition, Knob>,
|
||||
pub knobs: EnumMap<KnobPosition, Arc<Knob>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
|
@ -28,34 +29,89 @@ pub struct Knob {
|
|||
pub mode: KnobModes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct KnobIndicators {
|
||||
pub bar: Option<KnobIndicatorBarConfig>,
|
||||
pub circle: Option<KnobIndicatorCircleConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
impl KnobIndicators {
|
||||
pub fn merge_over(&self, base: &Self) -> Self {
|
||||
Self {
|
||||
bar: self
|
||||
.bar
|
||||
.as_ref()
|
||||
.zip(base.bar.as_ref())
|
||||
.map(|(a, b)| a.merge_over(b))
|
||||
.or_else(|| self.bar.clone())
|
||||
.or_else(|| base.bar.clone()),
|
||||
circle: self
|
||||
.circle
|
||||
.as_ref()
|
||||
.zip(base.circle.as_ref())
|
||||
.map(|(a, b)| a.merge_over(b))
|
||||
.or_else(|| self.circle.clone())
|
||||
.or_else(|| base.circle.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct KnobIndicatorBarConfig {
|
||||
pub color: Option<RGB8WithOptionalA>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
impl KnobIndicatorBarConfig {
|
||||
pub fn merge_over(&self, base: &Self) -> Self {
|
||||
Self {
|
||||
color: self.color.as_ref().or(base.color.as_ref()).cloned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct KnobIndicatorCircleConfig {
|
||||
pub color: Option<RGB8WithOptionalA>,
|
||||
pub width: Option<u8>,
|
||||
pub radius: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
impl KnobIndicatorCircleConfig {
|
||||
pub fn merge_over(&self, base: &Self) -> Self {
|
||||
Self {
|
||||
color: self.color.as_ref().or(base.color.as_ref()).cloned(),
|
||||
width: self.width.as_ref().or(base.width.as_ref()).cloned(),
|
||||
radius: self.radius.as_ref().or(base.radius.as_ref()).cloned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct KnobStyle {
|
||||
pub label: Option<String>,
|
||||
pub icon: Option<IconDescriptor>,
|
||||
pub indicators: Option<KnobIndicators>,
|
||||
}
|
||||
|
||||
impl KnobStyle {
|
||||
pub fn merge_over(&self, base: &Self) -> Self {
|
||||
Self {
|
||||
label: self.label.as_ref().or(base.label.as_ref()).cloned(),
|
||||
icon: self.icon.as_ref().or(base.icon.as_ref()).cloned(),
|
||||
indicators: self
|
||||
.indicators
|
||||
.as_ref()
|
||||
.zip(base.indicators.as_ref())
|
||||
.map(|(a, b)| a.merge_over(b))
|
||||
.or_else(|| self.indicators.clone())
|
||||
.or_else(|| base.indicators.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct KnobModes {
|
||||
pub audio_volume: Option<modes::knob::audio_volume::Config>,
|
||||
pub audio_volume: Option<Arc<modes::knob::audio_volume::Config>>,
|
||||
}
|
||||
|
||||
pub type StyleByStateMap<State> = HashMap<State, KnobStyle>;
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::fmt::{Display, Formatter};
|
|||
use std::str::FromStr;
|
||||
|
||||
use enum_map::Enum;
|
||||
use enum_ordinalize::Ordinalize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use thiserror::Error;
|
||||
|
@ -55,7 +56,7 @@ impl Display for KeyPath {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize, Enum)]
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize, Enum, Ordinalize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum KnobPosition {
|
||||
LeftTop,
|
||||
|
|
|
@ -28,7 +28,7 @@ pub enum KeyEvent {
|
|||
}
|
||||
|
||||
pub fn start_handlers(
|
||||
keys: impl Iterator<Item = (KeyPath, Arc<model::key_page::KeyConfig>)>,
|
||||
keys: impl Iterator<Item = (KeyPath, Arc<model::key_page::Key>)>,
|
||||
events: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||
commands: flume::Sender<IoWorkerCommand>,
|
||||
) {
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::model::knob_page::StyleByStateMap;
|
||||
use crate::model::position::KnobPath;
|
||||
use crate::modes::knob::KnobEvent;
|
||||
use crate::runner::command::IoWorkerCommand;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
|
@ -47,3 +52,13 @@ pub enum State {
|
|||
Active,
|
||||
Muted,
|
||||
}
|
||||
|
||||
pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::Receiver<(KnobPath, KnobEvent)>, commands: flume::Sender<IoWorkerCommand>) {
|
||||
while let Ok((event_path, event)) = events.recv().await {
|
||||
if event_path != path {
|
||||
continue;
|
||||
}
|
||||
|
||||
dbg!(path.clone(), event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,37 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::model;
|
||||
use crate::model::position::KnobPath;
|
||||
use crate::runner::command::IoWorkerCommand;
|
||||
|
||||
pub mod audio_volume;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum RotationDirection {
|
||||
Clockwise,
|
||||
Counterclockwise,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum KnobEvent {
|
||||
Press,
|
||||
ButtonDown,
|
||||
ButtonUp,
|
||||
Rotate { direction: RotationDirection },
|
||||
VisibilityChange { is_visible: bool },
|
||||
}
|
||||
|
||||
pub fn start_handlers(
|
||||
knobs: impl Iterator<Item = (KnobPath, Arc<model::knob_page::Knob>)>,
|
||||
events: broadcast::Sender<(KnobPath, KnobEvent)>,
|
||||
commands: flume::Sender<IoWorkerCommand>,
|
||||
) {
|
||||
for (path, config) in knobs {
|
||||
if let Some(c) = &config.mode.audio_volume {
|
||||
tokio::spawn(audio_volume::handle(path.clone(), Arc::clone(c), events.subscribe(), commands.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@ use serde::{Deserialize, Serialize};
|
|||
use loupedeck_serial::commands::VibrationPattern;
|
||||
|
||||
use crate::model::key_page::KeyStyle;
|
||||
use crate::model::position::KeyPath;
|
||||
use crate::model::knob_page::KnobStyle;
|
||||
use crate::model::position::{KeyPath, KnobPath};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum IoWorkerCommand {
|
||||
Vibrate { pattern: VibrationPattern },
|
||||
SetActivePages { key_page_id: String, knob_page_id: String },
|
||||
SetKeyStyle { path: KeyPath, value: Option<KeyStyle> },
|
||||
SetKnobStyle { path: KnobPath, value: Option<KnobStyle> },
|
||||
}
|
||||
|
|
|
@ -3,16 +3,14 @@ use std::collections::HashMap;
|
|||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use resvg::usvg::tiny_skia_path::PathBuilder;
|
||||
use tiny_skia::{
|
||||
BlendMode, Color, FilterQuality, IntSize, LineCap, LineJoin, Paint, Pixmap, PixmapPaint, PremultipliedColorU8, Rect, Shader, Stroke, Transform,
|
||||
};
|
||||
use tiny_skia::{Color, IntSize, LineCap, LineJoin, Paint, Pixmap, PremultipliedColorU8, Rect, Shader, Stroke, Transform};
|
||||
|
||||
use loupedeck_serial::util::Endianness;
|
||||
|
||||
use crate::icons::LoadedIconsMap;
|
||||
use crate::icons::{render_icon_in, LoadedIconsMap};
|
||||
use crate::model::image_filter::ImageFilter;
|
||||
use crate::runner::graphics::labels::LabelRenderer;
|
||||
use crate::runner::state::Key;
|
||||
use crate::runner::state::{Key, Knob};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GraphicsContext {
|
||||
|
@ -30,42 +28,12 @@ pub fn render_key(context: &GraphicsContext, key_size: IntSize, state: Option<&K
|
|||
let style = style.as_ref().unwrap_or(&state.base_style);
|
||||
|
||||
if let Some(icon) = &style.icon {
|
||||
let filter = if let Some(global_filter) = icon.source.pack_id().and_then(|i| context.global_icon_filter_by_pack_id.get(i)) {
|
||||
icon.filter.merge_over(global_filter)
|
||||
} else {
|
||||
icon.filter.clone()
|
||||
};
|
||||
|
||||
let loaded_icon = &context.loaded_icons[&(icon.source.clone(), filter.destructive)];
|
||||
|
||||
let scale = filter.transform.scale / loaded_icon.pre_scale;
|
||||
|
||||
let scaled_size = IntSize::from_wh(loaded_icon.pixmap.width(), loaded_icon.pixmap.height())
|
||||
.unwrap()
|
||||
.scale_by(scale)
|
||||
.unwrap();
|
||||
|
||||
pixmap.draw_pixmap(
|
||||
(((key_size.width() as i32 - scaled_size.width() as i32) / 2) as f32 / scale).round() as i32,
|
||||
(((key_size.height() as i32 - scaled_size.height() as i32) / 2) as f32 / scale).round() as i32,
|
||||
loaded_icon.pixmap.as_ref(),
|
||||
&PixmapPaint {
|
||||
opacity: filter.transform.alpha,
|
||||
blend_mode: BlendMode::SourceOver,
|
||||
quality: FilterQuality::Bicubic,
|
||||
},
|
||||
Transform::from_scale(scale, scale).post_rotate_at(
|
||||
(filter.transform.clockwise_quarter_rotations as f32) * 90.0,
|
||||
key_size.width() as f32 / 2.0,
|
||||
key_size.height() as f32 / 2.0,
|
||||
),
|
||||
None,
|
||||
);
|
||||
render_icon_in(&mut pixmap, &context.global_icon_filter_by_pack_id, &context.loaded_icons, icon);
|
||||
}
|
||||
|
||||
if let Some(label) = &style.label {
|
||||
if !label.is_empty() {
|
||||
context.label_renderer.borrow_mut().render(&mut pixmap, &label);
|
||||
context.label_renderer.borrow_mut().render(&mut pixmap, label);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +60,27 @@ pub fn render_key(context: &GraphicsContext, key_size: IntSize, state: Option<&K
|
|||
convert_pixels_to_rgb565(pixmap.pixels(), context.buffer_endianness).freeze()
|
||||
}
|
||||
|
||||
pub fn render_knob(context: &GraphicsContext, screen_size: IntSize, state: Option<&Knob>) -> Bytes {
|
||||
let mut pixmap = Pixmap::new(screen_size.width(), screen_size.height()).unwrap();
|
||||
|
||||
if let Some(state) = state {
|
||||
let style = state.style.as_ref().map(|s| s.merge_over(&state.base_style));
|
||||
let style = style.as_ref().unwrap_or(&state.base_style);
|
||||
|
||||
if let Some(icon) = &style.icon {
|
||||
render_icon_in(&mut pixmap, &context.global_icon_filter_by_pack_id, &context.loaded_icons, icon);
|
||||
}
|
||||
|
||||
if let Some(label) = &style.label {
|
||||
if !label.is_empty() {
|
||||
context.label_renderer.borrow_mut().render(&mut pixmap, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
convert_pixels_to_rgb565(pixmap.pixels(), context.buffer_endianness).freeze()
|
||||
}
|
||||
|
||||
fn convert_pixels_to_rgb565(pixels: &[PremultipliedColorU8], endianness: Endianness) -> BytesMut {
|
||||
let pixel_count = pixels.len();
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
@ -7,7 +6,6 @@ use std::time::Instant;
|
|||
|
||||
use color_eyre::eyre::{ContextCompat, WrapErr};
|
||||
use color_eyre::Result;
|
||||
use enum_map::EnumMap;
|
||||
use enum_ordinalize::Ordinalize;
|
||||
use log::{info, trace};
|
||||
use rgb::RGB8;
|
||||
|
@ -15,19 +13,19 @@ use tiny_skia::IntSize;
|
|||
use tokio::sync::broadcast;
|
||||
|
||||
use command::IoWorkerCommand;
|
||||
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics};
|
||||
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics, LoupedeckDisplayRect, LoupedeckKnob};
|
||||
use loupedeck_serial::commands::VibrationPattern;
|
||||
use loupedeck_serial::device::LoupedeckDevice;
|
||||
use loupedeck_serial::events::LoupedeckEvent;
|
||||
use loupedeck_serial::events::{LoupedeckEvent, RotationDirection};
|
||||
|
||||
use crate::icons::{get_used_icon_descriptors, load_icons, LoadedIconsMap};
|
||||
use crate::model;
|
||||
use crate::model::knob_page::KnobStyle;
|
||||
use crate::model::position::{ButtonPosition, KeyPath, KeyPosition, KnobPath};
|
||||
use crate::modes::key::{start_handlers, KeyEvent, KeyTouchEventKind};
|
||||
use crate::model::position::{ButtonPosition, KeyPath, KeyPosition, KnobPath, KnobPosition};
|
||||
use crate::modes::key::{KeyEvent, KeyTouchEventKind};
|
||||
use crate::modes::knob::KnobEvent;
|
||||
use crate::runner::graphics::labels::LabelRenderer;
|
||||
use crate::runner::graphics::{render_key, GraphicsContext};
|
||||
use crate::runner::state::{Key, State};
|
||||
use crate::runner::graphics::{render_key, render_knob, GraphicsContext};
|
||||
use crate::runner::state::{Key, Knob, State};
|
||||
use crate::{model, modes};
|
||||
|
||||
pub mod command;
|
||||
mod graphics;
|
||||
|
@ -55,8 +53,9 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
|||
device.set_brightness(0.5);
|
||||
device.vibrate(VibrationPattern::RiseFall);
|
||||
|
||||
let (commands_sender, commands_receiver) = flume::bounded::<IoWorkerCommand>(20);
|
||||
let key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)> = broadcast::Sender::new(20);
|
||||
let (commands_sender, commands_receiver) = flume::bounded::<IoWorkerCommand>(5);
|
||||
let key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)> = broadcast::Sender::new(5);
|
||||
let knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)> = broadcast::Sender::new(5);
|
||||
|
||||
commands_sender
|
||||
.send(IoWorkerCommand::SetActivePages {
|
||||
|
@ -65,24 +64,23 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
let cloned_config = Arc::clone(&config);
|
||||
let cloned_commands_sender = commands_sender.clone();
|
||||
let cloned_key_events_sender = key_events_sender.clone();
|
||||
let io_worker_context = IoWorkerContext::create(
|
||||
Arc::clone(&config),
|
||||
icons,
|
||||
device,
|
||||
commands_sender.clone(),
|
||||
key_events_sender.clone(),
|
||||
knob_events_sender.clone(),
|
||||
);
|
||||
|
||||
let io_worker_thread = thread::Builder::new()
|
||||
.name("deckster IO worker".to_owned())
|
||||
.spawn(move || {
|
||||
do_io_work(
|
||||
cloned_config,
|
||||
icons,
|
||||
device,
|
||||
cloned_key_events_sender,
|
||||
cloned_commands_sender,
|
||||
commands_receiver,
|
||||
)
|
||||
do_io_work(io_worker_context, commands_receiver);
|
||||
})
|
||||
.wrap_err("Could not spawn the worker thread")?;
|
||||
|
||||
start_handlers(
|
||||
modes::key::start_handlers(
|
||||
config.key_pages_by_id.iter().flat_map(|(page_id, page)| {
|
||||
page.keys.iter().map(|(position, key)| {
|
||||
(
|
||||
|
@ -95,6 +93,22 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
|||
})
|
||||
}),
|
||||
key_events_sender,
|
||||
commands_sender.clone(),
|
||||
);
|
||||
|
||||
modes::knob::start_handlers(
|
||||
config.knob_pages_by_id.iter().flat_map(|(page_id, page)| {
|
||||
page.knobs.iter().map(|(position, knob)| {
|
||||
(
|
||||
KnobPath {
|
||||
page_id: page_id.clone(),
|
||||
position,
|
||||
},
|
||||
Arc::clone(knob),
|
||||
)
|
||||
})
|
||||
}),
|
||||
knob_events_sender,
|
||||
commands_sender,
|
||||
);
|
||||
|
||||
|
@ -104,59 +118,6 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn create_state(config: &model::config::Config) -> State {
|
||||
let key_pages_by_id: HashMap<_, _> = config
|
||||
.key_pages_by_id
|
||||
.iter()
|
||||
.map(|(id, p)| state::KeyPage {
|
||||
id: id.clone(),
|
||||
keys_by_position: p
|
||||
.keys
|
||||
.iter()
|
||||
.map(|(position, k)| Key {
|
||||
path: KeyPath {
|
||||
page_id: p.id.clone(),
|
||||
position: *position,
|
||||
},
|
||||
base_style: k.base_style.clone(),
|
||||
style: None,
|
||||
})
|
||||
.map(|k| (k.path.position, k))
|
||||
.collect(),
|
||||
})
|
||||
.map(|p| (p.id.clone(), p))
|
||||
.collect();
|
||||
|
||||
let knob_pages_by_id: HashMap<_, _> = config
|
||||
.knob_pages_by_id
|
||||
.iter()
|
||||
.map(|(id, p)| state::KnobPage {
|
||||
id: id.clone(),
|
||||
knobs_by_position: EnumMap::from_fn(|position| {
|
||||
let knob_config = &p.knobs[position];
|
||||
|
||||
state::Knob {
|
||||
path: KnobPath {
|
||||
page_id: p.id.clone(),
|
||||
position,
|
||||
},
|
||||
base_style: knob_config.base_style.clone(),
|
||||
style: KnobStyle::default(),
|
||||
value: 0.0,
|
||||
}
|
||||
}),
|
||||
})
|
||||
.map(|p| (p.id.clone(), p))
|
||||
.collect();
|
||||
|
||||
State {
|
||||
active_key_page_id: config.initial.key_page.clone(),
|
||||
active_knob_page_id: config.initial.knob_page.clone(),
|
||||
key_pages_by_id,
|
||||
knob_pages_by_id,
|
||||
}
|
||||
}
|
||||
|
||||
enum IoWork {
|
||||
Event(LoupedeckEvent),
|
||||
Command(IoWorkerCommand),
|
||||
|
@ -165,20 +126,21 @@ enum IoWork {
|
|||
struct IoWorkerContext {
|
||||
config: Arc<model::config::Config>,
|
||||
device: LoupedeckDevice,
|
||||
state: State,
|
||||
commands_sender: flume::Sender<IoWorkerCommand>,
|
||||
key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||
knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)>,
|
||||
graphics: GraphicsContext,
|
||||
active_touch_ids: HashSet<u8>,
|
||||
}
|
||||
|
||||
fn do_io_work(
|
||||
impl IoWorkerContext {
|
||||
pub fn create(
|
||||
config: Arc<model::config::Config>,
|
||||
icons: LoadedIconsMap,
|
||||
device: LoupedeckDevice,
|
||||
key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||
commands_sender: flume::Sender<IoWorkerCommand>,
|
||||
commands_receiver: flume::Receiver<IoWorkerCommand>,
|
||||
) {
|
||||
let state = create_state(&config);
|
||||
key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||
knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)>,
|
||||
) -> Self {
|
||||
let buffer_endianness = device.characteristics().key_grid.display.endianness;
|
||||
let global_icon_filter_by_pack_id = config
|
||||
.icon_packs
|
||||
|
@ -188,20 +150,25 @@ fn do_io_work(
|
|||
|
||||
let label_renderer = RefCell::new(LabelRenderer::new(config.label_font_family.as_ref()));
|
||||
|
||||
let device_events_receiver = device.events();
|
||||
|
||||
let mut context = IoWorkerContext {
|
||||
IoWorkerContext {
|
||||
config,
|
||||
device,
|
||||
state,
|
||||
commands_sender,
|
||||
key_events_sender,
|
||||
knob_events_sender,
|
||||
graphics: GraphicsContext {
|
||||
loaded_icons: icons,
|
||||
buffer_endianness,
|
||||
label_renderer,
|
||||
global_icon_filter_by_pack_id,
|
||||
},
|
||||
active_touch_ids: HashSet::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_io_work(context: IoWorkerContext, commands_receiver: flume::Receiver<IoWorkerCommand>) {
|
||||
let mut state = State::create(&context.config);
|
||||
let device_events_receiver = context.device.events();
|
||||
|
||||
loop {
|
||||
let a = flume::Selector::new()
|
||||
|
@ -211,26 +178,26 @@ fn do_io_work(
|
|||
|
||||
match a {
|
||||
IoWork::Event(event) => {
|
||||
if !handle_event(&mut context, &commands_sender, &key_events_sender, event) {
|
||||
if !handle_event(&context, &mut state, event) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
IoWork::Command(command) => handle_command(&mut context, command),
|
||||
IoWork::Command(command) => handle_command(&context, &mut state, command),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
context: &mut IoWorkerContext,
|
||||
commands_sender: &flume::Sender<IoWorkerCommand>,
|
||||
key_events_sender: &broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||
event: LoupedeckEvent,
|
||||
) -> bool {
|
||||
fn handle_event(context: &IoWorkerContext, state: &mut State, event: LoupedeckEvent) -> bool {
|
||||
trace!("Handling event: {:?}", &event);
|
||||
|
||||
let send_key_event = |path: KeyPath, event: KeyEvent| {
|
||||
trace!("Sending key event ({}): {:?}", &path, &event);
|
||||
key_events_sender.send((path, event)).unwrap();
|
||||
context.key_events_sender.send((path, event)).unwrap();
|
||||
};
|
||||
|
||||
let send_knob_event = |path: KnobPath, event: KnobEvent| {
|
||||
trace!("Sending knob event ({:?}): {:?}", &path, &event);
|
||||
context.knob_events_sender.send((path, event)).unwrap();
|
||||
};
|
||||
|
||||
match event {
|
||||
|
@ -239,10 +206,11 @@ fn handle_event(
|
|||
let position = ButtonPosition::of(&button);
|
||||
let button_config = &context.config.buttons[position];
|
||||
|
||||
commands_sender
|
||||
context
|
||||
.commands_sender
|
||||
.send(IoWorkerCommand::SetActivePages {
|
||||
key_page_id: button_config.key_page.as_ref().unwrap_or(&context.state.active_key_page_id).clone(),
|
||||
knob_page_id: button_config.knob_page.as_ref().unwrap_or(&context.state.active_knob_page_id).clone(),
|
||||
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()
|
||||
}
|
||||
|
@ -251,7 +219,7 @@ fn handle_event(
|
|||
let display = characteristics.get_display_at_coordinates(x, y);
|
||||
|
||||
if let Some(display) = display {
|
||||
if display.name == characteristics.key_grid.display.name {
|
||||
if display == &characteristics.key_grid.display {
|
||||
let key_index = characteristics.key_grid.get_key_at_global_coordinates(x, y);
|
||||
if let Some(key_index) = key_index {
|
||||
let position = KeyPosition {
|
||||
|
@ -260,17 +228,19 @@ fn handle_event(
|
|||
};
|
||||
|
||||
let path = KeyPath {
|
||||
page_id: context.state.active_key_page_id.clone(),
|
||||
page_id: state.active_key_page_id.clone(),
|
||||
position,
|
||||
};
|
||||
|
||||
let (top_left_x, top_left_y, _, _) = characteristics.key_grid.get_local_key_rect_xywh(key_index).unwrap();
|
||||
let LoupedeckDisplayRect {
|
||||
x: top_left_x, y: top_left_y, ..
|
||||
} = characteristics.key_grid.get_local_key_rect(key_index).unwrap();
|
||||
|
||||
let kind = if is_end {
|
||||
context.active_touch_ids.remove(&touch_id);
|
||||
state.active_touch_ids.remove(&touch_id);
|
||||
KeyTouchEventKind::End
|
||||
} else {
|
||||
let is_new = context.active_touch_ids.insert(touch_id);
|
||||
let is_new = state.active_touch_ids.insert(touch_id);
|
||||
if is_new {
|
||||
KeyTouchEventKind::Start
|
||||
} else {
|
||||
|
@ -295,13 +265,36 @@ fn handle_event(
|
|||
}
|
||||
}
|
||||
}
|
||||
LoupedeckEvent::KnobRotate { knob, direction } => {
|
||||
let position = match knob {
|
||||
LoupedeckKnob::LeftTop => KnobPosition::LeftTop,
|
||||
LoupedeckKnob::LeftMiddle => KnobPosition::LeftMiddle,
|
||||
LoupedeckKnob::LeftBottom => KnobPosition::LeftBottom,
|
||||
LoupedeckKnob::RightTop => KnobPosition::RightTop,
|
||||
LoupedeckKnob::RightMiddle => KnobPosition::RightMiddle,
|
||||
LoupedeckKnob::RightBottom => KnobPosition::RightBottom,
|
||||
};
|
||||
|
||||
send_knob_event(
|
||||
KnobPath {
|
||||
page_id: state.active_knob_page_id.clone(),
|
||||
position,
|
||||
},
|
||||
KnobEvent::Rotate {
|
||||
direction: match direction {
|
||||
RotationDirection::Clockwise => modes::knob::RotationDirection::Clockwise,
|
||||
RotationDirection::Counterclockwise => modes::knob::RotationDirection::Counterclockwise,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_command(context: &mut IoWorkerContext, command: IoWorkerCommand) {
|
||||
fn handle_command(context: &IoWorkerContext, state: &mut State, command: IoWorkerCommand) {
|
||||
trace!("Handling command: {:?}", &command);
|
||||
|
||||
match command {
|
||||
|
@ -309,28 +302,43 @@ fn handle_command(context: &mut IoWorkerContext, command: IoWorkerCommand) {
|
|||
context.device.vibrate(pattern);
|
||||
}
|
||||
IoWorkerCommand::SetActivePages { key_page_id, knob_page_id } => {
|
||||
context.state.active_key_page_id = key_page_id;
|
||||
context.state.active_knob_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);
|
||||
|
||||
context.device.set_button_color(*button, get_correct_button_color(context, position)).unwrap();
|
||||
context
|
||||
.device
|
||||
.set_button_color(*button, get_correct_button_color(context, state, position))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let key_grid = &context.device.characteristics().key_grid;
|
||||
for index in 0..(key_grid.rows * key_grid.columns) {
|
||||
draw_key_at_index(context, index);
|
||||
draw_key_at_index(context, state, index);
|
||||
}
|
||||
|
||||
for position in KnobPosition::VARIANTS {
|
||||
draw_knob_at_position(context, state, *position);
|
||||
}
|
||||
|
||||
context.device.refresh_display(&key_grid.display).unwrap();
|
||||
}
|
||||
IoWorkerCommand::SetKeyStyle { path, value } => {
|
||||
context.state.mutate_key_for_command("SetKeyStyle", &path, |k| {
|
||||
state.mutate_key_for_command("SetKeyStyle", &path, |k| {
|
||||
k.style = value;
|
||||
});
|
||||
|
||||
draw_key_at_path_if_visible(context, path);
|
||||
draw_key_at_path_if_visible(context, state, path);
|
||||
context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
|
||||
}
|
||||
IoWorkerCommand::SetKnobStyle { path, value } => {
|
||||
state.mutate_knob_for_command("SetKnobStyle", &path, |k| {
|
||||
k.style = value;
|
||||
});
|
||||
|
||||
draw_knob_at_path_if_visible(context, state, path);
|
||||
context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -339,13 +347,13 @@ fn handle_command(context: &mut IoWorkerContext, command: IoWorkerCommand) {
|
|||
// active -> config.active_button_color
|
||||
// no actions defined -> #000000
|
||||
// inactive -> config.inactive_button_color
|
||||
fn get_correct_button_color(context: &IoWorkerContext, button_position: ButtonPosition) -> RGB8 {
|
||||
fn get_correct_button_color(context: &IoWorkerContext, state: &State, button_position: ButtonPosition) -> RGB8 {
|
||||
let button_config = &context.config.buttons[button_position];
|
||||
|
||||
if let Some(key_page) = &button_config.key_page {
|
||||
if key_page == &context.state.active_key_page_id {
|
||||
if key_page == &state.active_key_page_id {
|
||||
if let Some(knob_page) = &button_config.knob_page {
|
||||
if knob_page == &context.state.active_knob_page_id {
|
||||
if knob_page == &state.active_knob_page_id {
|
||||
return context.config.active_button_color.into();
|
||||
}
|
||||
}
|
||||
|
@ -379,29 +387,58 @@ fn get_key_position_for_index(key_grid: &LoupedeckDeviceKeyGridCharacteristics,
|
|||
|
||||
fn draw_key(context: &IoWorkerContext, index: u8, key: Option<&Key>) {
|
||||
let key_grid = &context.device.characteristics().key_grid;
|
||||
let (x, y, w, h) = key_grid.get_local_key_rect_xywh(index).unwrap();
|
||||
let rect = key_grid.get_local_key_rect(index).unwrap();
|
||||
|
||||
let p = render_key(&context.graphics, IntSize::from_wh(w as u32, h as u32).unwrap(), key);
|
||||
|
||||
context.device.replace_framebuffer_area_raw(&key_grid.display, x, y, w, h, p).unwrap();
|
||||
let buffer = render_key(&context.graphics, IntSize::from_wh(rect.w as u32, rect.h as u32).unwrap(), key);
|
||||
context
|
||||
.device
|
||||
.replace_framebuffer_area_raw(&key_grid.display, rect.x, rect.y, rect.w, rect.h, buffer)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn draw_key_at_index(context: &IoWorkerContext, index: u8) {
|
||||
fn draw_key_at_index(context: &IoWorkerContext, state: &State, index: u8) {
|
||||
let position = get_key_position_for_index(&context.device.characteristics().key_grid, index);
|
||||
|
||||
draw_key(context, index, context.state.active_key_page().keys_by_position.get(&position));
|
||||
draw_key(context, index, state.active_key_page().keys_by_position.get(&position));
|
||||
}
|
||||
|
||||
fn draw_key_at_position_if_visible(context: &IoWorkerContext, position: KeyPosition) {
|
||||
fn draw_key_at_position_if_visible(context: &IoWorkerContext, state: &State, position: KeyPosition) {
|
||||
let index = get_key_index_for_position(&context.device.characteristics().key_grid, position);
|
||||
|
||||
if let Some(index) = index {
|
||||
draw_key(context, index, context.state.active_key_page().keys_by_position.get(&position));
|
||||
draw_key(context, index, state.active_key_page().keys_by_position.get(&position));
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_key_at_path_if_visible(context: &IoWorkerContext, path: KeyPath) {
|
||||
if context.state.active_key_page_id == path.page_id {
|
||||
draw_key_at_position_if_visible(context, path.position);
|
||||
fn draw_key_at_path_if_visible(context: &IoWorkerContext, state: &State, path: KeyPath) {
|
||||
if state.active_key_page_id == path.page_id {
|
||||
draw_key_at_position_if_visible(context, state, path.position);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_knob(context: &IoWorkerContext, position: KnobPosition, knob: Option<&Knob>) {
|
||||
if let Some((display, rect)) = context.device.characteristics().get_display_and_rect_for_knob(match position {
|
||||
KnobPosition::LeftTop => LoupedeckKnob::LeftTop,
|
||||
KnobPosition::LeftMiddle => LoupedeckKnob::LeftMiddle,
|
||||
KnobPosition::LeftBottom => LoupedeckKnob::LeftBottom,
|
||||
KnobPosition::RightTop => LoupedeckKnob::RightTop,
|
||||
KnobPosition::RightMiddle => LoupedeckKnob::RightMiddle,
|
||||
KnobPosition::RightBottom => LoupedeckKnob::RightBottom,
|
||||
}) {
|
||||
let buffer = render_knob(&context.graphics, IntSize::from_wh(rect.w as u32, rect.h as u32).unwrap(), knob);
|
||||
context
|
||||
.device
|
||||
.replace_framebuffer_area_raw(display, rect.x, rect.y, rect.w, rect.h, buffer)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_knob_at_position(context: &IoWorkerContext, state: &State, position: KnobPosition) {
|
||||
draw_knob(context, position, Some(&state.active_knob_page().knobs_by_position[position]));
|
||||
}
|
||||
|
||||
fn draw_knob_at_path_if_visible(context: &IoWorkerContext, state: &State, path: KnobPath) {
|
||||
if state.active_knob_page_id == path.page_id {
|
||||
draw_knob_at_position(context, state, path.position);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,78 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use enum_map::EnumMap;
|
||||
use log::error;
|
||||
|
||||
use crate::model;
|
||||
use crate::model::key_page::KeyStyle;
|
||||
use crate::model::knob_page::KnobStyle;
|
||||
use crate::model::position::{KeyPath, KeyPosition, KnobPath, KnobPosition};
|
||||
use crate::runner::state;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
pub active_key_page_id: String,
|
||||
pub active_knob_page_id: String,
|
||||
pub active_touch_ids: HashSet<u8>,
|
||||
pub key_pages_by_id: HashMap<String, KeyPage>,
|
||||
pub knob_pages_by_id: HashMap<String, KnobPage>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn create(config: &model::config::Config) -> Self {
|
||||
let key_pages_by_id: HashMap<_, _> = config
|
||||
.key_pages_by_id
|
||||
.iter()
|
||||
.map(|(id, p)| state::KeyPage {
|
||||
id: id.clone(),
|
||||
keys_by_position: p
|
||||
.keys
|
||||
.iter()
|
||||
.map(|(position, k)| Key {
|
||||
path: KeyPath {
|
||||
page_id: p.id.clone(),
|
||||
position: *position,
|
||||
},
|
||||
base_style: k.base_style.clone(),
|
||||
style: None,
|
||||
})
|
||||
.map(|k| (k.path.position, k))
|
||||
.collect(),
|
||||
})
|
||||
.map(|p| (p.id.clone(), p))
|
||||
.collect();
|
||||
|
||||
let knob_pages_by_id: HashMap<_, _> = config
|
||||
.knob_pages_by_id
|
||||
.iter()
|
||||
.map(|(id, p)| KnobPage {
|
||||
id: id.clone(),
|
||||
knobs_by_position: EnumMap::from_fn(|position| {
|
||||
let knob_config = &p.knobs[position];
|
||||
|
||||
Knob {
|
||||
path: KnobPath {
|
||||
page_id: p.id.clone(),
|
||||
position,
|
||||
},
|
||||
base_style: knob_config.base_style.clone(),
|
||||
style: None,
|
||||
value: 0.0,
|
||||
}
|
||||
}),
|
||||
})
|
||||
.map(|p| (p.id.clone(), p))
|
||||
.collect();
|
||||
|
||||
State {
|
||||
active_key_page_id: config.initial.key_page.clone(),
|
||||
active_knob_page_id: config.initial.knob_page.clone(),
|
||||
active_touch_ids: HashSet::new(),
|
||||
key_pages_by_id,
|
||||
knob_pages_by_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mutate_key_for_command<R>(&mut self, command_name: &'static str, path: &KeyPath, mutator: impl 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),
|
||||
|
@ -28,6 +85,15 @@ impl State {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn mutate_knob_for_command<R>(&mut self, command_name: &'static str, path: &KnobPath, mutator: impl FnOnce(&mut Knob) -> R) -> Option<R> {
|
||||
match self.knob_pages_by_id.get_mut(&path.page_id) {
|
||||
None => error!("Received {} command with invalid path.page_id: {}", command_name, &path.page_id),
|
||||
Some(knob_page) => return Some(mutator(&mut knob_page.knobs_by_position[path.position])),
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn active_key_page(&self) -> &KeyPage {
|
||||
&self.key_pages_by_id[&self.active_key_page_id]
|
||||
}
|
||||
|
@ -60,6 +126,6 @@ pub struct Key {
|
|||
pub struct Knob {
|
||||
pub path: KnobPath,
|
||||
pub base_style: KnobStyle,
|
||||
pub style: KnobStyle,
|
||||
pub style: Option<KnobStyle>,
|
||||
pub value: f32,
|
||||
}
|
||||
|
|
|
@ -6,12 +6,26 @@ use crate::util::Endianness;
|
|||
#[derive(Debug, Ordinalize, EnumSetType)]
|
||||
#[repr(u8)]
|
||||
pub enum LoupedeckKnob {
|
||||
KnobLeftTop = 0x01,
|
||||
KnobLeftMiddle = 0x02,
|
||||
KnobLeftBottom = 0x03,
|
||||
KnobRightTop = 0x04,
|
||||
KnobRightMiddle = 0x05,
|
||||
KnobRightBottom = 0x06,
|
||||
LeftTop = 0x01,
|
||||
LeftMiddle = 0x02,
|
||||
LeftBottom = 0x03,
|
||||
RightTop = 0x04,
|
||||
RightMiddle = 0x05,
|
||||
RightBottom = 0x06,
|
||||
}
|
||||
|
||||
impl LoupedeckKnob {
|
||||
fn is_left(&self) -> bool {
|
||||
matches!(self, LoupedeckKnob::LeftTop | LoupedeckKnob::LeftMiddle | LoupedeckKnob::LeftBottom)
|
||||
}
|
||||
|
||||
fn row(&self) -> u8 {
|
||||
match self {
|
||||
LoupedeckKnob::LeftTop | LoupedeckKnob::RightTop => 0,
|
||||
LoupedeckKnob::LeftMiddle | LoupedeckKnob::RightMiddle => 1,
|
||||
LoupedeckKnob::LeftBottom | LoupedeckKnob::RightBottom => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Ordinalize, EnumSetType)]
|
||||
|
@ -31,7 +45,6 @@ pub enum LoupedeckButton {
|
|||
#[non_exhaustive]
|
||||
pub struct LoupedeckDeviceDisplayConfiguration {
|
||||
pub id: u8,
|
||||
pub name: &'static str,
|
||||
pub dpi: f32,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
|
@ -42,6 +55,16 @@ pub struct LoupedeckDeviceDisplayConfiguration {
|
|||
pub endianness: Endianness,
|
||||
}
|
||||
|
||||
impl Eq for LoupedeckDeviceDisplayConfiguration {
|
||||
fn assert_receiver_is_total_eq(&self) {}
|
||||
}
|
||||
|
||||
impl PartialEq for LoupedeckDeviceDisplayConfiguration {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct LoupedeckDeviceKeyGridCharacteristics {
|
||||
|
@ -50,6 +73,14 @@ pub struct LoupedeckDeviceKeyGridCharacteristics {
|
|||
pub display: LoupedeckDeviceDisplayConfiguration,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoupedeckDisplayRect {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub w: u16,
|
||||
pub h: u16,
|
||||
}
|
||||
|
||||
impl LoupedeckDeviceKeyGridCharacteristics {
|
||||
pub fn key_size(&self) -> (u16, u16) {
|
||||
// Assuming the sizes are integers
|
||||
|
@ -80,7 +111,7 @@ impl LoupedeckDeviceKeyGridCharacteristics {
|
|||
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)> {
|
||||
pub fn get_local_key_rect(&self, key_index: u8) -> Option<LoupedeckDisplayRect> {
|
||||
if key_index >= self.rows * self.columns {
|
||||
return None;
|
||||
}
|
||||
|
@ -89,7 +120,12 @@ impl LoupedeckDeviceKeyGridCharacteristics {
|
|||
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))
|
||||
Some(LoupedeckDisplayRect {
|
||||
x: column * column_width,
|
||||
y: row * row_height,
|
||||
w: column_width,
|
||||
h: row_height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,22 +138,69 @@ pub struct LoupedeckDeviceCharacteristics {
|
|||
pub available_knobs: EnumSet<LoupedeckKnob>,
|
||||
pub available_buttons: EnumSet<LoupedeckButton>,
|
||||
pub key_grid: LoupedeckDeviceKeyGridCharacteristics,
|
||||
pub additional_displays: &'static [LoupedeckDeviceDisplayConfiguration],
|
||||
pub knob_displays: Option<(LoupedeckDeviceDisplayConfiguration, LoupedeckDeviceDisplayConfiguration)>,
|
||||
}
|
||||
|
||||
impl Eq for LoupedeckDeviceCharacteristics {
|
||||
fn assert_receiver_is_total_eq(&self) {}
|
||||
}
|
||||
|
||||
impl PartialEq for LoupedeckDeviceCharacteristics {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl LoupedeckDeviceCharacteristics {
|
||||
pub fn knob_rows(&self) -> u8 {
|
||||
self.available_knobs.iter().map(|k| k.row()).max().map(|v| v + 1).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn get_display_at_coordinates(&self, x: u16, y: u16) -> Option<&LoupedeckDeviceDisplayConfiguration> {
|
||||
let check = |display: &&LoupedeckDeviceDisplayConfiguration| {
|
||||
let check = |display: &LoupedeckDeviceDisplayConfiguration| {
|
||||
x >= display.global_offset_x
|
||||
&& x <= display.global_offset_x + display.width
|
||||
&& y >= display.global_offset_y
|
||||
&& y <= display.global_offset_y + display.height
|
||||
};
|
||||
|
||||
if check(&&self.key_grid.display) {
|
||||
if check(&self.key_grid.display) {
|
||||
Some(&self.key_grid.display)
|
||||
} else if let Some(knob_displays) = &self.knob_displays {
|
||||
if check(&knob_displays.0) {
|
||||
Some(&knob_displays.0)
|
||||
} else if check(&knob_displays.1) {
|
||||
Some(&knob_displays.1)
|
||||
} else {
|
||||
self.additional_displays.iter().find(check)
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_display_and_rect_for_knob(&self, knob: LoupedeckKnob) -> Option<(&LoupedeckDeviceDisplayConfiguration, LoupedeckDisplayRect)> {
|
||||
if !self.available_knobs.contains(knob) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(knob_displays) = &self.knob_displays {
|
||||
let display = if knob.is_left() { &knob_displays.0 } else { &knob_displays.1 };
|
||||
let row = knob.row() as u16;
|
||||
let rows = self.knob_rows() as u16;
|
||||
|
||||
let row_height = display.height / rows;
|
||||
|
||||
let rect = LoupedeckDisplayRect {
|
||||
x: 0,
|
||||
y: row_height * row,
|
||||
w: display.width,
|
||||
h: row_height,
|
||||
};
|
||||
|
||||
Some((display, rect))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,12 +210,12 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
|||
product_id: 0x0004,
|
||||
name: "Loupedeck Live",
|
||||
available_knobs: enum_set!(
|
||||
LoupedeckKnob::KnobLeftTop
|
||||
| LoupedeckKnob::KnobLeftMiddle
|
||||
| LoupedeckKnob::KnobLeftBottom
|
||||
| LoupedeckKnob::KnobRightTop
|
||||
| LoupedeckKnob::KnobRightMiddle
|
||||
| LoupedeckKnob::KnobRightBottom
|
||||
LoupedeckKnob::LeftTop
|
||||
| LoupedeckKnob::LeftMiddle
|
||||
| LoupedeckKnob::LeftBottom
|
||||
| LoupedeckKnob::RightTop
|
||||
| LoupedeckKnob::RightMiddle
|
||||
| LoupedeckKnob::RightBottom
|
||||
),
|
||||
available_buttons: enum_set!(
|
||||
LoupedeckButton::N0
|
||||
|
@ -149,7 +232,6 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
|||
columns: 4,
|
||||
display: LoupedeckDeviceDisplayConfiguration {
|
||||
id: 0x4d,
|
||||
name: "center",
|
||||
dpi: 142.875,
|
||||
width: 360,
|
||||
height: 270,
|
||||
|
@ -160,10 +242,9 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
|||
endianness: Endianness::LittleEndian,
|
||||
},
|
||||
},
|
||||
additional_displays: &[
|
||||
knob_displays: Some((
|
||||
LoupedeckDeviceDisplayConfiguration {
|
||||
id: 0x4d,
|
||||
name: "left",
|
||||
dpi: 142.875,
|
||||
width: 60,
|
||||
height: 270,
|
||||
|
@ -175,7 +256,6 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
|||
},
|
||||
LoupedeckDeviceDisplayConfiguration {
|
||||
id: 0x4d,
|
||||
name: "right",
|
||||
dpi: 142.875,
|
||||
width: 60,
|
||||
height: 270,
|
||||
|
@ -185,7 +265,7 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
|||
global_offset_y: 0,
|
||||
endianness: Endianness::LittleEndian,
|
||||
},
|
||||
],
|
||||
)),
|
||||
};
|
||||
|
||||
pub static CHARACTERISTICS: [&LoupedeckDeviceCharacteristics; 1] = [&LOUPEDECK_LIVE_CHARACTERISTIC];
|
||||
|
|
|
@ -196,7 +196,13 @@ impl LoupedeckDevice {
|
|||
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) {
|
||||
if !(display.id == self.characteristics.key_grid.display.id
|
||||
|| self
|
||||
.characteristics
|
||||
.knob_displays
|
||||
.as_ref()
|
||||
.is_some_and(|d| d.0.id == display.id || d.1.id == display.id))
|
||||
{
|
||||
return Err(ReplaceFramebufferAreaError::UnknownDisplay);
|
||||
}
|
||||
|
||||
|
@ -224,7 +230,13 @@ impl LoupedeckDevice {
|
|||
}
|
||||
|
||||
pub fn refresh_display(&self, display: &LoupedeckDeviceDisplayConfiguration) -> Result<(), RefreshDisplayError> {
|
||||
if display.name != self.characteristics.key_grid.display.name && !self.characteristics.additional_displays.iter().any(|d| display.name == d.name) {
|
||||
if !(display.id == self.characteristics.key_grid.display.id
|
||||
|| self
|
||||
.characteristics
|
||||
.knob_displays
|
||||
.as_ref()
|
||||
.is_some_and(|d| d.0.id == display.id || d.1.id == display.id))
|
||||
{
|
||||
return Err(RefreshDisplayError::UnknownDisplay);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::characteristics::{LoupedeckButton, LoupedeckKnob};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub enum RotationDirection {
|
||||
Clockwise,
|
||||
Counterclockwise,
|
||||
|
|
|
@ -131,23 +131,19 @@ fn parse_message(command: u8, mut message: Bytes) -> ParseMessageResult {
|
|||
match command {
|
||||
0x00 => match message[1] {
|
||||
0x00 => match message[0] {
|
||||
0x01 => LoupedeckEvent::KnobDown {
|
||||
knob: LoupedeckKnob::KnobLeftTop,
|
||||
},
|
||||
0x01 => LoupedeckEvent::KnobDown { knob: LoupedeckKnob::LeftTop },
|
||||
0x02 => LoupedeckEvent::KnobDown {
|
||||
knob: LoupedeckKnob::KnobLeftMiddle,
|
||||
knob: LoupedeckKnob::LeftMiddle,
|
||||
},
|
||||
0x03 => LoupedeckEvent::KnobDown {
|
||||
knob: LoupedeckKnob::KnobLeftBottom,
|
||||
},
|
||||
0x04 => LoupedeckEvent::KnobDown {
|
||||
knob: LoupedeckKnob::KnobRightTop,
|
||||
knob: LoupedeckKnob::LeftBottom,
|
||||
},
|
||||
0x04 => LoupedeckEvent::KnobDown { knob: LoupedeckKnob::RightTop },
|
||||
0x05 => LoupedeckEvent::KnobDown {
|
||||
knob: LoupedeckKnob::KnobRightMiddle,
|
||||
knob: LoupedeckKnob::RightMiddle,
|
||||
},
|
||||
0x06 => LoupedeckEvent::KnobDown {
|
||||
knob: LoupedeckKnob::KnobRightBottom,
|
||||
knob: LoupedeckKnob::RightBottom,
|
||||
},
|
||||
0x07 => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N0 },
|
||||
0x08 => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N1 },
|
||||
|
@ -160,23 +156,19 @@ fn parse_message(command: u8, mut message: Bytes) -> ParseMessageResult {
|
|||
_ => panic!("Illegal button id: {}", message[1]),
|
||||
},
|
||||
_ => match message[0] {
|
||||
0x01 => LoupedeckEvent::KnobUp {
|
||||
knob: LoupedeckKnob::KnobLeftTop,
|
||||
},
|
||||
0x01 => LoupedeckEvent::KnobUp { knob: LoupedeckKnob::LeftTop },
|
||||
0x02 => LoupedeckEvent::KnobUp {
|
||||
knob: LoupedeckKnob::KnobLeftMiddle,
|
||||
knob: LoupedeckKnob::LeftMiddle,
|
||||
},
|
||||
0x03 => LoupedeckEvent::KnobUp {
|
||||
knob: LoupedeckKnob::KnobLeftBottom,
|
||||
},
|
||||
0x04 => LoupedeckEvent::KnobUp {
|
||||
knob: LoupedeckKnob::KnobRightTop,
|
||||
knob: LoupedeckKnob::LeftBottom,
|
||||
},
|
||||
0x04 => LoupedeckEvent::KnobUp { knob: LoupedeckKnob::RightTop },
|
||||
0x05 => LoupedeckEvent::KnobUp {
|
||||
knob: LoupedeckKnob::KnobRightMiddle,
|
||||
knob: LoupedeckKnob::RightMiddle,
|
||||
},
|
||||
0x06 => LoupedeckEvent::KnobUp {
|
||||
knob: LoupedeckKnob::KnobRightBottom,
|
||||
knob: LoupedeckKnob::RightBottom,
|
||||
},
|
||||
0x07 => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N0 },
|
||||
0x08 => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N1 },
|
||||
|
|
Loading…
Add table
Reference in a new issue