This commit is contained in:
Moritz Ruth 2024-01-06 23:16:23 +01:00
parent 78291b75b7
commit d97bf24168
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
14 changed files with 180 additions and 97 deletions

View file

@ -22,15 +22,15 @@ key_page = "default"
knob_page = "default" knob_page = "default"
[icon_packs.apps] [icon_packs.apps]
path = "./apps" path = "icons/apps"
format = "svg" format = "svg"
[icon_packs.fad] [icon_packs.fad]
path = "./fad" path = "icons/fad"
format = "svg" format = "svg"
global_filter = "invert" global_filter = "invert"
[icon_packs.ph] [icon_packs.ph]
path = "./ph" path = "icons/ph"
format = "svg" format = "svg"
global_filter = "invert" global_filter = "invert"

View file

@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#1DB954" d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg>

After

Width:  |  Height:  |  Size: 679 B

View file

@ -10,21 +10,67 @@ use tiny_skia::{Pixmap, Transform};
use crate::icons::filter::apply_filter; use crate::icons::filter::apply_filter;
use crate::model::config::{Config, IconFormat, IconPack}; use crate::model::config::{Config, IconFormat, IconPack};
use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource}; use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource};
use crate::model::IconMap;
mod filter; mod filter;
#[derive(Debug)]
pub struct LoadedIcon { pub struct LoadedIcon {
pixmap: Pixmap, pub pixmap: Pixmap,
scale: f32, pub scale: f32,
} }
pub fn get_used_icon_descriptors(config: &Config) -> HashSet<IconDescriptor> { pub fn get_used_icon_descriptors(config: &Config) -> HashSet<IconDescriptor> {
let mut result: HashSet<IconDescriptor> = HashSet::new(); let mut result: HashSet<IconDescriptor> = HashSet::new();
fn insert_all_from_map<T>(result: &mut HashSet<IconDescriptor>, map: &IconMap<T>) {
map.values().for_each(|v| {
result.insert(v.clone());
});
}
for page in config.key_pages_by_id.values() { for page in config.key_pages_by_id.values() {
for key in page.keys.values() { for key in page.keys.values() {
if let Some(d) = &key.icon { 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<IconDescriptor> {
} }
pub fn load_icons( pub fn load_icons(
icon_packs_by_id: HashMap<String, IconPack>, config_directory: &Path,
icon_packs_by_id: &HashMap<String, IconPack>,
descriptors: HashSet<IconDescriptor>, descriptors: HashSet<IconDescriptor>,
key_size: (u16, u16),
dpi: f32, dpi: f32,
) -> Result<HashMap<IconDescriptor, LoadedIcon>> { ) -> Result<HashMap<IconDescriptor, LoadedIcon>> {
let mut unfiltered_pixmap_by_source: HashMap<IconDescriptorSource, Pixmap> = HashMap::new(); let mut unfiltered_pixmap_by_source: HashMap<IconDescriptorSource, Pixmap> = HashMap::new();
@ -50,7 +96,7 @@ pub fn load_icons(
let original_image = match unfiltered_pixmap_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::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)?; let pixmap = apply_filter(original_image, &descriptor.filter)?;
@ -63,6 +109,7 @@ pub fn load_icons(
} }
fn read_image( fn read_image(
config_directory: &Path,
icon_packs_by_id: &HashMap<String, IconPack>, icon_packs_by_id: &HashMap<String, IconPack>,
dpi: f32, dpi: f32,
fonts_db: &resvg::usvg::fontdb::Database, fonts_db: &resvg::usvg::fontdb::Database,
@ -79,7 +126,7 @@ fn read_image(
IconFormat::Svg => "svg", IconFormat::Svg => "svg",
}; };
pack.path.join(icon_id + "." + extension) config_directory.join(&pack.path).join(icon_id + "." + extension)
} }
}; };

View file

@ -71,7 +71,7 @@ pub async fn main() -> Result<()> {
} }
.validate()?; .validate()?;
runner::start(config).await? runner::start(&config_path, config).await?
} }
}; };
@ -79,7 +79,7 @@ pub async fn main() -> Result<()> {
} }
fn read_and_deserialize<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> { fn read_and_deserialize<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
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::<T>(&content).wrap_err_with(|| format!("Parsing of {} failed.", path.to_string_lossy())) toml::from_str::<T>(&content).wrap_err_with(|| format!("Parsing of {} failed.", path.to_string_lossy()))
} }

View file

@ -1,16 +1,14 @@
use std::fmt::{Display, Formatter, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::DeserializeFromStr; use serde_with::{DeserializeFromStr, SerializeDisplay};
use thiserror::Error; use thiserror::Error;
use crate::model::image_filter::{ImageFilter, ImageFilterFromStringError}; use crate::model::image_filter::{ImageFilter, ImageFilterFromStringError};
#[derive(Debug, Default, PartialEq, Clone, DeserializeFromStr)] #[derive(Debug, Default, Eq, PartialEq, Hash, Clone, SerializeDisplay, DeserializeFromStr)]
pub struct IconDescriptorString(pub IconDescriptor);
#[derive(Debug, Default, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
pub struct IconDescriptor { pub struct IconDescriptor {
pub source: IconDescriptorSource, pub source: IconDescriptorSource,
pub filter: ImageFilter, pub filter: ImageFilter,
@ -28,7 +26,7 @@ pub enum IconDescriptorFromStrError {
MissingImageFilterClosingBracket, MissingImageFilterClosingBracket,
} }
impl FromStr for IconDescriptorString { impl FromStr for IconDescriptor {
type Err = IconDescriptorFromStrError; type Err = IconDescriptorFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -57,7 +55,13 @@ impl FromStr for IconDescriptorString {
ImageFilter::from_str(&raw_filter).map_err(IconDescriptorFromStrError::InvalidImageFilter)? 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), Path(PathBuf),
} }
impl Display for IconDescriptorSource {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
IconDescriptorSource::None => f.write_str("<none>"),
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()),
}
}
}

View file

@ -2,20 +2,20 @@ use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use crate::model::icon_descriptor::IconDescriptorString; use crate::model::icon_descriptor::IconDescriptor;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct SwitchConfig { pub struct SwitchConfig {
pub name: String, pub name: String,
#[serde(default)] #[serde(default)]
pub icon: HashMap<SwitchState, IconDescriptorString>, pub icon: HashMap<SwitchState, IconDescriptor>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct ButtonConfig { pub struct ButtonConfig {
pub name: String, pub name: String,
#[serde(default)] #[serde(default)]
pub icon: HashMap<ButtonState, IconDescriptorString>, pub icon: HashMap<ButtonState, IconDescriptor>,
} }
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)] #[derive(Debug, Eq, PartialEq, Hash, Deserialize)]

View file

@ -2,12 +2,12 @@ use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use crate::model::icon_descriptor::IconDescriptorString; use crate::model::icon_descriptor::IconDescriptor;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct PlayPauseConfig { pub struct PlayPauseConfig {
#[serde(default)] #[serde(default)]
pub icon: HashMap<PlayPauseState, IconDescriptorString>, pub icon: HashMap<PlayPauseState, IconDescriptor>,
#[serde(default)] #[serde(default)]
pub action: PlayPauseAction, pub action: PlayPauseAction,
} }
@ -24,7 +24,7 @@ pub enum PlayPauseAction {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct PreviousAndNextConfig { pub struct PreviousAndNextConfig {
#[serde(default)] #[serde(default)]
pub icon: HashMap<PreviousAndNextState, IconDescriptorString>, pub icon: HashMap<PreviousAndNextState, IconDescriptor>,
} }
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)] #[derive(Debug, Eq, PartialEq, Hash, Deserialize)]

View file

