mirror of
https://github.com/kaesaecracker/servicepoint-simulator.git
synced 2025-01-30 17:20:13 +01:00
split out gui options
This commit is contained in:
parent
31a8f6a40a
commit
3049a8bd7a
57
src/cli.rs
Normal file
57
src/cli.rs
Normal 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,
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
20
src/gui.rs
20
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<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,
|
||||
]
|
||||
}
|
||||
|
|
126
src/main.rs
126
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<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(_) => {
|
||||
|
|
Loading…
Reference in a new issue