egui_qr/
lib.rs

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}