Complete UI for desktop transmission.

This is now a working version of a desktop transmission program; it can't yet receive data. The code
is more than a little ugly, though, so it's not done by any means, it's just that it works.
This commit is contained in:
Joe Ardent 2023-08-07 12:41:54 -07:00
parent c2160b2218
commit 02d5e57eed
3 changed files with 112 additions and 28 deletions

View file

@ -1,38 +1,122 @@
use eframe::egui; use std::{
use egui_extras::RetainedImage; 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}; 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 { 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); 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| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading(&self.heading); ui.heading(&self.heading);
let img = match self.content { match self.content {
Content::Static(bytes) => { Content::Static(bytes) => {
RetainedImage::from_image_bytes("generated qr code", &mk_qr_bytes(bytes)) let img = RetainedImage::from_image_bytes(
.unwrap() "generated qr code",
} &mk_qr_bytes(bytes, static_height),
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),
) )
.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 => { 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( RetainedImage::from_image_bytes(
"generated qr code", "new packet",
&mk_qr_bytes(bytes), &mk_qr_bytes(&bytes, streaming_height),
) )
.unwrap() .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<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);
});
});
}

View file

@ -11,8 +11,8 @@ mod util;
pub use util::{mk_qr_bytes, stream_bytes}; pub use util::{mk_qr_bytes, stream_bytes};
pub type Receiver = std::sync::mpsc::Receiver<(usize, Vec<u8>)>; pub type CuttleSender = std::sync::mpsc::SyncSender<Vec<u8>>;
pub type Sender = std::sync::mpsc::SyncSender<(usize, Vec<u8>)>; pub type CuttleReceiver = std::sync::mpsc::Receiver<Vec<u8>>;
/// The application state /// The application state
#[derive(Debug)] #[derive(Debug)]
@ -32,9 +32,9 @@ pub enum Content {
#[derive(Debug)] #[derive(Debug)]
pub struct StreamedContent { pub struct StreamedContent {
pub txconfig: &'static [u8], pub txconfig: &'static [u8],
pub rx: Receiver, pub rx: CuttleReceiver,
pub status: StreamStatus, pub status: StreamStatus,
pub last_packet: Option<usize>, pub last_packet: Option<Vec<u8>>,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]

View file

@ -2,11 +2,11 @@ use fast_qr::convert::image::ImageBuilder;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use raptorq::{Encoder, ObjectTransmissionInformation}; use raptorq::{Encoder, ObjectTransmissionInformation};
use crate::{Sender, TxConfig}; use crate::{CuttleSender, TxConfig};
pub const STREAMING_MTU: u16 = 2326; pub const STREAMING_MTU: u16 = 2326;
pub fn stream_bytes(bytes: Vec<u8>, tx: Sender, desc: String) -> Vec<u8> { pub fn stream_bytes(bytes: Vec<u8>, tx: CuttleSender, desc: String) -> Vec<u8> {
let len = bytes.len() as u64; let len = bytes.len() as u64;
let txconfig = TxConfig { let txconfig = TxConfig {
len, len,
@ -30,22 +30,22 @@ pub fn stream_bytes(bytes: Vec<u8>, tx: Sender, desc: String) -> Vec<u8> {
packets.shuffle(rng); packets.shuffle(rng);
for (counter, packet) in packets.iter().cycle().enumerate() { for packet in packets.iter().cycle() {
tx.send((counter, packet.clone())).unwrap(); tx.send(packet.clone()).unwrap_or_default();
} }
}); });
txconfig txconfig
} }
/// Makes a PNG of a QR code for the given bytes, returns the bytes of the PNG. /// 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<u8> { pub fn mk_qr_bytes(bytes: &[u8], height: f32) -> Vec<u8> {
let qr = fast_qr::QRBuilder::new(bytes) let qr = fast_qr::QRBuilder::new(bytes)
.ecl(fast_qr::ECL::M) .ecl(fast_qr::ECL::M)
.build() .build()
.unwrap(); .unwrap();
ImageBuilder::default() ImageBuilder::default()
.fit_width(1100) .fit_width(height as u32)
.to_pixmap(&qr) .to_pixmap(&qr)
.encode_png() .encode_png()
.unwrap() .unwrap()