commit
This commit is contained in:
parent
78291b75b7
commit
d97bf24168
14 changed files with 180 additions and 97 deletions
|
@ -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"
|
1
deckster/examples/full/icons/apps/spotify.svg
Normal file
1
deckster/examples/full/icons/apps/spotify.svg
Normal 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 |
|
@ -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<IconDescriptor> {
|
||||
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 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<IconDescriptor> {
|
|||
}
|
||||
|
||||
pub fn load_icons(
|
||||
icon_packs_by_id: HashMap<String, IconPack>,
|
||||
config_directory: &Path,
|
||||
icon_packs_by_id: &HashMap<String, IconPack>,
|
||||
descriptors: HashSet<IconDescriptor>,
|
||||
key_size: (u16, u16),
|
||||
dpi: f32,
|
||||
) -> Result<HashMap<IconDescriptor, LoadedIcon>> {
|
||||
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()) {
|
||||
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<String, IconPack>,
|
||||
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)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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<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()))
|
||||
}
|
||||
|
|
|
@ -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<Self, Self::Err> {
|
||||
|
@ -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("<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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SwitchState, IconDescriptorString>,
|
||||
pub icon: HashMap<SwitchState, IconDescriptor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ButtonConfig {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub icon: HashMap<ButtonState, IconDescriptorString>,
|
||||
pub icon: HashMap<ButtonState, IconDescriptor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
||||
|
|
|
@ -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<PlayPauseState, IconDescriptorString>,
|
||||
pub icon: HashMap<PlayPauseState, IconDescriptor>,
|
||||
#[serde(default)]
|
||||
pub action: PlayPauseAction,
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ pub enum PlayPauseAction {
|
|||
#[derive(Debug, Deserialize)]
|
||||
pub struct PreviousAndNextConfig {
|
||||
#[serde(default)]
|
||||
pub icon: HashMap<PreviousAndNextState, IconDescriptorString>,
|
||||
pub icon: HashMap<PreviousAndNextState, IconDescriptor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
||||
|
|
|
@ -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<ShuffleState, IconDescriptorString>,
|
||||
pub icon: HashMap<ShuffleState, IconDescriptor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RepeatConfig {
|
||||
#[serde(default)]
|
||||
pub icon: HashMap<RepeatState, IconDescriptorString>,
|
||||
pub icon: HashMap<RepeatState, IconDescriptor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
||||
|
|
|
@ -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<String>,
|
||||
pub icon: Option<IconDescriptorString>,
|
||||
pub icon: Option<IconDescriptor>,
|
||||
#[serde(default)]
|
||||
pub mode: KeyModes,
|
||||
}
|
||||
|
|
|
@ -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<State, String>,
|
||||
#[serde(default)]
|
||||
pub icon: HashMap<State, IconDescriptorString>,
|
||||
pub icon: IconMap<State>,
|
||||
pub circle_indicator: Option<CircleIndicatorConfig>,
|
||||
pub bar_indicator: Option<BarIndicatorConfig>,
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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<State> = HashMap<State, IconDescriptor>;
|
||||
|
|
|
@ -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<IconDescriptor, LoadedIcon>, 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()
|
||||
}
|
||||
|
|
|
@ -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<model::config::Config>,
|
||||
icons: HashMap<IconDescriptor, LoadedIcon>,
|
||||
device: LoupedeckDevice,
|
||||
state: State,
|
||||
}
|
||||
|
||||
fn do_io_work(
|
||||
config: Arc<model::config::Config>,
|
||||
icons: HashMap<IconDescriptor, LoadedIcon>,
|
||||
device: LoupedeckDevice,
|
||||
events_receiver: Receiver<LoupedeckEvent>,
|
||||
commands_sender: Sender<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 {
|
||||
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<StateChangeCommand>,
|
||||
event: LoupedeckEvent,
|
||||
) -> bool {
|
||||
fn handle_event(context: &IoWorkerContext, commands_sender: &Sender<StateChangeCommand>, 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<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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue