commit
This commit is contained in:
parent
a57347bd78
commit
a352885158
7 changed files with 251 additions and 73 deletions
|
@ -28,9 +28,9 @@ format = "svg"
|
|||
[icon_packs.fad]
|
||||
path = "icons/fad"
|
||||
format = "svg"
|
||||
global_filter = "invert"
|
||||
global_filter = "invert|scale=1.2"
|
||||
|
||||
[icon_packs.ph]
|
||||
path = "icons/ph"
|
||||
format = "svg"
|
||||
global_filter = "invert"
|
||||
global_filter = "invert|scale=1.2"
|
|
@ -1,31 +1,31 @@
|
|||
[keys.1x1]
|
||||
icon = "@ph/play[invert|alpha=0.5|scale=2.0]"
|
||||
icon = "@apps/spotify[scale=2.0|grayscale]"
|
||||
mode.vibrate.pattern = "low"
|
||||
mode.media__play_pause.icon.paused = "@ph/play"
|
||||
mode.media__play_pause.icon.playing = "@ph/pause"
|
||||
|
||||
[keys.1x2]
|
||||
icon = "@fad/shuffle[invert]"
|
||||
icon = "@fad/shuffle[alpha=0.6]"
|
||||
mode.vibrate.pattern = "low"
|
||||
mode.spotify__shuffle.icon.active = "@fad/shuffle[color=#58fc11]"
|
||||
|
||||
[keys.2x1]
|
||||
icon = "@ph/timer[invert|scale=0.5]"
|
||||
icon = "@ph/timer[color=#ff0000]"
|
||||
mode.vibrate.pattern = "low"
|
||||
mode.timer.durations = ["60s", "5m", "10m", "15m", "30m"]
|
||||
mode.timer.vibrate_when_finished = true
|
||||
mode.timer.needy = true
|
||||
|
||||
[keys.3x3]
|
||||
icon = "@fad/thunderbolt[invert]"
|
||||
icon = "@fad/thunderbolt[border=#00ff00]"
|
||||
label = "Dock"
|
||||
mode.vibrate.pattern = "low"
|
||||
mode.home_assistant__switch.name = "switch.moritz_thunderbolt_dock"
|
||||
mode.home_assistant__switch.icon.on = "@fad/thunderbolt[color=#58fc11]"
|
||||
|
||||
[keys.3x4]
|
||||
icon = "@ph/computer-tower[invert]"
|
||||
label = "Tower PC"
|
||||
[keys.4x3]
|
||||
icon = "@ph/computer-tower[border=#00ff00]"
|
||||
label = "Tower PC unnötig lang"
|
||||
mode.vibrate.pattern = "low"
|
||||
mode.home_assistant__switch.name = "switch.mwin"
|
||||
mode.home_assistant__switch.icon.on = "@ph/computer-tower[color=#58fc11]"
|
|
@ -2,7 +2,6 @@ use color_eyre::{eyre::ContextCompat, Result};
|
|||
use tiny_skia::{ColorU8, Pixmap, PremultipliedColorU8};
|
||||
|
||||
use crate::model::image_filter::ImageFilter;
|
||||
use crate::model::rgb::RGB8Wrapper;
|
||||
|
||||
pub fn apply(original: &Pixmap, filter: &ImageFilter) -> Result<Pixmap> {
|
||||
let mut result = if let Some(rect) = filter.crop {
|
||||
|
@ -11,45 +10,34 @@ pub fn apply(original: &Pixmap, filter: &ImageFilter) -> Result<Pixmap> {
|
|||
original.clone()
|
||||
};
|
||||
|
||||
// scale is handled in runner::graphics::render_key
|
||||
// scale, rotate and border are handled in runner::graphics::render_key
|
||||
|
||||
// rotate
|
||||
|
||||
if let Some(color) = &filter.color {
|
||||
apply_color(&mut result, color);
|
||||
for p in result.pixels_mut() {
|
||||
if filter.invert {
|
||||
*p = PremultipliedColorU8::from_rgba(p.alpha() - p.red(), p.alpha() - p.green(), p.alpha() - p.blue(), p.alpha()).unwrap();
|
||||
}
|
||||
|
||||
if filter.alpha != 1.0 {
|
||||
apply_alpha(&mut result, filter.alpha);
|
||||
}
|
||||
|
||||
// grayscale
|
||||
|
||||
if filter.invert {
|
||||
apply_invert(&mut result);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn apply_alpha(pixmap: &mut Pixmap, alpha: f32) {
|
||||
for p in pixmap.pixels_mut() {
|
||||
let d = p.demultiply();
|
||||
*p = ColorU8::from_rgba(d.red(), d.green(), d.blue(), (d.alpha() as f32 * alpha).round() as u8).premultiply();
|
||||
*p = ColorU8::from_rgba(d.red(), d.green(), d.blue(), (d.alpha() as f32 * filter.alpha).round() as u8).premultiply();
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_invert(pixmap: &mut Pixmap) {
|
||||
for p in pixmap.pixels_mut() {
|
||||
*p = PremultipliedColorU8::from_rgba(p.alpha() - p.red(), p.alpha() - p.green(), p.alpha() - p.blue(), p.alpha()).unwrap();
|
||||
if filter.grayscale {
|
||||
let a = p.alpha();
|
||||
if a > 0 {
|
||||
// The values are adjusted to make it look right on the Loupedeck Live display
|
||||
let luminosity = (0.3 * p.red() as f32 + 0.6 * p.green() as f32 + 0.1 * p.blue() as f32).floor() as u8;
|
||||
*p = ColorU8::from_rgba(luminosity, luminosity, luminosity, a).premultiply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_color(pixmap: &mut Pixmap, color: &RGB8Wrapper) {
|
||||
for p in pixmap.pixels_mut() {
|
||||
if let Some(color) = &filter.color {
|
||||
let a = p.alpha();
|
||||
if a > 0 {
|
||||
*p = ColorU8::from_rgba(color.r, color.g, color.b, a).premultiply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use tiny_skia::{Pixmap, Transform};
|
|||
|
||||
use crate::model::config::{Config, IconFormat, IconPack};
|
||||
use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource};
|
||||
use crate::model::rgb::RGB8Wrapper;
|
||||
use crate::model::IconMap;
|
||||
|
||||
mod filter;
|
||||
|
@ -17,6 +18,8 @@ mod filter;
|
|||
pub struct LoadedIcon {
|
||||
pub pixmap: Pixmap,
|
||||
pub scale: f32,
|
||||
pub clockwise_quarter_rotations: u8,
|
||||
pub border: Option<RGB8Wrapper>,
|
||||
}
|
||||
|
||||
pub fn get_used_icon_descriptors(config: &Config) -> HashSet<IconDescriptor> {
|
||||
|
@ -83,7 +86,27 @@ pub fn load_icons(
|
|||
descriptors: HashSet<IconDescriptor>,
|
||||
dpi: f32,
|
||||
) -> Result<HashMap<IconDescriptor, LoadedIcon>> {
|
||||
let mut unfiltered_pixmap_by_source: HashMap<IconDescriptorSource, Pixmap> = HashMap::new();
|
||||
let mut highest_scale_by_source: HashMap<IconDescriptorSource, f32> = HashMap::new();
|
||||
|
||||
for d in &descriptors {
|
||||
let mut scale = d.filter.scale;
|
||||
if let IconDescriptorSource::IconPack { pack_id, .. } = &d.source {
|
||||
let pack = &icon_packs_by_id[pack_id];
|
||||
if let Some(filter) = &pack.global_filter {
|
||||
scale *= filter.scale;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(v) = highest_scale_by_source.get_mut(&d.source) {
|
||||
if *v < scale {
|
||||
*v = scale;
|
||||
}
|
||||
} else {
|
||||
highest_scale_by_source.insert(d.source.clone(), scale);
|
||||
}
|
||||
}
|
||||
|
||||
let mut unfiltered_scaled_pixmap_by_source: HashMap<IconDescriptorSource, (Pixmap, f32)> = HashMap::new();
|
||||
let mut icons_by_descriptor: HashMap<IconDescriptor, LoadedIcon> = HashMap::new();
|
||||
let mut fonts_db = resvg::usvg::fontdb::Database::new();
|
||||
fonts_db.load_system_fonts();
|
||||
|
@ -93,29 +116,59 @@ pub fn load_icons(
|
|||
continue;
|
||||
}
|
||||
|
||||
let original_image = match unfiltered_pixmap_by_source.entry(descriptor.source.clone()) {
|
||||
let (original_image, original_image_scale) = match unfiltered_scaled_pixmap_by_source.entry(descriptor.source.clone()) {
|
||||
Entry::Occupied(o) => o.into_mut(),
|
||||
Entry::Vacant(v) => v.insert(read_image(config_directory, icon_packs_by_id, dpi, &fonts_db, descriptor.source.clone())?),
|
||||
Entry::Vacant(v) => v.insert(read_image_and_get_scale(
|
||||
config_directory,
|
||||
icon_packs_by_id,
|
||||
dpi,
|
||||
&fonts_db,
|
||||
descriptor.source.clone(),
|
||||
highest_scale_by_source[&descriptor.source],
|
||||
)?),
|
||||
};
|
||||
|
||||
let pixmap = filter::apply(original_image, &descriptor.filter)?;
|
||||
let scale = descriptor.filter.scale;
|
||||
let (pixmap, scale) = if let IconDescriptorSource::IconPack { pack_id, .. } = &descriptor.source {
|
||||
let pack = &icon_packs_by_id[pack_id];
|
||||
if let Some(global_filter) = &pack.global_filter {
|
||||
(
|
||||
filter::apply(&filter::apply(original_image, global_filter)?, &descriptor.filter)?,
|
||||
descriptor.filter.scale * global_filter.scale,
|
||||
)
|
||||
} else {
|
||||
(filter::apply(original_image, &descriptor.filter)?, descriptor.filter.scale)
|
||||
}
|
||||
} else {
|
||||
(filter::apply(original_image, &descriptor.filter)?, descriptor.filter.scale)
|
||||
};
|
||||
|
||||
icons_by_descriptor.insert(descriptor, LoadedIcon { pixmap, scale });
|
||||
let scale = scale / *original_image_scale;
|
||||
let clockwise_quarter_rotations = descriptor.filter.clockwise_quarter_rotations;
|
||||
let border = descriptor.filter.border;
|
||||
icons_by_descriptor.insert(
|
||||
descriptor,
|
||||
LoadedIcon {
|
||||
pixmap,
|
||||
scale,
|
||||
clockwise_quarter_rotations,
|
||||
border,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(icons_by_descriptor)
|
||||
}
|
||||
|
||||
fn read_image(
|
||||
fn read_image_and_get_scale(
|
||||
config_directory: &Path,
|
||||
icon_packs_by_id: &HashMap<String, IconPack>,
|
||||
dpi: f32,
|
||||
fonts_db: &resvg::usvg::fontdb::Database,
|
||||
source: IconDescriptorSource,
|
||||
) -> Result<Pixmap> {
|
||||
highest_scale: f32,
|
||||
) -> Result<(Pixmap, f32)> {
|
||||
let path = match source {
|
||||
IconDescriptorSource::None => return Ok(Pixmap::new(1, 1).unwrap()),
|
||||
IconDescriptorSource::None => return Ok((Pixmap::new(1, 1).unwrap(), 1.0)),
|
||||
IconDescriptorSource::Path(path) => path,
|
||||
IconDescriptorSource::IconPack { pack_id, icon_id } => {
|
||||
let pack = icon_packs_by_id.get(&pack_id).wrap_err_with(|| format!("Unknown icon pack: @{}", pack_id))?;
|
||||
|
@ -132,16 +185,21 @@ fn read_image(
|
|||
Ok(match path.extension() {
|
||||
None => return Err(eyre!("Invalid icon path: {:?}", path)),
|
||||
Some(extension) => match extension.to_string_lossy().as_ref() {
|
||||
"png" => Pixmap::load_png(&path).wrap_err_with(|| format!("Failed to open or decode the PNG file at {}", path.to_string_lossy()))?,
|
||||
"svg" => {
|
||||
read_image_from_svg(&path, dpi, fonts_db).wrap_err_with(|| format!("Failed to open or decode the SVG file at {}", path.to_string_lossy()))?
|
||||
}
|
||||
"png" => (
|
||||
Pixmap::load_png(&path).wrap_err_with(|| format!("Failed to open or decode the PNG file at {}", path.to_string_lossy()))?,
|
||||
1.0,
|
||||
),
|
||||
"svg" => (
|
||||
read_image_from_svg(&path, dpi, fonts_db, highest_scale)
|
||||
.wrap_err_with(|| format!("Failed to open or decode the SVG file at {}", path.to_string_lossy()))?,
|
||||
highest_scale,
|
||||
),
|
||||
extension => return Err(eyre!("Invalid file extension, only *.png and *.svg are allowed: {}", extension)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn read_image_from_svg(path: &Path, dpi: f32, font_db: &resvg::usvg::fontdb::Database) -> Result<Pixmap> {
|
||||
fn read_image_from_svg(path: &Path, dpi: f32, font_db: &resvg::usvg::fontdb::Database, scale: f32) -> Result<Pixmap> {
|
||||
let raw_data = std::fs::read(path)?;
|
||||
|
||||
let tree = {
|
||||
|
@ -162,9 +220,9 @@ fn read_image_from_svg(path: &Path, dpi: f32, font_db: &resvg::usvg::fontdb::Dat
|
|||
};
|
||||
|
||||
let size = tree.size.to_int_size();
|
||||
let mut pixmap = Pixmap::new(size.width(), size.height()).unwrap();
|
||||
let mut pixmap = Pixmap::new((size.width() as f32 * scale).ceil() as u32, (size.height() as f32 * scale).ceil() as u32).unwrap();
|
||||
|
||||
tree.render(Transform::default(), &mut pixmap.as_mut());
|
||||
tree.render(Transform::from_scale(scale, scale), &mut pixmap.as_mut());
|
||||
|
||||
Ok(pixmap)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ pub struct ImageFilter {
|
|||
pub alpha: f32,
|
||||
pub grayscale: bool,
|
||||
pub invert: bool,
|
||||
pub border: Option<RGB8Wrapper>,
|
||||
}
|
||||
|
||||
impl Eq for ImageFilter {
|
||||
|
@ -37,6 +38,7 @@ const DEFAULT_IMAGE_FILTER: ImageFilter = ImageFilter {
|
|||
alpha: 1.0,
|
||||
grayscale: false,
|
||||
invert: false,
|
||||
border: None,
|
||||
};
|
||||
|
||||
impl Default for ImageFilter {
|
||||
|
@ -53,7 +55,7 @@ pub enum ImageFilterFromStringError {
|
|||
#[error("Filter {name} can only be used once.")]
|
||||
FilterUsedMoreThanOnce { name: String },
|
||||
|
||||
#[error("Only the following values are for rotate: -90, 90, 180, 270")]
|
||||
#[error("Only the following values are allowed for rotate: -90, 90, 180, 270")]
|
||||
RotationNotAllowed,
|
||||
|
||||
#[error("Filter {filter_name} requires a value ({filter_name}=<value>).")]
|
||||
|
@ -169,6 +171,16 @@ impl FromStr for ImageFilter {
|
|||
"alpha" => result.alpha = parse_f32_filter_value(&filter_name, use_raw_value()?, "0..<1", Box::new(|v| (0.0..1.0).contains(v)))?,
|
||||
"grayscale" => result.grayscale = use_bool_value()?,
|
||||
"invert" => result.invert = use_bool_value()?,
|
||||
"border" => {
|
||||
let raw_value = use_raw_value()?;
|
||||
|
||||
result.border = Some(
|
||||
RGB8Wrapper::from_str(&raw_value).map_err(|_| ImageFilterFromStringError::FilterValueNotParsable {
|
||||
filter_name: filter_name.to_string(),
|
||||
raw_value,
|
||||
})?,
|
||||
)
|
||||
}
|
||||
_ => return Err(ImageFilterFromStringError::UnknownFilter { name: filter_name }),
|
||||
};
|
||||
}
|
||||
|
@ -202,7 +214,7 @@ impl Display for ImageFilter {
|
|||
f.write_str("|")?
|
||||
}
|
||||
|
||||
f.write_fmt(format_args!("rotate={}", self.clockwise_quarter_rotations * 90))?;
|
||||
f.write_fmt(format_args!("rotate={}", self.clockwise_quarter_rotations as u16 * 90))?;
|
||||
is_first = false;
|
||||
}
|
||||
|
||||
|
@ -236,6 +248,15 @@ impl Display for ImageFilter {
|
|||
f.write_str("|")?
|
||||
}
|
||||
f.write_str("invert")?;
|
||||
is_first = false;
|
||||
}
|
||||
|
||||
if let Some(color) = self.color {
|
||||
if !is_first {
|
||||
f.write_str("|")?
|
||||
}
|
||||
|
||||
f.write_fmt(format_args!("color={}", color))?;
|
||||
// is_first = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use tiny_skia::{BlendMode, Color, FilterQuality, IntSize, Pixmap, PixmapPaint, PremultipliedColorU8, Transform};
|
||||
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 loupedeck_serial::util::Endianness;
|
||||
|
||||
use crate::icons::LoadedIcon;
|
||||
use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource};
|
||||
use crate::runner::graphics::labels::LabelRenderer;
|
||||
use crate::runner::state::Key;
|
||||
|
||||
pub fn render_key(key_size: IntSize, buffer_endianness: Endianness, icons: &HashMap<IconDescriptor, LoadedIcon>, state: Option<&Key>) -> Bytes {
|
||||
let mut canvas = Pixmap::new(key_size.width(), key_size.height()).unwrap();
|
||||
pub fn render_key(
|
||||
label_renderer: &mut LabelRenderer,
|
||||
key_size: IntSize,
|
||||
buffer_endianness: Endianness,
|
||||
icons: &HashMap<IconDescriptor, LoadedIcon>,
|
||||
state: Option<&Key>,
|
||||
) -> Bytes {
|
||||
let mut pixmap = Pixmap::new(key_size.width(), key_size.height()).unwrap();
|
||||
|
||||
if let Some(state) = state {
|
||||
if state.icon.source != IconDescriptorSource::None {
|
||||
|
@ -26,20 +36,47 @@ pub fn render_key(key_size: IntSize, buffer_endianness: Endianness, icons: &Hash
|
|||
quality: FilterQuality::Bicubic,
|
||||
};
|
||||
|
||||
canvas.draw_pixmap(
|
||||
(((key_size.width() - scaled_size.width()) / 2) as f32 / icon.scale).round() as i32,
|
||||
(((key_size.height() - scaled_size.height()) / 2) as f32 / icon.scale).round() as i32,
|
||||
pixmap.draw_pixmap(
|
||||
(((key_size.width() as i32 - scaled_size.width() as i32) / 2) as f32 / icon.scale).round() as i32,
|
||||
(((key_size.height() as i32 - scaled_size.height() as i32) / 2) as f32 / icon.scale).round() as i32,
|
||||
icon.pixmap.as_ref(),
|
||||
&PAINT,
|
||||
Transform::from_scale(icon.scale, icon.scale),
|
||||
Transform::from_scale(icon.scale, icon.scale).post_rotate_at(
|
||||
(icon.clockwise_quarter_rotations as f32) * 90.0,
|
||||
key_size.width() as f32 / 2.0,
|
||||
key_size.height() as f32 / 2.0,
|
||||
),
|
||||
None,
|
||||
);
|
||||
|
||||
if let Some(color) = icon.border {
|
||||
let path = PathBuilder::from_rect(Rect::from_xywh(-1.0, -2.0, pixmap.width() as f32, pixmap.height() as f32).unwrap());
|
||||
|
||||
let paint = Paint {
|
||||
shader: Shader::SolidColor(Color::from_rgba8(color.r, color.g, color.b, 255)),
|
||||
..Paint::default()
|
||||
};
|
||||
|
||||
static STROKE: Stroke = Stroke {
|
||||
width: 15.0,
|
||||
miter_limit: 4.0,
|
||||
line_cap: LineCap::Butt,
|
||||
line_join: LineJoin::Round,
|
||||
dash: None,
|
||||
};
|
||||
|
||||
pixmap.stroke_path(&path, &paint, &STROKE, Transform::identity(), None);
|
||||
}
|
||||
} else {
|
||||
canvas.fill(Color::BLACK);
|
||||
}
|
||||
|
||||
convert_pixels_to_rgb565(canvas.pixels(), buffer_endianness).freeze()
|
||||
if !state.label.is_empty() {
|
||||
label_renderer.render(&mut pixmap, &state.label);
|
||||
}
|
||||
} else {
|
||||
pixmap.fill(Color::BLACK);
|
||||
}
|
||||
|
||||
convert_pixels_to_rgb565(pixmap.pixels(), buffer_endianness).freeze()
|
||||
}
|
||||
|
||||
fn convert_pixels_to_rgb565(pixels: &[PremultipliedColorU8], endianness: Endianness) -> BytesMut {
|
||||
|
@ -61,3 +98,62 @@ fn convert_pixels_to_rgb565(pixels: &[PremultipliedColorU8], endianness: Endiann
|
|||
|
||||
result
|
||||
}
|
||||
|
||||
pub mod labels {
|
||||
use cosmic_text::{Align, Attrs, AttrsList, Buffer, BufferLine, FontSystem, Metrics, Shaping, SwashCache};
|
||||
use tiny_skia::{Color, Paint, Pixmap, Rect, Shader, Transform};
|
||||
|
||||
pub struct LabelRenderer {
|
||||
font_system: FontSystem,
|
||||
swash_cache: SwashCache,
|
||||
buffer: Buffer,
|
||||
}
|
||||
|
||||
impl LabelRenderer {
|
||||
pub fn new() -> Self {
|
||||
let mut font_system = FontSystem::new();
|
||||
let buffer = Buffer::new(&mut font_system, Metrics::new(11.0, 11.0));
|
||||
|
||||
LabelRenderer {
|
||||
font_system,
|
||||
swash_cache: SwashCache::new(),
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, pixmap: &mut Pixmap, text: &String) {
|
||||
let attrs = Attrs::new();
|
||||
let mut line = BufferLine::new(text, AttrsList::new(attrs), Shaping::Advanced);
|
||||
line.set_align(Some(Align::Center));
|
||||
|
||||
self.buffer.lines.clear();
|
||||
self.buffer.lines.push(line);
|
||||
|
||||
const PADDING: f32 = 12.0;
|
||||
self.buffer.set_size(
|
||||
&mut self.font_system,
|
||||
pixmap.width() as f32 - PADDING * 2.0,
|
||||
pixmap.height() as f32 - PADDING * 2.0,
|
||||
);
|
||||
|
||||
self.buffer.shape_until_scroll(&mut self.font_system);
|
||||
|
||||
self.buffer.draw(
|
||||
&mut self.font_system,
|
||||
&mut self.swash_cache,
|
||||
cosmic_text::Color::rgb(255, 255, 255),
|
||||
|x, y, w, h, color| {
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(x as f32 + PADDING, y as f32 + PADDING, w as f32, h as f32).unwrap(),
|
||||
&Paint {
|
||||
shader: Shader::SolidColor(Color::from_rgba8(color.r(), color.g(), color.b(), color.a())),
|
||||
..Paint::default()
|
||||
},
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
@ -22,6 +23,7 @@ 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};
|
||||
use crate::runner::graphics::labels::LabelRenderer;
|
||||
use crate::runner::graphics::render_key;
|
||||
use crate::runner::state::{Key, State, StateChangeCommand};
|
||||
|
||||
|
@ -136,6 +138,7 @@ enum IoWork {
|
|||
struct IoWorkerContext {
|
||||
config: Arc<model::config::Config>,
|
||||
icons: HashMap<IconDescriptor, LoadedIcon>,
|
||||
label_renderer: RefCell<LabelRenderer>,
|
||||
device: LoupedeckDevice,
|
||||
state: State,
|
||||
}
|
||||
|
@ -149,7 +152,13 @@ fn do_io_work(
|
|||
commands_receiver: Receiver<StateChangeCommand>,
|
||||
) {
|
||||
let state = create_state(&config);
|
||||
let mut context = IoWorkerContext { config, icons, device, state };
|
||||
let mut context = IoWorkerContext {
|
||||
config,
|
||||
icons,
|
||||
label_renderer: RefCell::new(LabelRenderer::new()),
|
||||
device,
|
||||
state,
|
||||
};
|
||||
|
||||
loop {
|
||||
let a = flume::Selector::new()
|
||||
|
@ -306,7 +315,13 @@ 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 p = render_key(IntSize::from_wh(w as u32, h as u32).unwrap(), key_grid.display.endianness, &context.icons, key);
|
||||
let p = render_key(
|
||||
&mut context.label_renderer.borrow_mut(),
|
||||
IntSize::from_wh(w as u32, h as u32).unwrap(),
|
||||
key_grid.display.endianness,
|
||||
&context.icons,
|
||||
key,
|
||||
);
|
||||
context.device.replace_framebuffer_area_raw(&key_grid.display, x, y, w, h, p).unwrap();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue