1use egui::{Color32, CornerRadius, Rect, Response, Sense, Stroke, StrokeKind, Ui, Widget, vec2};
2use qrcode::types::QrError;
3use qrcode::{Color, QrCode};
4
5enum CodeSrc<'a> {
6 Ref(&'a QrCode),
7 Owned(QrCode),
8}
9
10pub struct QrCodeWidget<'a> {
11 code: CodeSrc<'a>,
12 quiet_zone: f32,
13}
14
15impl<'a> QrCodeWidget<'a> {
16 pub fn new(code: &'a QrCode) -> Self {
17 QrCodeWidget {
18 code: CodeSrc::Ref(code),
19 quiet_zone: 1.,
20 }
21 }
22
23 pub fn from_data(data: &[u8]) -> Result<Self, QrError> {
24 let code = QrCode::new(data)?;
25 Ok(QrCodeWidget {
26 code: CodeSrc::Owned(code),
27 quiet_zone: 1.,
28 })
29 }
30
31 pub fn quiet_zone(mut self, quiet_zone: f32) -> Self {
32 self.quiet_zone = quiet_zone;
33 self
34 }
35}
36
37impl Widget for QrCodeWidget<'_> {
38 fn ui(self, ui: &mut Ui) -> Response {
39 let outer_size = ui.available_size();
40
41 let code_ref = match &self.code {
42 CodeSrc::Ref(code) => code,
43 CodeSrc::Owned(q) => q,
44 };
45 let w = code_ref.width();
46 let min_size = outer_size.x.min(outer_size.y);
47 let scale = min_size / (w as f32 + (self.quiet_zone * 2.));
48 let start = ui.cursor().min + vec2(self.quiet_zone * scale, self.quiet_zone * scale);
49 let (response, painter) = ui.allocate_painter(vec2(min_size, min_size), Sense::click());
50
51 painter.rect(response.rect, CornerRadius::ZERO, Color32::WHITE, Stroke::NONE, StrokeKind::Inside);
52 let mut ctr = 0;
53 for c in code_ref.to_colors() {
54 let row = ctr / w;
55 let col = ctr % w;
56 let c_start = start.floor() + vec2(col as f32 * scale, row as f32 * scale).floor();
57 let c_end = c_start.ceil() + vec2(scale, scale).ceil();
58 if matches!(c, Color::Dark) {
59 painter.rect(
60 Rect::from_min_max(c_start, c_end),
61 CornerRadius::ZERO,
62 match c {
63 Color::Light => Color32::WHITE,
64 Color::Dark => Color32::BLACK,
65 },
66 Stroke::NONE,
67 StrokeKind::Inside
68 );
69 }
70 ctr += 1;
71 }
72 response
73 }
74}