From 3049a8bd7a27abfe69ce55160ab1c35c50549c2b Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 26 Jan 2025 12:40:59 +0100 Subject: [PATCH] split out gui options --- src/cli.rs | 57 +++++++++++++++ src/{font.rs => cp437_font.rs} | 26 +++---- src/execute_command.rs | 9 +-- src/font_renderer.rs | 25 ++++--- src/gui.rs | 20 +++--- src/main.rs | 126 ++++++++++++--------------------- 6 files changed, 143 insertions(+), 120 deletions(-) create mode 100644 src/cli.rs rename src/{font.rs => cp437_font.rs} (95%) diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..0925980 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,57 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct Cli { + #[arg( + long, + default_value = "0.0.0.0:2342", + help = "address and port to bind to" + )] + pub bind: String, + #[arg( + short, + long, + help = "Set default log level lower. You can also change this via the RUST_LOG environment variable." + )] + pub debug: bool, + #[arg( + short, + long, + help = "The name of the font family to use. This defaults to the system monospace font." + )] + pub font: Option, + #[clap(flatten)] + pub gui: GuiOptions, +} + +#[derive(Parser, Debug)] +pub struct GuiOptions { + #[arg( + short, + long, + default_value_t = false, + help = "Use the red color channel" + )] + pub red: bool, + #[arg( + short, + long, + default_value_t = false, + help = "Use the green color channel" + )] + pub green: bool, + #[arg( + short, + long, + default_value_t = false, + help = "Use the blue color channel" + )] + pub blue: bool, + #[arg( + short, + long, + default_value_t = false, + help = "add spacers between tile rows to simulate gaps in real display" + )] + pub spacers: bool, +} \ No newline at end of file diff --git a/src/font.rs b/src/cp437_font.rs similarity index 95% rename from src/font.rs rename to src/cp437_font.rs index 5f19492..1ff34b9 100644 --- a/src/font.rs +++ b/src/cp437_font.rs @@ -18,7 +18,17 @@ impl Cp437Font { impl Default for Cp437Font { fn default() -> Self { - load_static() + let mut bitmaps = + core::array::from_fn(|_| Bitmap::new(TILE_SIZE, TILE_SIZE)); + + for (char_code, bitmap) in bitmaps.iter_mut().enumerate() { + let bits = CP437_FONT_LINEAR[char_code]; + let mut bytes = bits.to_be_bytes(); + bytes.reverse(); + bitmap.data_ref_mut().copy_from_slice(bytes.as_slice()); + } + + Self::new(bitmaps) } } @@ -281,17 +291,3 @@ pub(crate) const CP437_FONT_LINEAR: [u64; 256] = [ 0x00007c7c7c7c7c00, // 0xfe 0x0000000000000000, // 0xff ]; - -fn load_static() -> Cp437Font { - let mut bitmaps = - core::array::from_fn(|_| Bitmap::new(TILE_SIZE, TILE_SIZE)); - - for (char_code, bitmap) in bitmaps.iter_mut().enumerate() { - let bits = CP437_FONT_LINEAR[char_code]; - let mut bytes = bits.to_be_bytes(); - bytes.reverse(); - bitmap.data_ref_mut().copy_from_slice(bytes.as_slice()); - } - - Cp437Font::new(bitmaps) -} diff --git a/src/execute_command.rs b/src/execute_command.rs index a32c7b2..36d4d0a 100644 --- a/src/execute_command.rs +++ b/src/execute_command.rs @@ -1,5 +1,5 @@ use crate::execute_command::ExecutionResult::{Failure, Shutdown, Success}; -use crate::font::Cp437Font; +use crate::cp437_font::Cp437Font; use crate::font_renderer::FontRenderer8x8; use log::{debug, error, info, trace, warn}; use servicepoint::{ @@ -13,7 +13,7 @@ pub struct CommandExecutor<'t> { display: &'t RwLock, luma: &'t RwLock, cp437_font: Cp437Font, - utf8_font: FontRenderer8x8, + font_renderer: FontRenderer8x8, } #[must_use] @@ -27,11 +27,12 @@ impl<'t> CommandExecutor<'t> { pub fn new( display: &'t RwLock, luma: &'t RwLock, + font_renderer: FontRenderer8x8, ) -> Self { CommandExecutor { display, luma, - utf8_font: FontRenderer8x8::default(), + font_renderer, cp437_font: Cp437Font::default(), } } @@ -190,7 +191,7 @@ impl<'t> CommandExecutor<'t> { let tile_x = char_x + x; let tile_y = char_y + y; - if let Err(e) = self.utf8_font.render( + if let Err(e) = self.font_renderer.render( char, &mut display, Origin::new(tile_x * TILE_SIZE, tile_y * TILE_SIZE), diff --git a/src/font_renderer.rs b/src/font_renderer.rs index dbb7a91..31f3537 100644 --- a/src/font_renderer.rs +++ b/src/font_renderer.rs @@ -43,12 +43,13 @@ pub enum RenderError { } impl FontRenderer8x8 { - pub fn new(font: Font, fallback_char: Option) -> Self { + const FALLBACK_CHAR: char = '?'; + pub fn new(font: Font) -> Self { let canvas = Canvas::new(vec2i(TILE_SIZE as i32, TILE_SIZE as i32), Format::A8); assert_eq!(canvas.pixels.len(), TILE_SIZE * TILE_SIZE); assert_eq!(canvas.stride, TILE_SIZE); - let fallback_char = fallback_char.and_then(|c| font.glyph_for_char(c)); + let fallback_char = font.glyph_for_char(Self::FALLBACK_CHAR); let result = Self { font: SendFont(font), fallback_char, @@ -57,6 +58,15 @@ impl FontRenderer8x8 { result } + pub fn from_name(family_name: String) -> Self { + let font = SystemSource::new() + .select_best_match(&[FamilyName::Title(family_name)], &Properties::new()) + .unwrap() + .load() + .unwrap(); + Self::new(font) + } + pub fn render( &self, char: char, @@ -104,17 +114,10 @@ impl FontRenderer8x8 { impl Default for FontRenderer8x8 { fn default() -> Self { let utf8_font = SystemSource::new() - .select_best_match( - &[ - FamilyName::Title("Roboto Mono".to_string()), - FamilyName::Title("Fira Mono".to_string()), - FamilyName::Monospace, - ], - &Properties::new(), - ) + .select_best_match(&[FamilyName::Monospace], &Properties::new()) .unwrap() .load() .unwrap(); - FontRenderer8x8::new(utf8_font, Some('?')) + FontRenderer8x8::new(utf8_font) } } diff --git a/src/gui.rs b/src/gui.rs index 8fceba8..562f7d6 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -12,14 +12,14 @@ use winit::event_loop::ActiveEventLoop; use winit::keyboard::KeyCode::KeyC; use winit::window::{Window, WindowId}; -use crate::Cli; +use crate::cli::{GuiOptions}; pub struct Gui<'t> { display: &'t RwLock, luma: &'t RwLock, window: Option, stop_udp_tx: Sender<()>, - cli: &'t Cli, + options: GuiOptions, logical_size: LogicalSize, } @@ -41,15 +41,15 @@ impl<'t> Gui<'t> { display: &'t RwLock, luma: &'t RwLock, stop_udp_tx: Sender<()>, - cli: &'t Cli, + options: GuiOptions, ) -> Self { Gui { + window: None, + logical_size: Self::get_logical_size(options.spacers), display, luma, stop_udp_tx, - cli, - window: None, - logical_size: Self::get_logical_size(cli.spacers), + options, } } @@ -79,7 +79,7 @@ impl<'t> Gui<'t> { let brightness_scale = (u8::MAX as f32) / (u8::from(Brightness::MAX) as f32); for tile_y in 0..TILE_HEIGHT { - if self.cli.spacers && tile_y != 0 { + if self.options.spacers && tile_y != 0 { // cannot just frame.skip(PIXEL_WIDTH as usize * SPACER_HEIGHT as usize) because of typing for _ in 0..PIXEL_WIDTH * SPACER_HEIGHT { frame.next().unwrap(); @@ -105,9 +105,9 @@ impl<'t> Gui<'t> { fn get_on_color(&self, brightness: u8) -> [u8; 4] { [ - if self.cli.red { brightness } else { 0u8 }, - if self.cli.green { brightness } else { 0u8 }, - if self.cli.blue { brightness } else { 0u8 }, + if self.options.red { brightness } else { 0u8 }, + if self.options.green { brightness } else { 0u8 }, + if self.options.blue { brightness } else { 0u8 }, 255, ] } diff --git a/src/main.rs b/src/main.rs index 433a4c9..e6daaa5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,89 +11,37 @@ use std::io::ErrorKind; use std::net::UdpSocket; use std::sync::{mpsc, RwLock}; use std::time::Duration; -use winit::event_loop::{ControlFlow, EventLoop}; +use winit::event_loop::{ControlFlow, EventLoop, EventLoopProxy}; +use cli::Cli; +use crate::font_renderer::FontRenderer8x8; mod execute_command; -mod font; +mod cp437_font; mod font_renderer; mod gui; - -#[derive(Parser, Debug)] -struct Cli { - #[arg( - long, - default_value = "0.0.0.0:2342", - help = "address and port to bind to" - )] - bind: String, - #[arg( - short, - long, - default_value_t = false, - help = "add spacers between tile rows to simulate gaps in real display" - )] - spacers: bool, - #[arg( - short, - long, - help = "Set default log level lower. You can also change this via the RUST_LOG environment variable." - )] - debug: bool, - #[arg( - short, - long, - default_value_t = false, - help = "Use the red color channel" - )] - red: bool, - #[arg( - short, - long, - default_value_t = false, - help = "Use the green color channel" - )] - green: bool, - #[arg( - short, - long, - default_value_t = false, - help = "Use the blue color channel" - )] - blue: bool, -} +mod cli; const BUF_SIZE: usize = 8985; fn main() { let mut cli = Cli::parse(); - - env_logger::builder() - .filter_level(if cli.debug { - LevelFilter::Debug - } else { - LevelFilter::Info - }) - .parse_default_env() - .init(); - - if !(cli.red || cli.blue || cli.green) { - cli.green = true; + if !(cli.gui.red || cli.gui.blue || cli.gui.green) { + cli.gui.green = true; } + init_logging(cli.debug); info!("starting with args: {:?}", &cli); + let socket = UdpSocket::bind(&cli.bind).expect("could not bind socket"); socket .set_nonblocking(true) .expect("could not enter non blocking mode"); let display = RwLock::new(Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT)); - - let mut luma = BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT); - luma.fill(Brightness::MAX); - let luma = RwLock::new(luma); + let luma = RwLock::new(BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT)); let (stop_udp_tx, stop_udp_rx) = mpsc::channel(); - let mut app = Gui::new(&display, &luma, stop_udp_tx, &cli); + let mut gui = Gui::new(&display, &luma, stop_udp_tx, cli.gui); let event_loop = EventLoop::with_user_event() .build() @@ -101,7 +49,9 @@ fn main() { event_loop.set_control_flow(ControlFlow::Wait); let event_proxy = event_loop.create_proxy(); - let command_executor = CommandExecutor::new(&display, &luma); + let font_renderer = cli.font.map(move |font| FontRenderer8x8::from_name(font)) + .unwrap_or_else(move || FontRenderer8x8::default()); + let command_executor = CommandExecutor::new(&display, &luma, font_renderer); std::thread::scope(move |scope| { scope.spawn(move || { @@ -117,29 +67,45 @@ fn main() { None => continue, }; - match command_executor.execute(command) { - ExecutionResult::Success => { - event_proxy - .send_event(AppEvents::UdpPacketHandled) - .expect("could not send packet handled event"); - } - ExecutionResult::Failure => { - error!("failed to execute command"); - } - ExecutionResult::Shutdown => { - event_proxy - .send_event(AppEvents::UdpThreadClosed) - .expect("could not send close event"); - } - } + handle_command(&event_proxy, &command_executor, command); } }); event_loop - .run_app(&mut app) + .run_app(&mut gui) .expect("could not run event loop"); }); } +fn handle_command(event_proxy: &EventLoopProxy, command_executor: &CommandExecutor, command: Command) { + match command_executor.execute(command) { + ExecutionResult::Success => { + event_proxy + .send_event(AppEvents::UdpPacketHandled) + .expect("could not send packet handled event"); + } + ExecutionResult::Failure => { + error!("failed to execute command"); + } + ExecutionResult::Shutdown => { + event_proxy + .send_event(AppEvents::UdpThreadClosed) + .expect("could not send close event"); + } + } +} + +fn init_logging(debug: bool) { + let filter = if debug { + LevelFilter::Debug + } else { + LevelFilter::Info + }; + env_logger::builder() + .filter_level(filter) + .parse_default_env() + .init(); +} + fn command_from_slice(slice: &[u8]) -> Option { let package = match servicepoint::Packet::try_from(slice) { Err(_) => {