@ -2,18 +2,18 @@ use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use crate::model::icon_descriptor::IconDescriptorString; use crate::model::icon_descriptor::IconDescriptor;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct ShuffleConfig { pub struct ShuffleConfig {
#[serde(default)] #[serde(default)]
pub icon: HashMap<ShuffleState, IconDescriptorString>, pub icon: HashMap<ShuffleState, IconDescriptor>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct RepeatConfig { pub struct RepeatConfig {
#[serde(default)] #[serde(default)]
pub icon: HashMap<RepeatState, IconDescriptorString>, pub icon: HashMap<RepeatState, IconDescriptor>,
} }
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)] #[derive(Debug, Eq, PartialEq, Hash, Deserialize)]

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use crate::model::geometry::UIntVec2; use crate::model::geometry::UIntVec2;
use crate::model::icon_descriptor::IconDescriptorString; use crate::model::icon_descriptor::IconDescriptor;
use crate::model::{key_modes, KeyPosition, KnobPosition}; use crate::model::{key_modes, KeyPosition, KnobPosition};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -46,7 +46,7 @@ pub enum ScrollingConfigAxis {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct KeyConfig { pub struct KeyConfig {
pub label: Option<String>, pub label: Option<String>,
pub icon: Option<IconDescriptorString>, pub icon: Option<IconDescriptor>,
#[serde(default)] #[serde(default)]
pub mode: KeyModes, pub mode: KeyModes,
} }

View file

@ -4,8 +4,8 @@ use std::num::NonZeroU8;
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use crate::model::icon_descriptor::IconDescriptorString;
use crate::model::rgb::RGB8WithOptionalA; use crate::model::rgb::RGB8WithOptionalA;
use crate::model::IconMap;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Config { pub struct Config {
@ -22,7 +22,7 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub label: HashMap<State, String>, pub label: HashMap<State, String>,
#[serde(default)] #[serde(default)]
pub icon: HashMap<State, IconDescriptorString>, pub icon: IconMap<State>,
pub circle_indicator: Option<CircleIndicatorConfig>, pub circle_indicator: Option<CircleIndicatorConfig>,
pub bar_indicator: Option<BarIndicatorConfig>, pub bar_indicator: Option<BarIndicatorConfig>,
} }

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
use enum_map::EnumMap; use enum_map::EnumMap;
use serde::Deserialize; use serde::Deserialize;
use crate::model::icon_descriptor::IconDescriptorString; use crate::model::icon_descriptor::IconDescriptor;
use crate::model::rgb::RGB8WithOptionalA; use crate::model::rgb::RGB8WithOptionalA;
use crate::model::{knob_modes, KnobPosition}; use crate::model::{knob_modes, KnobPosition};
@ -24,7 +24,7 @@ pub struct Knob {
#[serde(default)] #[serde(default)]
pub label: String, pub label: String,
#[serde(default)] #[serde(default)]
pub icon: IconDescriptorString, pub icon: IconDescriptor,
#[serde(default)] #[serde(default)]
pub indicator: KnobIndicators, pub indicator: KnobIndicators,
#[serde(default)] #[serde(default)]

View file

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
@ -8,6 +9,8 @@ use thiserror::Error;
use loupedeck_serial::characteristics::LoupedeckButton; use loupedeck_serial::characteristics::LoupedeckButton;
use crate::model::icon_descriptor::IconDescriptor;
pub mod config; pub mod config;
pub mod geometry; pub mod geometry;
pub mod icon_descriptor; pub mod icon_descriptor;
@ -125,3 +128,5 @@ impl Display for ButtonPosition {
}) })
} }
} }
pub type IconMap<State> = HashMap<State, IconDescriptor>;

View file

@ -1,22 +1,30 @@
use std::collections::HashMap;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use tiny_skia::{Color, Pixmap, PremultipliedColorU8}; use tiny_skia::{Color, IntSize, Pixmap, PremultipliedColorU8};
use loupedeck_serial::util::Endianness; use loupedeck_serial::util::Endianness;
use crate::icons::LoadedIcon;
use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource};
use crate::runner::state::Key; use crate::runner::state::Key;
pub fn render_key(width: u16, height: u16, buffer_endianness: Endianness, state: Option<&Key>) -> Bytes { pub fn render_key(key_size: (u16, u16), buffer_endianness: Endianness, icons: &HashMap<IconDescriptor, LoadedIcon>, state: Option<&Key>) -> Bytes {
let mut canvas = Pixmap::new(width as u32, height as u32).unwrap(); let mut canvas = Pixmap::new(key_size.0 as u32, key_size.1 as u32).unwrap();
if let Some(state) = state { if let Some(state) = state {
canvas.fill(if state.label.is_empty() { if state.icon.source != IconDescriptorSource::None {
Color::WHITE let icon = &icons[&state.icon];
} else { let scaled_size = IntSize::from_wh(icon.pixmap.width(), icon.pixmap.height())
Color::from_rgba8(0, 255, 0, 255) .unwrap()
}); .scale_by(icon.scale)
} else { .unwrap();
canvas.fill(Color::BLACK);
//canvas.draw_pixmap(0, 0, icon.pixmap.as_ref(), &PixmapPaint::default(), Transform::from_scale(icon.scale, icon.scale), None);
} }
} else {
}
canvas.fill(Color::WHITE);
convert_pixels_to_rgb565(canvas.pixels(), buffer_endianness).freeze() convert_pixels_to_rgb565(canvas.pixels(), buffer_endianness).freeze()
} }

View file

