From d97bf241681795f4f4ff2e0237b588e2f7db4692 Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Sat, 6 Jan 2024 23:16:23 +0100 Subject: [PATCH] commit --- deckster/examples/full/deckster.toml | 6 +- deckster/examples/full/icons/apps/spotify.svg | 1 + deckster/src/icons/mod.rs | 61 +++++++-- deckster/src/main.rs | 4 +- deckster/src/model/icon_descriptor.rs | 28 +++-- .../src/model/key_modes/home_assistant.rs | 6 +- deckster/src/model/key_modes/media.rs | 6 +- deckster/src/model/key_modes/spotify.rs | 6 +- deckster/src/model/key_page.rs | 4 +- deckster/src/model/knob_modes/audio_volume.rs | 4 +- deckster/src/model/knob_page.rs | 4 +- deckster/src/model/mod.rs | 5 + deckster/src/runner/graphics.rs | 26 ++-- deckster/src/runner/mod.rs | 116 ++++++++++-------- 14 files changed, 180 insertions(+), 97 deletions(-) create mode 100644 deckster/examples/full/icons/apps/spotify.svg diff --git a/deckster/examples/full/deckster.toml b/deckster/examples/full/deckster.toml index ccd0cc6..906ce3c 100644 --- a/deckster/examples/full/deckster.toml +++ b/deckster/examples/full/deckster.toml @@ -22,15 +22,15 @@ key_page = "default" knob_page = "default" [icon_packs.apps] -path = "./apps" +path = "icons/apps" format = "svg" [icon_packs.fad] -path = "./fad" +path = "icons/fad" format = "svg" global_filter = "invert" [icon_packs.ph] -path = "./ph" +path = "icons/ph" format = "svg" global_filter = "invert" \ No newline at end of file diff --git a/deckster/examples/full/icons/apps/spotify.svg b/deckster/examples/full/icons/apps/spotify.svg new file mode 100644 index 0000000..78c3a1d --- /dev/null +++ b/deckster/examples/full/icons/apps/spotify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/deckster/src/icons/mod.rs b/deckster/src/icons/mod.rs index 86ddc3c..60270b3 100644 --- a/deckster/src/icons/mod.rs +++ b/deckster/src/icons/mod.rs @@ -10,21 +10,67 @@ use tiny_skia::{Pixmap, Transform}; use crate::icons::filter::apply_filter; use crate::model::config::{Config, IconFormat, IconPack}; use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource}; +use crate::model::IconMap; mod filter; +#[derive(Debug)] pub struct LoadedIcon { - pixmap: Pixmap, - scale: f32, + pub pixmap: Pixmap, + pub scale: f32, } pub fn get_used_icon_descriptors(config: &Config) -> HashSet { let mut result: HashSet = HashSet::new(); + fn insert_all_from_map(result: &mut HashSet, map: &IconMap) { + map.values().for_each(|v| { + result.insert(v.clone()); + }); + } + 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.insert(d.clone()); + + if let Some(c) = &key.mode.media__next { + insert_all_from_map(&mut result, &c.icon); + } + + if let Some(c) = &key.mode.media__play_pause { + insert_all_from_map(&mut result, &c.icon); + } + + if let Some(c) = &key.mode.media__previous { + insert_all_from_map(&mut result, &c.icon); + } + + if let Some(c) = &key.mode.home_assistant__button { + insert_all_from_map(&mut result, &c.icon); + } + + if let Some(c) = &key.mode.home_assistant__switch { + insert_all_from_map(&mut result, &c.icon); + } + + if let Some(c) = &key.mode.spotify__repeat { + insert_all_from_map(&mut result, &c.icon); + } + + if let Some(c) = &key.mode.spotify__shuffle { + insert_all_from_map(&mut result, &c.icon); + } + } + } + } + + for page in config.knob_pages_by_id.values() { + for knob in page.knobs.values() { + result.insert(knob.icon.clone()); + + if let Some(c) = &knob.mode.audio_volume { + insert_all_from_map(&mut result, &c.icon); } } } @@ -33,9 +79,9 @@ pub fn get_used_icon_descriptors(config: &Config) -> HashSet { } pub fn load_icons( - icon_packs_by_id: HashMap, + config_directory: &Path, + icon_packs_by_id: &HashMap, descriptors: HashSet, - key_size: (u16, u16), dpi: f32, ) -> Result> { let mut unfiltered_pixmap_by_source: HashMap = HashMap::new(); @@ -50,7 +96,7 @@ pub fn load_icons( 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.clone())?), + Entry::Vacant(v) => v.insert(read_image(config_directory, icon_packs_by_id, dpi, &fonts_db, descriptor.source.clone())?), }; let pixmap = apply_filter(original_image, &descriptor.filter)?; @@ -63,6 +109,7 @@ pub fn load_icons( } fn read_image( + config_directory: &Path, icon_packs_by_id: &HashMap, dpi: f32, fonts_db: &resvg::usvg::fontdb::Database, @@ -79,7 +126,7 @@ fn read_image( IconFormat::Svg => "svg", }; - pack.path.join(icon_id + "." + extension) + config_directory.join(&pack.path).join(icon_id + "." + extension) } }; diff --git a/deckster/src/main.rs b/deckster/src/main.rs index daf30da..64b24d7 100644 --- a/deckster/src/main.rs +++ b/deckster/src/main.rs @@ -71,7 +71,7 @@ pub async fn main() -> Result<()> { } .validate()?; - runner::start(config).await? + runner::start(&config_path, config).await? } }; @@ -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/icon_descriptor.rs b/deckster/src/model/icon_descriptor.rs index b45114c..d9ce6ad 100644 --- a/deckster/src/model/icon_descriptor.rs +++ b/deckster/src/model/icon_descriptor.rs @@ -1,16 +1,14 @@ +use std::fmt::{Display, Formatter, Write}; use std::path::PathBuf; use std::str::FromStr; use serde::{Deserialize, Serialize}; -use serde_with::DeserializeFromStr; +use serde_with::{DeserializeFromStr, SerializeDisplay}; use thiserror::Error; use crate::model::image_filter::{ImageFilter, ImageFilterFromStringError}; -#[derive(Debug, Default, PartialEq, Clone, DeserializeFromStr)] -pub struct IconDescriptorString(pub IconDescriptor); - -#[derive(Debug, Default, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Eq, PartialEq, Hash, Clone, SerializeDisplay, DeserializeFromStr)] pub struct IconDescriptor { pub source: IconDescriptorSource, pub filter: ImageFilter, @@ -28,7 +26,7 @@ pub enum IconDescriptorFromStrError { MissingImageFilterClosingBracket, } -impl FromStr for IconDescriptorString { +impl FromStr for IconDescriptor { type Err = IconDescriptorFromStrError; fn from_str(s: &str) -> Result { @@ -57,7 +55,13 @@ impl FromStr for IconDescriptorString { ImageFilter::from_str(&raw_filter).map_err(IconDescriptorFromStrError::InvalidImageFilter)? }; - Ok(IconDescriptorString(IconDescriptor { source, filter })) + Ok(IconDescriptor { source, filter }) + } +} + +impl Display for IconDescriptor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}[{}]", self.source, self.filter)) } } @@ -71,3 +75,13 @@ pub enum IconDescriptorSource { }, Path(PathBuf), } + +impl Display for IconDescriptorSource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + IconDescriptorSource::None => f.write_str(""), + IconDescriptorSource::IconPack { pack_id, icon_id } => f.write_fmt(format_args!("@{}/{}", pack_id, icon_id)), + IconDescriptorSource::Path(path) => f.write_str(&path.to_string_lossy()), + } + } +} diff --git a/deckster/src/model/key_modes/home_assistant.rs b/deckster/src/model/key_modes/home_assistant.rs index 6ff1ae8..6d71a2b 100644 --- a/deckster/src/model/key_modes/home_assistant.rs +++ b/deckster/src/model/key_modes/home_assistant.rs @@ -2,20 +2,20 @@ use std::collections::HashMap; use serde::Deserialize; -use crate::model::icon_descriptor::IconDescriptorString; +use crate::model::icon_descriptor::IconDescriptor; #[derive(Debug, Deserialize)] pub struct SwitchConfig { pub name: String, #[serde(default)] - pub icon: HashMap, + pub icon: HashMap, } #[derive(Debug, Deserialize)] pub struct ButtonConfig { pub name: String, #[serde(default)] - pub icon: HashMap, + pub icon: HashMap, } #[derive(Debug, Eq, PartialEq, Hash, Deserialize)] diff --git a/deckster/src/model/key_modes/media.rs b/deckster/src/model/key_modes/media.rs index 05ad60f..fe9ac1b 100644 --- a/deckster/src/model/key_modes/media.rs +++ b/deckster/src/model/key_modes/media.rs @@ -2,12 +2,12 @@ use std::collections::HashMap; use serde::Deserialize; -use crate::model::icon_descriptor::IconDescriptorString; +use crate::model::icon_descriptor::IconDescriptor; #[derive(Debug, Deserialize)] pub struct PlayPauseConfig { #[serde(default)] - pub icon: HashMap, + pub icon: HashMap, #[serde(default)] pub action: PlayPauseAction, } @@ -24,7 +24,7 @@ pub enum PlayPauseAction { #[derive(Debug, Deserialize)] pub struct PreviousAndNextConfig { #[serde(default)] - pub icon: HashMap, + pub icon: HashMap, } #[derive(Debug, Eq, PartialEq, Hash, Deserialize)] diff --git a/deckster/src/model/key_modes/spotify.rs b/deckster/src/model/key_modes/spotify.rs index a6e30b4..612a424 100644 --- a/deckster/src/model/key_modes/spotify.rs +++ b/deckster/src/model/key_modes/spotify.rs @@ -2,18 +2,18 @@ use std::collections::HashMap; use serde::Deserialize; -use crate::model::icon_descriptor::IconDescriptorString; +use crate::model::icon_descriptor::IconDescriptor; #[derive(Debug, Deserialize)] pub struct ShuffleConfig { #[serde(default)] - pub icon: HashMap, + pub icon: HashMap, } #[derive(Debug, Deserialize)] pub struct RepeatConfig { #[serde(default)] - pub icon: HashMap, + pub icon: HashMap, } #[derive(Debug, Eq, PartialEq, Hash, Deserialize)] diff --git a/deckster/src/model/key_page.rs b/deckster/src/model/key_page.rs index d5d0379..f3a8131 100644 --- a/deckster/src/model/key_page.rs +++ b/deckster/src/model/key_page.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use serde::Deserialize; use crate::model::geometry::UIntVec2; -use crate::model::icon_descriptor::IconDescriptorString; +use crate::model::icon_descriptor::IconDescriptor; use crate::model::{key_modes, KeyPosition, KnobPosition}; #[derive(Debug, Deserialize)] @@ -46,7 +46,7 @@ pub enum ScrollingConfigAxis { #[derive(Debug, Deserialize)] pub struct KeyConfig { pub label: Option, - pub icon: Option, + pub icon: Option, #[serde(default)] pub mode: KeyModes, } diff --git a/deckster/src/model/knob_modes/audio_volume.rs b/deckster/src/model/knob_modes/audio_volume.rs index 2e905ee..124937d 100644 --- a/deckster/src/model/knob_modes/audio_volume.rs +++ b/deckster/src/model/knob_modes/audio_volume.rs @@ -4,8 +4,8 @@ use std::num::NonZeroU8; use regex::Regex; use serde::Deserialize; -use crate::model::icon_descriptor::IconDescriptorString; use crate::model::rgb::RGB8WithOptionalA; +use crate::model::IconMap; #[derive(Debug, Deserialize)] pub struct Config { @@ -22,7 +22,7 @@ pub struct Config { #[serde(default)] pub label: HashMap, #[serde(default)] - pub icon: HashMap, + pub icon: IconMap, pub circle_indicator: Option, pub bar_indicator: Option, } diff --git a/deckster/src/model/knob_page.rs b/deckster/src/model/knob_page.rs index 5d7a6f1..749794f 100644 --- a/deckster/src/model/knob_page.rs +++ b/deckster/src/model/knob_page.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use enum_map::EnumMap; use serde::Deserialize; -use crate::model::icon_descriptor::IconDescriptorString; +use crate::model::icon_descriptor::IconDescriptor; use crate::model::rgb::RGB8WithOptionalA; use crate::model::{knob_modes, KnobPosition}; @@ -24,7 +24,7 @@ pub struct Knob { #[serde(default)] pub label: String, #[serde(default)] - pub icon: IconDescriptorString, + pub icon: IconDescriptor, #[serde(default)] pub indicator: KnobIndicators, #[serde(default)] diff --git a/deckster/src/model/mod.rs b/deckster/src/model/mod.rs index 5320226..6158191 100644 --- a/deckster/src/model/mod.rs +++ b/deckster/src/model/mod.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::str::FromStr; @@ -8,6 +9,8 @@ use thiserror::Error; use loupedeck_serial::characteristics::LoupedeckButton; +use crate::model::icon_descriptor::IconDescriptor; + pub mod config; pub mod geometry; pub mod icon_descriptor; @@ -125,3 +128,5 @@ impl Display for ButtonPosition { }) } } + +pub type IconMap = HashMap; diff --git a/deckster/src/runner/graphics.rs b/deckster/src/runner/graphics.rs index f48ccb7..4634b61 100644 --- a/deckster/src/runner/graphics.rs +++ b/deckster/src/runner/graphics.rs @@ -1,22 +1,30 @@ +use std::collections::HashMap; + use bytes::{BufMut, Bytes, BytesMut}; -use tiny_skia::{Color, Pixmap, PremultipliedColorU8}; +use tiny_skia::{Color, IntSize, Pixmap, PremultipliedColorU8}; use loupedeck_serial::util::Endianness; +use crate::icons::LoadedIcon; +use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource}; use crate::runner::state::Key; -pub fn render_key(width: u16, height: u16, buffer_endianness: Endianness, state: Option<&Key>) -> Bytes { - let mut canvas = Pixmap::new(width as u32, height as u32).unwrap(); +pub fn render_key(key_size: (u16, u16), buffer_endianness: Endianness, icons: &HashMap, state: Option<&Key>) -> Bytes { + let mut canvas = Pixmap::new(key_size.0 as u32, key_size.1 as u32).unwrap(); if let Some(state) = state { - canvas.fill(if state.label.is_empty() { - Color::WHITE - } else { - Color::from_rgba8(0, 255, 0, 255) - }); + if state.icon.source != IconDescriptorSource::None { + let icon = &icons[&state.icon]; + let scaled_size = IntSize::from_wh(icon.pixmap.width(), icon.pixmap.height()) + .unwrap() + .scale_by(icon.scale) + .unwrap(); + + //canvas.draw_pixmap(0, 0, icon.pixmap.as_ref(), &PixmapPaint::default(), Transform::from_scale(icon.scale, icon.scale), None); + } } else { - canvas.fill(Color::BLACK); } + canvas.fill(Color::WHITE); convert_pixels_to_rgb565(canvas.pixels(), buffer_endianness).freeze() } diff --git a/deckster/src/runner/mod.rs b/deckster/src/runner/mod.rs index 13c07e3..2831f90 100644 --- a/deckster/src/runner/mod.rs +++ b/deckster/src/runner/mod.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::path::Path; use std::sync::Arc; use std::thread; @@ -15,6 +16,7 @@ use loupedeck_serial::commands::VibrationPattern; use loupedeck_serial::device::LoupedeckDevice; use loupedeck_serial::events::LoupedeckEvent; +use crate::icons::{get_used_icon_descriptors, load_icons, LoadedIcon}; use crate::model; use crate::model::icon_descriptor::IconDescriptor; use crate::model::{ButtonPosition, KeyPath, KeyPosition, KnobPath}; @@ -24,7 +26,7 @@ use crate::runner::state::{Key, State, StateChangeCommand}; mod graphics; mod state; -pub async fn start(config: model::config::Config) -> Result<()> { +pub async fn start(config_directory: &Path, config: model::config::Config) -> Result<()> { let config = Arc::new(config); let device = LoupedeckDevice::discover()? .first() @@ -32,6 +34,11 @@ pub async fn start(config: model::config::Config) -> Result<()> { .connect() .wrap_err("Connecting to the device failed.")?; + let key_grid = &device.characteristics().key_grid; + + let used_icon_descriptors = get_used_icon_descriptors(&config); + let icons = load_icons(config_directory, &config.icon_packs, used_icon_descriptors, key_grid.display.dpi)?; + device.set_brightness(0.0); device.vibrate(VibrationPattern::RiseFall); @@ -49,7 +56,7 @@ pub async fn start(config: model::config::Config) -> Result<()> { let cloned_commands_sender = commands_sender.clone(); let io_worker_thread = thread::Builder::new() .name("deckster IO worker".to_owned()) - .spawn(move || do_io_work(cloned_config, device, events_receiver, cloned_commands_sender, commands_receiver)) + .spawn(move || do_io_work(cloned_config, icons, device, events_receiver, cloned_commands_sender, commands_receiver)) .wrap_err("Could not spawn the worker thread")?; io_worker_thread.join().unwrap(); @@ -72,7 +79,7 @@ fn create_state(config: &model::config::Config) -> State { position: *position, }, label: k.label.clone().unwrap_or_default(), - icon: IconDescriptor::default(), + icon: k.icon.clone().unwrap_or_default(), }) .map(|k| (k.path.position, k)) .collect(), @@ -94,7 +101,7 @@ fn create_state(config: &model::config::Config) -> State { position, }, label: knob_config.label.clone(), - icon: knob_config.icon.0.clone(), + icon: knob_config.icon.clone(), value: 0.0, } }), @@ -115,14 +122,23 @@ enum IoWork { Command(StateChangeCommand), } +struct IoWorkerContext { + config: Arc, + icons: HashMap, + device: LoupedeckDevice, + state: State, +} + fn do_io_work( config: Arc, + icons: HashMap, device: LoupedeckDevice, events_receiver: Receiver, commands_sender: Sender, commands_receiver: Receiver, ) { - let mut state = create_state(&config); + let state = create_state(&config); + let mut context = IoWorkerContext { config, icons, device, state }; loop { let a = flume::Selector::new() @@ -132,40 +148,34 @@ fn do_io_work( match a { IoWork::Event(event) => { - if !handle_event(&config, &state, &device, &commands_sender, event) { + if !handle_event(&context, &commands_sender, event) { break; } } - IoWork::Command(command) => handle_command(&config, &mut state, &device, command), + IoWork::Command(command) => handle_command(&mut context, command), } } } -fn handle_event( - config: &model::config::Config, - state: &State, - device: &LoupedeckDevice, - commands_sender: &Sender, - event: LoupedeckEvent, -) -> bool { +fn handle_event(context: &IoWorkerContext, commands_sender: &Sender, event: LoupedeckEvent) -> bool { trace!("Handling event: {:?}", &event); match event { LoupedeckEvent::Disconnected => return false, LoupedeckEvent::ButtonDown { button } => { let position = ButtonPosition::of(&button); - let button_config = &config.buttons[position]; + let button_config = &context.config.buttons[position]; commands_sender .send(StateChangeCommand::SetActivePages { - key_page_id: button_config.key_page.as_ref().unwrap_or(&state.active_key_page_id).clone(), - knob_page_id: button_config.knob_page.as_ref().unwrap_or(&state.active_knob_page_id).clone(), + 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(), }) .unwrap() } LoupedeckEvent::Touch { x, y, is_end, .. } => { if is_end { - let characteristics = device.characteristics(); + let characteristics = context.device.characteristics(); let display = characteristics.get_display_at_coordinates(x, y); if let Some(display) = display { @@ -178,11 +188,11 @@ fn handle_event( }; let path = KeyPath { - page_id: state.active_key_page_id.clone(), + page_id: context.state.active_key_page_id.clone(), position, }; - let value = if state.get_key(&path).label.is_empty() { + let value = if context.state.get_key(&path).label.is_empty() { "a".to_owned() } else { String::new() @@ -200,29 +210,29 @@ fn handle_event( true } -fn handle_command(config: &model::config::Config, state: &mut State, device: &LoupedeckDevice, command: StateChangeCommand) { +fn handle_command(context: &mut IoWorkerContext, command: StateChangeCommand) { debug!("Handling command: {:?}", &command); match command { StateChangeCommand::SetActivePages { key_page_id, knob_page_id } => { - state.active_key_page_id = key_page_id; - state.active_knob_page_id = knob_page_id; + context.state.active_key_page_id = key_page_id; + context.state.active_knob_page_id = knob_page_id; for button in LoupedeckButton::VARIANTS { let position = ButtonPosition::of(button); - device.set_button_color(*button, get_correct_button_color(config, state, position)).unwrap(); + context.device.set_button_color(*button, get_correct_button_color(context, position)).unwrap(); } - let key_grid = &device.characteristics().key_grid; + let key_grid = &context.device.characteristics().key_grid; for index in 0..(key_grid.rows * key_grid.columns) { - draw_key_at_index(device, state, index); + draw_key_at_index(context, index); } - device.refresh_display(&key_grid.display).unwrap(); + context.device.refresh_display(&key_grid.display).unwrap(); } StateChangeCommand::SetKeyLabel { path, value } => { - state.mutate_key_for_command( + context.state.mutate_key_for_command( "SetKeyLabel", &path, Box::new(|k| { @@ -230,11 +240,11 @@ fn handle_command(config: &model::config::Config, state: &mut State, device: &Lo }), ); - draw_key_at_path_if_visible(device, state, path); - device.refresh_display(&device.characteristics().key_grid.display).unwrap(); + draw_key_at_path_if_visible(context, path); + context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap(); } StateChangeCommand::SetKeyIcon { path, value } => { - state.mutate_key_for_command( + context.state.mutate_key_for_command( "SetKeyIcon", &path, Box::new(|k| { @@ -242,8 +252,8 @@ fn handle_command(config: &model::config::Config, state: &mut State, device: &Lo }), ); - draw_key_at_path_if_visible(device, state, path); - device.refresh_display(&device.characteristics().key_grid.display).unwrap(); + draw_key_at_path_if_visible(context, path); + context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap(); } } } @@ -251,14 +261,14 @@ fn handle_command(config: &model::config::Config, state: &mut State, device: &Lo // active -> config.active_button_color // no actions defined -> #000000 // inactive -> config.inactive_button_color -fn get_correct_button_color(config: &model::config::Config, state: &mut State, button_position: ButtonPosition) -> RGB8 { - let button_config = &config.buttons[button_position]; +fn get_correct_button_color(context: &IoWorkerContext, button_position: ButtonPosition) -> RGB8 { + let button_config = &context.config.buttons[button_position]; if let Some(key_page) = &button_config.key_page { - if key_page == &state.active_key_page_id { + if key_page == &context.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.into(); + if knob_page == &context.state.active_knob_page_id { + return context.config.active_button_color.into(); } } } @@ -266,7 +276,7 @@ fn get_correct_button_color(config: &model::config::Config, state: &mut State, b return RGB8::new(0, 0, 0); } - config.inactive_button_color.into() + context.config.inactive_button_color.into() } fn get_key_index_for_position(key_grid: &LoupedeckDeviceKeyGridCharacteristics, position: KeyPosition) -> Option { @@ -289,32 +299,30 @@ fn get_key_position_for_index(key_grid: &LoupedeckDeviceKeyGridCharacteristics, } } -fn draw_key(device: &LoupedeckDevice, key_grid: &LoupedeckDeviceKeyGridCharacteristics, index: u8, key: Option<&Key>) { +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(); - device - .replace_framebuffer_area_raw(&key_grid.display, x, y, w, h, render_key(w, h, key_grid.display.endianness, key)) - .unwrap(); + let p = render_key((w, h), key_grid.display.endianness, &context.icons, key); + context.device.replace_framebuffer_area_raw(&key_grid.display, x, y, w, h, p).unwrap(); } -fn draw_key_at_index(device: &LoupedeckDevice, state: &State, index: u8) { - let key_grid = &device.characteristics().key_grid; - let position = get_key_position_for_index(key_grid, index); +fn draw_key_at_index(context: &IoWorkerContext, index: u8) { + let position = get_key_position_for_index(&context.device.characteristics().key_grid, index); - draw_key(device, key_grid, index, state.active_key_page().keys_by_position.get(&position)); + draw_key(context, index, context.state.active_key_page().keys_by_position.get(&position)); } -fn draw_key_at_position_if_visible(device: &LoupedeckDevice, state: &State, position: KeyPosition) { - let key_grid = &device.characteristics().key_grid; - let index = get_key_index_for_position(key_grid, position); +fn draw_key_at_position_if_visible(context: &IoWorkerContext, position: KeyPosition) { + let index = get_key_index_for_position(&context.device.characteristics().key_grid, position); if let Some(index) = index { - draw_key(device, key_grid, index, state.active_key_page().keys_by_position.get(&position)); + draw_key(context, index, context.state.active_key_page().keys_by_position.get(&position)); } } -fn draw_key_at_path_if_visible(device: &LoupedeckDevice, state: &State, path: KeyPath) { - if state.active_key_page_id == path.page_id { - draw_key_at_position_if_visible(device, state, path.position); +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); } }