270 lines
8.3 KiB
Rust
270 lines
8.3 KiB
Rust
mod calendar;
|
|
|
|
use image::{Luma, Pixel, RgbaImage};
|
|
use palette::{FromColor, IntoColor, LinLumaa};
|
|
use showbits_common::{
|
|
color::{self, BLACK, WHITE},
|
|
widgets::{Block, FontStuff, HasFontStuff, Image, Text},
|
|
Node, Tree, WidgetExt,
|
|
};
|
|
use taffy::{
|
|
style_helpers::{length, percent},
|
|
AlignItems, Display, FlexDirection,
|
|
};
|
|
use tokio::sync::mpsc;
|
|
|
|
use crate::printer::Printer;
|
|
|
|
pub enum Command {
|
|
Stop,
|
|
Rip,
|
|
Test,
|
|
Text(String),
|
|
Image { image: RgbaImage, bright: bool },
|
|
Photo { image: RgbaImage, title: String },
|
|
ChatMessage { username: String, content: String },
|
|
Calendar { year: i32, month: u8 },
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct Context {
|
|
font_stuff: FontStuff,
|
|
}
|
|
|
|
impl HasFontStuff for Context {
|
|
fn font_stuff(&mut self) -> &mut FontStuff {
|
|
&mut self.font_stuff
|
|
}
|
|
}
|
|
|
|
pub struct Drawer {
|
|
rx: mpsc::Receiver<Command>,
|
|
printer: Printer,
|
|
ctx: Context,
|
|
}
|
|
|
|
impl Drawer {
|
|
const FEED: f32 = 64.0;
|
|
|
|
pub fn new(rx: mpsc::Receiver<Command>, printer: Printer) -> Self {
|
|
Self {
|
|
rx,
|
|
printer,
|
|
ctx: Context::default(),
|
|
}
|
|
}
|
|
|
|
pub fn run(&mut self) -> anyhow::Result<()> {
|
|
while let Some(command) = self.rx.blocking_recv() {
|
|
if matches!(command, Command::Stop) {
|
|
break;
|
|
};
|
|
|
|
self.on_command(command)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn on_command(&mut self, command: Command) -> anyhow::Result<()> {
|
|
match command {
|
|
Command::Stop => {} // Already handled one level above
|
|
Command::Rip => self.printer.rip()?,
|
|
Command::Test => self.on_test()?,
|
|
Command::Text(text) => self.on_text(text)?,
|
|
Command::Image { image, bright } => self.on_image(image, bright)?,
|
|
Command::Photo { image, title } => self.on_photo(image, title)?,
|
|
Command::ChatMessage { username, content } => {
|
|
self.on_chat_message(username, content)?
|
|
}
|
|
Command::Calendar { year, month } => self.draw_calendar(year, month)?,
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn on_test(&mut self) -> anyhow::Result<()> {
|
|
let mut tree = Tree::<Context>::new(WHITE);
|
|
|
|
let text = Text::new()
|
|
.with_metrics(Text::default_metrics().scale(2.0))
|
|
.and_plain("Hello\nworld!")
|
|
.widget(&mut self.ctx.font_stuff)
|
|
.node()
|
|
.with_margin_horiz(length(8.0))
|
|
.with_margin_vert(length(2.0))
|
|
.register(&mut tree)?;
|
|
|
|
let wrap = Block::new()
|
|
.with_border(BLACK)
|
|
.node()
|
|
.with_border_all(length(2.0))
|
|
.and_child(text)
|
|
.register(&mut tree)?;
|
|
|
|
let root = Block::new()
|
|
.with_border(BLACK)
|
|
.node()
|
|
.with_size_width(percent(1.0))
|
|
.with_border_all(length(2.0))
|
|
.with_padding_all(length(10.0))
|
|
.and_child(wrap)
|
|
.register(&mut tree)?;
|
|
|
|
self.printer.print_tree(&mut tree, &mut self.ctx, root)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn on_text(&mut self, text: String) -> anyhow::Result<()> {
|
|
let mut tree = Tree::<Context>::new(WHITE);
|
|
|
|
let text = Text::new()
|
|
.with_metrics(Text::default_metrics().scale(2.0))
|
|
.and_plain(text)
|
|
.widget(&mut self.ctx.font_stuff)
|
|
.node()
|
|
.register(&mut tree)?;
|
|
|
|
let root = Node::empty()
|
|
.with_size_width(percent(1.0))
|
|
.and_child(text)
|
|
.register(&mut tree)?;
|
|
|
|
self.printer.print_tree(&mut tree, &mut self.ctx, root)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn on_image(&mut self, mut image: RgbaImage, bright: bool) -> anyhow::Result<()> {
|
|
let mut tree = Tree::<Context>::new(WHITE);
|
|
|
|
if bright {
|
|
for pixel in image.pixels_mut() {
|
|
let mut color = LinLumaa::from_color(color::from_image_color(*pixel));
|
|
color.luma = 1.0 - 0.4 * (1.0 - color.luma);
|
|
*pixel = color::to_image_color(color.into_color());
|
|
}
|
|
}
|
|
|
|
let image = Image::new(image)
|
|
.with_dither_palette(&[BLACK, WHITE])
|
|
.node()
|
|
.register(&mut tree)?;
|
|
|
|
let root = Node::empty()
|
|
.with_size_width(percent(1.0))
|
|
.with_padding_bottom(length(Self::FEED))
|
|
.with_display(Display::Flex)
|
|
.with_flex_direction(FlexDirection::Column)
|
|
.with_align_items(Some(AlignItems::Center))
|
|
.and_child(image)
|
|
.register(&mut tree)?;
|
|
|
|
self.printer.print_tree(&mut tree, &mut self.ctx, root)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn on_photo(&mut self, mut image: RgbaImage, title: String) -> anyhow::Result<()> {
|
|
let mut tree = Tree::<Context>::new(WHITE);
|
|
|
|
for pixel in image.pixels_mut() {
|
|
let [l] = pixel.to_luma().0;
|
|
let l = l as f32 / 255.0; // Convert to [0, 1]
|
|
let l = 1.0 - (0.4 * (1.0 - l)); // Lerp to [0.6, 1]
|
|
let l = (l.clamp(0.0, 1.0) * 255.0) as u8; // Convert back to [0, 255]
|
|
*pixel = Luma([l]).to_rgba();
|
|
}
|
|
|
|
let image = Image::new(image)
|
|
.with_dither_palette(&[BLACK, WHITE])
|
|
.node()
|
|
.register(&mut tree)?;
|
|
|
|
let title = Text::new()
|
|
.with_metrics(Text::default_metrics().scale(2.0))
|
|
.and_plain(title)
|
|
.widget(&mut self.ctx.font_stuff)
|
|
.node()
|
|
.register(&mut tree)?;
|
|
|
|
let root = Node::empty()
|
|
.with_size_width(percent(1.0))
|
|
.with_padding_bottom(length(Self::FEED))
|
|
.with_display(Display::Flex)
|
|
.with_flex_direction(FlexDirection::Column)
|
|
.with_align_items(Some(AlignItems::Center))
|
|
.with_gap(length(8.0))
|
|
.and_child(image)
|
|
.and_child(title)
|
|
.register(&mut tree)?;
|
|
|
|
self.printer.print_tree(&mut tree, &mut self.ctx, root)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn on_chat_message(&mut self, username: String, content: String) -> anyhow::Result<()> {
|
|
let mut tree = Tree::<Context>::new(WHITE);
|
|
|
|
let max_username_width_in_chars = 32.0;
|
|
let max_username_height_in_lines = 3.0;
|
|
let max_content_height_in_lines = 16.0;
|
|
|
|
let username = Text::new()
|
|
.and_plain(username)
|
|
.widget(&mut self.ctx.font_stuff)
|
|
.node()
|
|
.with_max_size_width(length(max_username_width_in_chars * 8.0))
|
|
.with_max_size_height(length(max_username_height_in_lines * 16.0))
|
|
.register(&mut tree)?;
|
|
|
|
let username = Block::new()
|
|
.with_border(BLACK)
|
|
.node()
|
|
.with_border_all(length(1.0))
|
|
.with_padding_horiz(length(1.0))
|
|
.with_flex_shrink(0.0) // Avoid wrapping
|
|
.and_child(username)
|
|
.register(&mut tree)?;
|
|
|
|
let content = if let Some(content) = content.strip_prefix("/me") {
|
|
let content = content.trim_start();
|
|
|
|
let content = Text::new()
|
|
.and_plain(content)
|
|
.widget(&mut self.ctx.font_stuff)
|
|
.node()
|
|
.with_max_size_height(length(max_content_height_in_lines * 16.0))
|
|
.register(&mut tree)?;
|
|
|
|
Block::new()
|
|
.with_border(BLACK)
|
|
.node()
|
|
.with_border_all(length(1.0))
|
|
.with_padding_horiz(length(1.0))
|
|
.and_child(content)
|
|
.register(&mut tree)?
|
|
} else {
|
|
let content = Text::new()
|
|
.and_plain(content)
|
|
.widget(&mut self.ctx.font_stuff)
|
|
.node()
|
|
.with_max_size_height(length(max_content_height_in_lines * 16.0))
|
|
.register(&mut tree)?;
|
|
|
|
Node::empty()
|
|
.with_padding_vert(length(1.0))
|
|
.and_child(content)
|
|
.register(&mut tree)?
|
|
};
|
|
|
|
let root = Node::empty()
|
|
.with_size_width(percent(1.0))
|
|
.with_padding_all(length(1.0))
|
|
.with_flex_direction(FlexDirection::Row)
|
|
.with_align_items(Some(AlignItems::Start))
|
|
.with_gap_width(length(2.0))
|
|
.and_child(username)
|
|
.and_child(content)
|
|
.register(&mut tree)?;
|
|
|
|
self.printer.print_tree(&mut tree, &mut self.ctx, root)?;
|
|
Ok(())
|
|
}
|
|
}
|