@ -1,4 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
@ -15,6 +16,7 @@ use loupedeck_serial::commands::VibrationPattern;
use loupedeck_serial::device::LoupedeckDevice; use loupedeck_serial::device::LoupedeckDevice;
use loupedeck_serial::events::LoupedeckEvent; use loupedeck_serial::events::LoupedeckEvent;
use crate::icons::{get_used_icon_descriptors, load_icons, LoadedIcon};
use crate::model; use crate::model;
use crate::model::icon_descriptor::IconDescriptor; use crate::model::icon_descriptor::IconDescriptor;
use crate::model::{ButtonPosition, KeyPath, KeyPosition, KnobPath}; use crate::model::{ButtonPosition, KeyPath, KeyPosition, KnobPath};
@ -24,7 +26,7 @@ use crate::runner::state::{Key, State, StateChangeCommand};
mod graphics; mod graphics;
mod state; 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 config = Arc::new(config);
let device = LoupedeckDevice::discover()? let device = LoupedeckDevice::discover()?
.first() .first()
@ -32,6 +34,11 @@ pub async fn start(config: model::config::Config) -> Result<()> {
.connect() .connect()
.wrap_err("Connecting to the device failed.")?; .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.set_brightness(0.0);
device.vibrate(VibrationPattern::RiseFall); 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 cloned_commands_sender = commands_sender.clone();
let io_worker_thread = thread::Builder::new() let io_worker_thread = thread::Builder::new()
.name("deckster IO worker".to_owned()) .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")?; .wrap_err("Could not spawn the worker thread")?;
io_worker_thread.join().unwrap(); io_worker_thread.join().unwrap();
@ -72,7 +79,7 @@ fn create_state(config: &model::config::Config) -> State {
position: *position, position: *position,
}, },
label: k.label.clone().unwrap_or_default(), label: k.label.clone().unwrap_or_default(),
icon: IconDescriptor::default(), icon: k.icon.clone().unwrap_or_default(),
}) })
.map(|k| (k.path.position, k)) .map(|k| (k.path.position, k))
.collect(), .collect(),
@ -94,7 +101,7 @@ fn create_state(config: &model::config::Config) -> State {
position, position,
}, },
label: knob_config.label.clone(), label: knob_config.label.clone(),
icon: knob_config.icon.0.clone(), icon: knob_config.icon.clone(),
value: 0.0, value: 0.0,
} }
}), }),
@ -115,14 +122,23 @@ enum IoWork {
Command(StateChangeCommand), Command(StateChangeCommand),
} }
struct IoWorkerContext {
config: Arc<model::config::Config>,
icons: HashMap<IconDescriptor, LoadedIcon>,
device: LoupedeckDevice,
state: State,
}
fn do_io_work( fn do_io_work(
config: Arc<model::config::Config>, config: Arc<model::config::Config>,
icons: HashMap<IconDescriptor, LoadedIcon>,
device: LoupedeckDevice, device: LoupedeckDevice,
events_receiver: Receiver<LoupedeckEvent>, events_receiver: Receiver<LoupedeckEvent>,
commands_sender: Sender<StateChangeCommand>, commands_sender: Sender<StateChangeCommand>,
commands_receiver: Receiver<StateChangeCommand>, commands_receiver: Receiver<StateChangeCommand>,
) { ) {
let mut state = create_state(&config); let state = create_state(&config);
let mut context = IoWorkerContext { config, icons, device, state };
loop { loop {
let a = flume::Selector::new() let a = flume::Selector::new()
@ -132,40 +148,34 @@ fn do_io_work(
match a { match a {
IoWork::Event(event) => { IoWork::Event(event) => {
if !handle_event(&config, &state, &device, &commands_sender, event) { if !handle_event(&context, &commands_sender, event) {
break; break;
} }
} }
IoWork::Command(command) => handle_command(&config, &mut state, &device, command), IoWork::Command(command) => handle_command(&mut context, command),
} }
} }
} }
fn handle_event( fn handle_event(context: &IoWorkerContext, commands_sender: &Sender<StateChangeCommand>, event: LoupedeckEvent) -> bool {
config: &model::config::Config,
state: &State,
device: &LoupedeckDevice,
commands_sender: &Sender<StateChangeCommand>,
event: LoupedeckEvent,
) -> bool {
trace!("Handling event: {:?}", &event); trace!("Handling event: {:?}", &event);
match event { match event {
LoupedeckEvent::Disconnected => return false, LoupedeckEvent::Disconnected => return false,
LoupedeckEvent::ButtonDown { button } => { LoupedeckEvent::ButtonDown { button } => {
let position = ButtonPosition::of(&button); let position = ButtonPosition::of(&button);
let button_config = &config.buttons[position]; let button_config = &context.config.buttons[position];
commands_sender commands_sender
.send(StateChangeCommand::SetActivePages { .send(StateChangeCommand::SetActivePages {
key_page_id: button_config.key_page.as_ref().unwrap_or(&state.active_key_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(&state.active_knob_page_id).clone(), knob_page_id: button_config.knob_page.as_ref().unwrap_or(&context.state.active_knob_page_id).clone(),
}) })
.unwrap() .unwrap()
} }
LoupedeckEvent::Touch { x, y, is_end, .. } => { LoupedeckEvent::Touch { x, y, is_end, .. } => {
if is_end { if is_end {
let characteristics = device.characteristics(); let characteristics = context.device.characteristics();
let display = characteristics.get_display_at_coordinates(x, y); let display = characteristics.get_display_at_coordinates(x, y);
if let Some(display) = display { if let Some(display) = display {
@ -178,11 +188,11 @@ fn handle_event(
}; };
let path = KeyPath { let path = KeyPath {
page_id: state.active_key_page_id.clone(), page_id: context.state.active_key_page_id.clone(),
position, 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() "a".to_owned()
} else { } else {
String::new() String::new()
@ -200,29 +210,29 @@ fn handle_event(
true 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); debug!("Handling command: {:?}", &command);
match command { match command {
StateChangeCommand::SetActivePages { key_page_id, knob_page_id } => { StateChangeCommand::SetActivePages { key_page_id, knob_page_id } => {
state.active_key_page_id = key_page_id; context.state.active_key_page_id = key_page_id;
state.active_knob_page_id = knob_page_id; context.state.active_knob_page_id = knob_page_id;
for button in LoupedeckButton::VARIANTS { for button in LoupedeckButton::VARIANTS {
let position = ButtonPosition::of(button); 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) { 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 } => { StateChangeCommand::SetKeyLabel { path, value } => {
state.mutate_key_for_command( context.state.mutate_key_for_command(
"SetKeyLabel", "SetKeyLabel",
&path, &path,
Box::new(|k| { 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); draw_key_at_path_if_visible(context, path);
device.refresh_display(&device.characteristics().key_grid.display).unwrap(); context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
} }
StateChangeCommand::SetKeyIcon { path, value } => { StateChangeCommand::SetKeyIcon { path, value } => {
state.mutate_key_for_command( context.state.mutate_key_for_command(
"SetKeyIcon", "SetKeyIcon",
&path, &path,
Box::new(|k| { 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); draw_key_at_path_if_visible(context, path);
device.refresh_display(&device.characteristics().key_grid.display).unwrap(); 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 // active -> config.active_button_color
// no actions defined -> #000000 // no actions defined -> #000000
// inactive -> config.inactive_button_color // inactive -> config.inactive_button_color
fn get_correct_button_color(config: &model::config::Config, state: &mut State, button_position: ButtonPosition) -> RGB8 { fn get_correct_button_color(context: &IoWorkerContext, button_position: ButtonPosition) -> RGB8 {
let button_config = &config.buttons[button_position]; let button_config = &context.config.buttons[button_position];
if let Some(key_page) = &button_config.key_page { 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 let Some(knob_page) = &button_config.knob_page {
if knob_page == &state.active_knob_page_id { if knob_page == &context.state.active_knob_page_id {
return config.active_button_color.into(); 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); 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<u8> { fn get_key_index_for_position(key_grid: &LoupedeckDeviceKeyGridCharacteristics, position: KeyPosition) -> Option<u8> {
@ -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(); let (x, y, w, h) = key_grid.get_local_key_rect_xywh(index).unwrap();
device let p = render_key((w, h), key_grid.display.endianness, &context.icons, key);
.replace_framebuffer_area_raw(&key_grid.display, x, y, w, h, render_key(w, h, key_grid.display.endianness, key)) context.device.replace_framebuffer_area_raw(&key_grid.display, x, y, w, h, p).unwrap();
.unwrap();
} }
fn draw_key_at_index(device: &LoupedeckDevice, state: &State, index: u8) { fn draw_key_at_index(context: &IoWorkerContext, index: u8) {
let key_grid = &device.characteristics().key_grid; let position = get_key_position_for_index(&context.device.characteristics().key_grid, index);
let position = get_key_position_for_index(key_grid, index);
draw_key(device, key_grid, index, state.active_key_page().keys_by_position.get(&position)); 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) { fn draw_key_at_position_if_visible(context: &IoWorkerContext, position: KeyPosition) {
let key_grid = &device.characteristics().key_grid; let index = get_key_index_for_position(&context.device.characteristics().key_grid, position);
let index = get_key_index_for_position(key_grid, position);
if let Some(index) = index { 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) { fn draw_key_at_path_if_visible(context: &IoWorkerContext, path: KeyPath) {
if state.active_key_page_id == path.page_id { if context.state.active_key_page_id == path.page_id {
draw_key_at_position_if_visible(device, state, path.position); draw_key_at_position_if_visible(context, path.position);
} }
} }