minor re-org and tidy

This commit is contained in:
Joe Ardent 2023-08-07 14:54:36 -07:00
parent 02d5e57eed
commit c579cc0851
5 changed files with 187 additions and 167 deletions

View file

@ -1,122 +0,0 @@
use std::{
sync::mpsc::{channel, Sender},
time::Instant,
};
use eframe::{
egui::{self, Direction, Layout, RichText, Ui},
epaint::FontId,
};
use egui_extras::{RetainedImage, Size, StripBuilder};
use crate::{mk_qr_bytes, Content, Flasher, StreamStatus};
const TEXT_FACTOR: f32 = 0.05;
const IMG_AREA_PCT: f32 = 0.85;
impl eframe::App for Flasher {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
ctx.request_repaint_after(self.sleep);
let height = ctx.screen_rect().height();
let tsize = (height * TEXT_FACTOR) - 10.0;
let streaming_height = (height * IMG_AREA_PCT) - 50.0;
let static_height = height - 50.0;
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading(&self.heading);
match self.content {
Content::Static(bytes) => {
let img = RetainedImage::from_image_bytes(
"generated qr code",
&mk_qr_bytes(bytes, static_height),
)
.unwrap();
img.show(ui);
}
Content::Streamed(ref mut streaming_content) => match streaming_content.status {
StreamStatus::Paused => {
let img = RetainedImage::from_image_bytes(
"tx config for receiver initialization",
&mk_qr_bytes(streaming_content.txconfig, streaming_height),
)
.unwrap();
let (tx, rx) = channel();
let button = (
"Scan, then click to begin streaming",
StreamStatus::Streaming,
);
streaming_ui(ui, button, tx, img, tsize);
if let Ok(new_status) = rx.try_recv() {
streaming_content.status = new_status;
}
self.last = Instant::now();
}
StreamStatus::Streaming => {
let dur = Instant::now() - self.last;
let img = if dur < self.sleep {
if let Some(bytes) = streaming_content.last_packet.clone() {
RetainedImage::from_image_bytes(
"last packet",
&mk_qr_bytes(&bytes, streaming_height),
)
.unwrap()
} else {
return;
}
} else {
let bytes = streaming_content.rx.recv().unwrap();
self.last = Instant::now();
streaming_content.last_packet = Some(bytes.clone());
RetainedImage::from_image_bytes(
"new packet",
&mk_qr_bytes(&bytes, streaming_height),
)
.unwrap()
};
let (tx, rx) = channel();
let button = ("pause and show txconfig", StreamStatus::Paused);
streaming_ui(ui, button, tx, img, tsize);
if let Ok(new_status) = rx.try_recv() {
streaming_content.status = new_status;
}
}
},
};
// check for quit key
if ui.input(|i| i.key_pressed(egui::Key::Q))
|| ui.input(|i| i.key_pressed(egui::Key::Escape))
{
frame.close();
}
});
}
}
fn streaming_ui(
ui: &mut Ui,
button: (&str, StreamStatus),
sender: Sender<StreamStatus>,
image: RetainedImage,
tsize: f32,
) {
StripBuilder::new(ui)
.size(Size::relative(0.10))
.size(Size::remainder())
.cell_layout(Layout::centered_and_justified(Direction::TopDown))
.vertical(|mut strip| {
strip.strip(|pstrip| {
pstrip.sizes(Size::remainder(), 1).horizontal(|mut pstrip| {
pstrip.cell(|ui| {
let button_text = RichText::new(button.0).font(FontId::monospace(tsize));
if ui.button(button_text.clone()).clicked() {
sender.send(button.1).unwrap();
}
});
});
});
strip.cell(|ui| {
image.show(ui);
});
});
}

133
src/desktop/mod.rs Normal file
View file

