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:
parent
c2160b2218
commit
02d5e57eed
3 changed files with 112 additions and 28 deletions
120
src/desktop.rs
120
src/desktop.rs
|
@ -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()
|
|
||||||
}
|
|
||||||
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(),
|
|
||||||
StreamStatus::Streaming => {
|
|
||||||
if let Ok((_counter, ref bytes)) = streaming_content.rx.try_recv() {
|
|
||||||
RetainedImage::from_image_bytes(
|
|
||||||
"generated qr code",
|
"generated qr code",
|
||||||
&mk_qr_bytes(bytes),
|
&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()
|
.unwrap()
|
||||||
} else {
|
} else {
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
12
src/util.rs
12
src/util.rs
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue