split out gui options

This commit is contained in:
Vinzenz Schroeter 2025-01-26 12:40:59 +01:00
parent 31a8f6a40a
commit 3049a8bd7a
6 changed files with 143 additions and 120 deletions

57
src/cli.rs Normal file
View file

@ -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<String>,
#[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,
}

View file

@ -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)
}

View file

@ -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<Bitmap>,
luma: &'t RwLock<BrightnessGrid>,
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<Bitmap>,
luma: &'t RwLock<BrightnessGrid>,
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),

View file

@ -43,12 +43,13 @@ pub enum RenderError {
}
impl FontRenderer8x8 {
pub fn new(font: Font, fallback_char: Option<char>) -> 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)
}
}

View file

@ -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<Bitmap>,
luma: &'t RwLock<BrightnessGrid>,
window: Option<Window>,
stop_udp_tx: Sender<()>,
cli: &'t Cli,
options: GuiOptions,
logical_size: LogicalSize<u16>,
}
@ -41,15 +41,15 @@ impl<'t> Gui<'t> {
display: &'t RwLock<Bitmap>,
luma: &'t RwLock<BrightnessGrid>,
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,
]
}

View file

@ -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<AppEvents>, 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<Command> {
let package = match servicepoint::Packet::try_from(slice) {
Err(_) => {