@ -0,0 +1,133 @@
use std::{
sync::mpsc::{channel, Sender},
time::{Duration, Instant},
};
use eframe::{
egui::{self, Direction, Layout, RichText, Ui},
epaint::FontId,
};
use egui_extras::{RetainedImage, Size, StripBuilder};
use crate::{mk_qr_bytes, Content, Flasher, StreamStatus, StreamedContent};
const TEXT_FACTOR: f32 = 0.05;
const IMG_AREA_PCT: f32 = 0.85;
struct Button<'text> {
pub text: &'text str,
pub next_state: StreamStatus,
}
impl eframe::App for Flasher {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
ctx.request_repaint_after(self.sleep);
let height = ctx.screen_rect().height();
let tsize = (height * TEXT_FACTOR) - 10.0;
let streaming_height = (height * IMG_AREA_PCT) - 50.0;
let static_height = height - 50.0;
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading(&self.description);
match self.content {
Content::Static(bytes) => {
let img = RetainedImage::from_image_bytes(
"static content",
&mk_qr_bytes(bytes, static_height),
)
.unwrap();
// If it's static, just show the thing, don't set up any other UI or buttons or
// anything
img.show(ui);
}
Content::Streamed(ref mut streamed_content) => match streamed_content.status {
StreamStatus::Paused => {
paused(streamed_content, ui, streaming_height, tsize);
}
StreamStatus::Streaming => {
streaming(streamed_content, ui, streaming_height, tsize, self.sleep);
}
},
}
// check for quit key
if ui.input(|i| i.key_pressed(egui::Key::Q))
|| ui.input(|i| i.key_pressed(egui::Key::Escape))
{
frame.close();
}
});
}
}
fn paused(sc: &mut StreamedContent, ui: &mut Ui, height: f32, tsize: f32) {
let img = RetainedImage::from_image_bytes(
"tx config for receiver initialization",
&mk_qr_bytes(sc.txconfig, height),
)
.unwrap();
let (tx, rx) = channel();
let button = Button {
text: "Scan, then click to begin streaming",
next_state: StreamStatus::Streaming,
};
render_streaming_ui(ui, button, tx, img, tsize);
if let Ok(new_status) = rx.try_recv() {
sc.status = new_status;
}
sc.last_packet_time = Instant::now();
}
fn streaming(sc: &mut StreamedContent, ui: &mut Ui, height: f32, tsize: f32, sleep: Duration) {
let dur = Instant::now() - sc.last_packet_time;
let img = if dur < sleep {
if let Some(bytes) = sc.last_packet.clone() {
RetainedImage::from_image_bytes("last packet", &mk_qr_bytes(&bytes, height)).unwrap()
} else {
return;
}
} else {
let bytes = sc.rx.recv().unwrap();
sc.last_packet = Some(bytes.clone());
sc.last_packet_time = Instant::now();
RetainedImage::from_image_bytes("new packet", &mk_qr_bytes(&bytes, height)).unwrap()
};
let (tx, rx) = channel();
let button = Button {
text: "pause and show txconfig",
next_state: StreamStatus::Paused,
};
render_streaming_ui(ui, button, tx, img, tsize);
if let Ok(new_status) = rx.try_recv() {
sc.status = new_status;
}
}
fn render_streaming_ui(
ui: &mut Ui,
button: Button,
sender: Sender<StreamStatus>,
image: RetainedImage,
tsize: f32,
) {
StripBuilder::new(ui)
.size(Size::relative(0.10))
.size(Size::remainder())
.cell_layout(Layout::centered_and_justified(Direction::TopDown))
.vertical(|mut strip| {
strip.strip(|pstrip| {
pstrip.sizes(Size::remainder(), 1).horizontal(|mut pstrip| {
pstrip.cell(|ui| {
let button_text = RichText::new(button.text).font(FontId::monospace(tsize));
if ui.button(button_text.clone()).clicked() {
sender.send(button.next_state).unwrap();
}
});
});
});
strip.cell(|ui| {
image.show(ui);
});
});
}

View file

@ -9,7 +9,7 @@ use rkyv::{Archive, Deserialize, Serialize};
mod desktop; mod desktop;
mod util; mod util;
pub use util::{mk_qr_bytes, stream_bytes}; pub use util::{get_content, mk_qr_bytes, stream_bytes};
pub type CuttleSender = std::sync::mpsc::SyncSender<Vec<u8>>; pub type CuttleSender = std::sync::mpsc::SyncSender<Vec<u8>>;
pub type CuttleReceiver = std::sync::mpsc::Receiver<Vec<u8>>; pub type CuttleReceiver = std::sync::mpsc::Receiver<Vec<u8>>;
@ -17,10 +17,21 @@ pub type CuttleReceiver = std::sync::mpsc::Receiver<Vec<u8>>;
/// The application state /// The application state
#[derive(Debug)] #[derive(Debug)]
pub struct Flasher { pub struct Flasher {
pub heading: String, pub description: String,
pub content: Content, pub content: Content,
pub sleep: Duration, pub sleep: Duration,
pub last: Instant, }
impl Flasher {
pub fn new(description: String, content: Content, fps: f64) -> Self {
let sleep = 1000.0 / fps;
let sleep = Duration::from_millis(sleep as u64);
Flasher {
description,
content,
sleep,
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -35,6 +46,19 @@ pub struct StreamedContent {
pub rx: CuttleReceiver, pub rx: CuttleReceiver,
pub status: StreamStatus, pub status: StreamStatus,
pub last_packet: Option<Vec<u8>>, pub last_packet: Option<Vec<u8>>,
pub last_packet_time: Instant,
}
impl StreamedContent {
pub fn new(txconfig: &'static [u8], rx: CuttleReceiver) -> Self {
StreamedContent {
txconfig,
rx,
status: StreamStatus::Paused,
last_packet: None,
last_packet_time: Instant::now() - Duration::from_secs(1),
}
}
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]

View file

@ -1,10 +1,7 @@
use std::{ use std::ffi::OsString;
ffi::OsString,
time::{Duration, Instant},
};
use clap::Parser; use clap::Parser;
use cuttle::{stream_bytes, Content, Flasher, StreamStatus, StreamedContent}; use cuttle::{get_content, Flasher};
use eframe::egui; use eframe::egui;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -13,8 +10,8 @@ struct Cli {
#[clap(long, short, help = "File to expose")] #[clap(long, short, help = "File to expose")]
pub file: Option<OsString>, pub file: Option<OsString>,
#[clap(long, help = "Frames per second", default_value_t = 10)] #[clap(long, help = "Frames per second", default_value_t = 10.0)]
pub fps: u64, pub fps: f64,
#[clap( #[clap(
help = "all remaining arguments treated as a string; this string is the whole message if `-f` is not given, otherwise it's an optional description of the file" help = "all remaining arguments treated as a string; this string is the whole message if `-f` is not given, otherwise it's an optional description of the file"
@ -45,18 +42,16 @@ fn main() -> Result<(), eframe::Error> {
"text message".to_string() "text message".to_string()
}; };
let content = get_content(&cli, &description); let bytes = if let Some(ref file) = cli.file {
std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}"))
let sleep = 1000.0 / cli.fps as f64; } else {
let sleep = Duration::from_millis(sleep as u64); cli.text().join(" ").bytes().collect()
let last = Instant::now();
let flasher = Flasher {
heading: description,
content,
sleep,
last,
}; };
let content = get_content(bytes, &description);
let flasher = Flasher::new(description, content, cli.fps);
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1200.0, 1200.0)), initial_window_size: Some(egui::vec2(1200.0, 1200.0)),
active: true, active: true,
@ -70,27 +65,3 @@ fn main() -> Result<(), eframe::Error> {
Box::new(move |_cc| Box::new(flasher)), Box::new(move |_cc| Box::new(flasher)),
) )
} }
fn get_content(cli: &Cli, desc: &str) -> Content {
let bytes = if let Some(ref file) = cli.file {
std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}"))
} else {
cli.text().join(" ").bytes().collect()
};
if bytes.len() < 2000 && fast_qr::QRBuilder::new(bytes.clone()).build().is_ok() {
let bytes = bytes.leak();
Content::Static(bytes)
} else {
let (tx, rx) = std::sync::mpsc::sync_channel(2);
let txconfig = stream_bytes(bytes, tx, desc.to_string()).leak();
let stream = StreamedContent {
txconfig,
rx,
status: StreamStatus::Paused,
last_packet: None,
};
Content::Streamed(stream)
}
}

View file

@ -2,7 +2,7 @@ use fast_qr::convert::image::ImageBuilder;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use raptorq::{Encoder, ObjectTransmissionInformation}; use raptorq::{Encoder, ObjectTransmissionInformation};
use crate::{CuttleSender, TxConfig}; use crate::{Content, CuttleSender, StreamedContent, TxConfig};
pub const STREAMING_MTU: u16 = 2326; pub const STREAMING_MTU: u16 = 2326;
@ -50,3 +50,17 @@ pub fn mk_qr_bytes(bytes: &[u8], height: f32) -> Vec<u8> {
.encode_png() .encode_png()
.unwrap() .unwrap()
} }
/// Turns bytes and a description into either a single QR code, or a stream of
/// them, depending on the size of the input.
pub fn get_content(bytes: Vec<u8>, desc: &str) -> Content {
if bytes.len() < 2000 && fast_qr::QRBuilder::new(bytes.clone()).build().is_ok() {
let bytes = bytes.leak();
Content::Static(bytes)
} else {
let (tx, rx) = std::sync::mpsc::sync_channel(2);
let txconfig = stream_bytes(bytes, tx, desc.to_string()).leak();
let stream = StreamedContent::new(txconfig, rx);
Content::Streamed(stream)
}
}