diff --git a/src/desktop.rs b/src/desktop.rs index 491d7a6..aebac08 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,38 +1,122 @@ -use eframe::egui; -use egui_extras::RetainedImage; +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) { + 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); - let img = match self.content { + match self.content { Content::Static(bytes) => { - RetainedImage::from_image_bytes("generated qr code", &mk_qr_bytes(bytes)) - .unwrap() - } - Content::Streamed(ref streaming_content) => match streaming_content.status { - StreamStatus::Paused => RetainedImage::from_image_bytes( - "tx config for receiver initialization", - &mk_qr_bytes(streaming_content.txconfig), + let img = RetainedImage::from_image_bytes( + "generated qr code", + &mk_qr_bytes(bytes, static_height), ) - .unwrap(), + .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 => { - if let Ok((_counter, ref bytes)) = streaming_content.rx.try_recv() { + 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( - "generated qr code", - &mk_qr_bytes(bytes), + "new packet", + &mk_qr_bytes(&bytes, streaming_height), ) .unwrap() - } else { - return; + }; + + 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; } } }, }; - img.show(ui); + // 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, + 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); + }); + }); +} diff --git a/src/lib.rs b/src/lib.rs index 4a8664e..a1af47e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,8 @@ mod util; pub use util::{mk_qr_bytes, stream_bytes}; -pub type Receiver = std::sync::mpsc::Receiver<(usize, Vec)>; -pub type Sender = std::sync::mpsc::SyncSender<(usize, Vec)>; +pub type CuttleSender = std::sync::mpsc::SyncSender>; +pub type CuttleReceiver = std::sync::mpsc::Receiver>; /// The application state #[derive(Debug)] @@ -32,9 +32,9 @@ pub enum Content { #[derive(Debug)] pub struct StreamedContent { pub txconfig: &'static [u8], - pub rx: Receiver, + pub rx: CuttleReceiver, pub status: StreamStatus, - pub last_packet: Option, + pub last_packet: Option>, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/src/util.rs b/src/util.rs index 7d16163..269917e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,11 +2,11 @@ use fast_qr::convert::image::ImageBuilder; use rand::seq::SliceRandom; use raptorq::{Encoder, ObjectTransmissionInformation}; -use crate::{Sender, TxConfig}; +use crate::{CuttleSender, TxConfig}; pub const STREAMING_MTU: u16 = 2326; -pub fn stream_bytes(bytes: Vec, tx: Sender, desc: String) -> Vec { +pub fn stream_bytes(bytes: Vec, tx: CuttleSender, desc: String) -> Vec { let len = bytes.len() as u64; let txconfig = TxConfig { len, @@ -30,22 +30,22 @@ pub fn stream_bytes(bytes: Vec, tx: Sender, desc: String) -> Vec { packets.shuffle(rng); - for (counter, packet) in packets.iter().cycle().enumerate() { - tx.send((counter, packet.clone())).unwrap(); + for packet in packets.iter().cycle() { + tx.send(packet.clone()).unwrap_or_default(); } }); txconfig } /// Makes a PNG of a QR code for the given bytes, returns the bytes of the PNG. -pub fn mk_qr_bytes(bytes: &[u8]) -> Vec { +pub fn mk_qr_bytes(bytes: &[u8], height: f32) -> Vec { let qr = fast_qr::QRBuilder::new(bytes) .ecl(fast_qr::ECL::M) .build() .unwrap(); ImageBuilder::default() - .fit_width(1100) + .fit_width(height as u32) .to_pixmap(&qr) .encode_png() .unwrap()