1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use gtk::{gdk, glib, graphene, prelude::*, subclass::prelude::*};

#[allow(clippy::upper_case_acronyms)]
pub struct QRCodeData {
    pub width: i32,
    pub height: i32,
    pub items: Vec<Vec<bool>>,
}

impl<B: AsRef<[u8]>> From<B> for QRCodeData {
    fn from(data: B) -> Self {
        let code = qrencode::QrCode::new(data).unwrap();
        let items = code
            .render::<char>()
            .quiet_zone(false)
            .module_dimensions(1, 1)
            .build()
            .split('\n')
            .map(|line| {
                line.chars()
                    .map(|c| !c.is_whitespace())
                    .collect::<Vec<bool>>()
            })
            .collect::<Vec<Vec<bool>>>();

        let width = items.first().unwrap().len() as i32;
        let height = items.len() as i32;
        Self {
            width,
            height,
            items,
        }
    }
}

mod imp {
    use std::cell::RefCell;

    use super::*;

    #[allow(clippy::upper_case_acronyms)]
    #[derive(Default)]
    pub struct QRCodePaintable {
        pub qrcode: RefCell<Option<QRCodeData>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for QRCodePaintable {
        const NAME: &'static str = "QRCodePaintable";
        type Type = super::QRCodePaintable;
        type Interfaces = (gdk::Paintable,);
    }

    impl ObjectImpl for QRCodePaintable {}
    impl PaintableImpl for QRCodePaintable {
        fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) {
            if let Some(ref qrcode) = *self.qrcode.borrow() {
                let padding_squares = 3.max(qrcode.height / 10);
                let square_height = height as f32 / (qrcode.height + 2 * padding_squares) as f32;
                let square_width = width as f32 / (qrcode.width + 2 * padding_squares) as f32;
                let padding = square_height * padding_squares as f32;

                let rect = graphene::Rect::new(0.0, 0.0, width as f32, height as f32);
                snapshot.append_color(&gdk::RGBA::WHITE, &rect);
                qrcode.items.iter().enumerate().for_each(|(y, line)| {
                    line.iter().enumerate().for_each(|(x, is_dark)| {
                        if *is_dark {
                            let mut black = gdk::RGBA::BLACK;
                            black.set_alpha(0.85);

                            let position = graphene::Rect::new(
                                (x as f32) * square_width + padding,
                                (y as f32) * square_height + padding,
                                square_width,
                                square_height,
                            );

                            snapshot.append_color(&black, &position);
                        };
                    });
                });
            }
        }
    }
}

glib::wrapper! {
    pub struct QRCodePaintable(ObjectSubclass<imp::QRCodePaintable>)
        @implements gdk::Paintable;
}

impl QRCodePaintable {
    pub fn set_qrcode(&self, qrcode: QRCodeData) {
        self.imp().qrcode.replace(Some(qrcode));
        self.invalidate_contents();
    }
}

impl Default for QRCodePaintable {
    fn default() -> Self {
        glib::Object::new()
    }
}