mirror of
https://codeberg.org/Fl1tzi/microdeck.git
synced 2024-05-19 19:20:20 +00:00
complete folder switching, temporary icon
This commit is contained in:
parent
5ddff29c87
commit
41420d3080
|
@ -1,5 +0,0 @@
|
||||||
# Fonts
|
|
||||||
|
|
||||||
This folder holds all fonts which may be used by the modules.
|
|
||||||
|
|
||||||
Thanks to the creators of these awesome fonts!
|
|
|
@ -1,94 +0,0 @@
|
||||||
Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
|
|
Binary file not shown.
|
@ -16,7 +16,7 @@ use tokio::{
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
sync::mpsc::{self, error::TrySendError},
|
sync::mpsc::{self, error::TrySendError},
|
||||||
};
|
};
|
||||||
use tracing::{debug, error, info_span, trace};
|
use tracing::{debug, error, info_span, trace, warn};
|
||||||
|
|
||||||
/// A module controller in holding the information of a Module
|
/// A module controller in holding the information of a Module
|
||||||
pub type ModuleController = (Arc<Button>, Option<mpsc::Sender<HostEvent>>);
|
pub type ModuleController = (Arc<Button>, Option<mpsc::Sender<HostEvent>>);
|
||||||
|
@ -42,6 +42,7 @@ pub struct Device {
|
||||||
modules_runtime: Option<Runtime>,
|
modules_runtime: Option<Runtime>,
|
||||||
config: DeviceConfig,
|
config: DeviceConfig,
|
||||||
spaces: Arc<HashMap<String, Space>>,
|
spaces: Arc<HashMap<String, Space>>,
|
||||||
|
selected_space: Option<String>,
|
||||||
serial: String,
|
serial: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ impl Device {
|
||||||
modules_runtime: None,
|
modules_runtime: None,
|
||||||
config: device_conf,
|
config: device_conf,
|
||||||
spaces,
|
spaces,
|
||||||
|
selected_space: None,
|
||||||
serial,
|
serial,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -91,8 +93,17 @@ impl Device {
|
||||||
if self.modules_runtime.is_none() {
|
if self.modules_runtime.is_none() {
|
||||||
self.modules_runtime = Some(Runtime::new().unwrap());
|
self.modules_runtime = Some(Runtime::new().unwrap());
|
||||||
}
|
}
|
||||||
for i in 0..self.config.buttons.len() {
|
// TODO: DO THIS WITHOUT CLONING! Currently takes up a big amount of memory.
|
||||||
let button = self.config.buttons.get(i).unwrap().to_owned();
|
let button_config = match &self.selected_space {
|
||||||
|
Some(s) => self.spaces.get(s).unwrap_or_else(|| {
|
||||||
|
warn!("The space \"{}\" was not found", s);
|
||||||
|
&self.config.buttons
|
||||||
|
}
|
||||||
|
).to_owned(),
|
||||||
|
None => self.config.buttons.to_owned()
|
||||||
|
};
|
||||||
|
for i in 0..button_config.len() {
|
||||||
|
let button = button_config.get(i).unwrap().to_owned();
|
||||||
unwrap_or_error!(self._create_module(button).await);
|
unwrap_or_error!(self._create_module(button).await);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,16 +184,16 @@ impl Device {
|
||||||
|
|
||||||
/// Switch to a space. This will tear down the whole runtime of the current space.
|
/// Switch to a space. This will tear down the whole runtime of the current space.
|
||||||
#[tracing::instrument(skip_all, fields(serial = self.serial))]
|
#[tracing::instrument(skip_all, fields(serial = self.serial))]
|
||||||
async fn switch_to_space(&mut self, name: &String) {
|
async fn switch_to_space(&mut self, name: String) {
|
||||||
debug!("Switching to space {}", name);
|
debug!("Switching to space {}", name);
|
||||||
self.drop();
|
if name.to_lowercase() == "home" {
|
||||||
if let Some(space) = self.spaces.get(name) {
|
self.selected_space = None
|
||||||
self.config.buttons = space.clone();
|
|
||||||
self.device.reset().await.unwrap();
|
|
||||||
self.init_modules().await;
|
|
||||||
} else {
|
} else {
|
||||||
error!("Space {} was not found", name);
|
self.selected_space = Some(name)
|
||||||
};
|
}
|
||||||
|
self.drop();
|
||||||
|
self.device.reset().await.unwrap();
|
||||||
|
self.init_modules().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle all incoming button state updates from the listener (shell actions, module sender)
|
/// Handle all incoming button state updates from the listener (shell actions, module sender)
|
||||||
|
@ -219,7 +230,7 @@ impl Device {
|
||||||
Some(n) => n.clone(),
|
Some(n) => n.clone(),
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
self.switch_to_space(&name).await;
|
self.switch_to_space(name).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,21 @@ mod space;
|
||||||
use self::counter::Counter;
|
use self::counter::Counter;
|
||||||
use self::space::Space;
|
use self::space::Space;
|
||||||
|
|
||||||
use crate::GLOBAL_FONT;
|
|
||||||
// other things
|
// other things
|
||||||
|
use crate::GLOBAL_FONT;
|
||||||
use crate::config::Button;
|
use crate::config::Button;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
pub use deck_driver as streamdeck;
|
pub use deck_driver as streamdeck;
|
||||||
use futures_util::Future;
|
use futures_util::Future;
|
||||||
use image::{DynamicImage, Rgb, RgbImage};
|
use image::imageops::{resize, self};
|
||||||
|
use image::io::Reader;
|
||||||
|
use image::{DynamicImage, Rgb, RgbImage, ImageBuffer};
|
||||||
use imageproc::drawing::draw_text_mut;
|
use imageproc::drawing::draw_text_mut;
|
||||||
|
use imageproc::filter;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rusttype::Scale;
|
use rusttype::Scale;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::{BufReader, self};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{error::Error, sync::Arc};
|
use std::{error::Error, sync::Arc};
|
||||||
|
@ -25,7 +29,7 @@ use streamdeck::info::Kind;
|
||||||
use streamdeck::AsyncStreamDeck;
|
use streamdeck::AsyncStreamDeck;
|
||||||
pub use streamdeck::StreamDeckError;
|
pub use streamdeck::StreamDeckError;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info, trace};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref MODULE_MAP: HashMap<&'static str, ModuleFunction> = {
|
static ref MODULE_MAP: HashMap<&'static str, ModuleFunction> = {
|
||||||
|
@ -114,26 +118,44 @@ impl DeviceAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw an image with text
|
/// Draw an image with text
|
||||||
///
|
#[tracing::instrument(skip_all, fields(index = config.index))]
|
||||||
/// 60% image
|
pub fn image_with_text(&self, image: DynamicImage, text: String, config: &Button) -> DynamicImage {
|
||||||
/// 40% text
|
trace!("Render start");
|
||||||
pub fn image_with_text(&self, image: Vec<u8>, text: String, config: &Button) -> DynamicImage {
|
let (w, h) = self.resolution();
|
||||||
let res = self.resolution();
|
|
||||||
let mut image = RgbImage::new(res.0 as u32, res.1 as u32);
|
let image_scaling = parse_config(&config, &"IMAGE_SCALE".into(), 65.0);
|
||||||
|
|
||||||
|
// TODO: lots of parsing. This can probbably be improved.
|
||||||
|
let new_h = (h as f32 * (image_scaling * 0.01)) as u32;
|
||||||
|
let new_w = (w as f32 * (image_scaling * 0.01)) as u32;
|
||||||
|
|
||||||
|
// Calculate percentage of which we can scale down to the button resolution.
|
||||||
|
// By taking the smallest it keeps the aspect ratio.
|
||||||
|
// let percentage = f32::min(deck_w / image.width() as f32, deck_h / image.height() as f32);
|
||||||
|
|
||||||
|
let image = image.resize_to_fill(new_w, new_h, image::imageops::FilterType::Nearest);
|
||||||
|
|
||||||
|
let mut base_image = RgbImage::new(h as u32, w as u32);
|
||||||
draw_text_mut(
|
draw_text_mut(
|
||||||
&mut image,
|
&mut base_image,
|
||||||
Rgb([255, 255, 255]),
|
Rgb([255, 255, 255]),
|
||||||
-10,
|
0,
|
||||||
10,
|
h as i32 - 20,
|
||||||
Scale::uniform(parse_config(config, &"FONT_SIZE".into(), 20.0)),
|
Scale::uniform(parse_config(config, &"FONT_SIZE".into(), 15.0)),
|
||||||
&GLOBAL_FONT.get().unwrap(),
|
&GLOBAL_FONT.get().unwrap(),
|
||||||
&text,
|
&text,
|
||||||
);
|
);
|
||||||
image::DynamicImage::ImageRgb8(image)
|
// position at the middle
|
||||||
|
let free_space = w - image.width() as usize;
|
||||||
|
imageops::overlay(&mut base_image, &image.to_rgb8(), (free_space/2) as i64, 0);
|
||||||
|
trace!("Render end");
|
||||||
|
image::DynamicImage::ImageRgb8(base_image)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw text
|
/// Draw text
|
||||||
|
#[tracing::instrument(skip_all, fields(index = config.index))]
|
||||||
pub fn text(&self, text: String, config: &Button) -> DynamicImage {
|
pub fn text(&self, text: String, config: &Button) -> DynamicImage {
|
||||||
|
trace!("Render start");
|
||||||
let res = self.resolution();
|
let res = self.resolution();
|
||||||
let mut image = RgbImage::new(res.0 as u32, res.1 as u32);
|
let mut image = RgbImage::new(res.0 as u32, res.1 as u32);
|
||||||
draw_text_mut(
|
draw_text_mut(
|
||||||
|
@ -141,16 +163,41 @@ impl DeviceAccess {
|
||||||
Rgb([255, 255, 255]),
|
Rgb([255, 255, 255]),
|
||||||
10,
|
10,
|
||||||
10,
|
10,
|
||||||
Scale::uniform(parse_config(config, &"FONT_SIZE".into(), 20.0)),
|
Scale::uniform(parse_config(config, &"FONT_SIZE".into(), 15.0)),
|
||||||
&GLOBAL_FONT.get().unwrap(),
|
&GLOBAL_FONT.get().unwrap(),
|
||||||
&text,
|
&text,
|
||||||
);
|
);
|
||||||
|
trace!("Render end");
|
||||||
image::DynamicImage::ImageRgb8(image)
|
image::DynamicImage::ImageRgb8(image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads the image from the `IMAGE` option.
|
||||||
|
/// Displays [create_error_image] if it does not exist or cannot be loeded.
|
||||||
|
pub fn load_image(config: &Button) -> io::Result<DynamicImage> {
|
||||||
|
// TODO: maybe us an Option (faster?)
|
||||||
|
let file_path = parse_config(config, &"IMAGE".into(), "None".to_string());
|
||||||
|
|
||||||
|
if file_path == "None" {
|
||||||
|
return Ok(create_error_image());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Reader::open(file_path)?.decode().expect("Unable to decode image"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A smooth red image which should represent an empty space
|
||||||
|
pub fn create_error_image() -> DynamicImage {
|
||||||
|
let mut error_img: RgbImage = ImageBuffer::new(1, 1);
|
||||||
|
|
||||||
|
for pixel in error_img.enumerate_pixels_mut() {
|
||||||
|
*pixel.2 = image::Rgb([240, 128, 128]);
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicImage::ImageRgb8(error_img)
|
||||||
|
}
|
||||||
|
|
||||||
/// reads a key from the config and parses the config in the given type
|
/// reads a key from the config and parses the config in the given type
|
||||||
fn parse_config<T>(config: &Button, key: &String, if_wrong_type: T) -> T
|
pub fn parse_config<T>(config: &Button, key: &String, if_wrong_type: T) -> T
|
||||||
where
|
where
|
||||||
T: FromStr,
|
T: FromStr,
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,9 @@ use super::ChannelReceiver;
|
||||||
use super::DeviceAccess;
|
use super::DeviceAccess;
|
||||||
use super::Module;
|
use super::Module;
|
||||||
use super::ReturnError;
|
use super::ReturnError;
|
||||||
|
use super::create_error_image;
|
||||||
|
use super::load_image;
|
||||||
|
use super::parse_config;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -12,10 +15,16 @@ pub struct Space;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Module for Space {
|
impl Module for Space {
|
||||||
async fn run(
|
async fn run(
|
||||||
_streamdeck: DeviceAccess,
|
streamdeck: DeviceAccess,
|
||||||
_button_receiver: ChannelReceiver,
|
_button_receiver: ChannelReceiver,
|
||||||
_config: Arc<Button>,
|
config: Arc<Button>,
|
||||||
) -> Result<(), ReturnError> {
|
) -> Result<(), ReturnError> {
|
||||||
|
// let icon = load_image(&config).unwrap();
|
||||||
|
let icon = create_error_image();
|
||||||
|
|
||||||
|
let image = streamdeck.image_with_text(icon, parse_config(&config, &"NAME".into(), "Unknown".to_string()), &config);
|
||||||
|
|
||||||
|
streamdeck.write_img(image).await.unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue