fractal/contrib/
qr_code.rs1use gettextrs::gettext;
5use gtk::{glib, prelude::*, subclass::prelude::*};
6
7pub(crate) mod imp {
8 use std::cell::{Cell, RefCell};
9
10 use gtk::{gdk, graphene};
11
12 use super::*;
13
14 #[derive(Debug, glib::Properties)]
15 #[properties(wrapper_type = super::QRCode)]
16 pub struct QRCode {
17 pub data: RefCell<QRCodeData>,
18 #[property(get, set = Self::set_block_size)]
22 pub block_size: Cell<u32>,
23 }
24
25 impl Default for QRCode {
26 fn default() -> Self {
27 Self {
28 data: Default::default(),
29 block_size: Cell::new(6),
30 }
31 }
32 }
33
34 #[glib::object_subclass]
35 impl ObjectSubclass for QRCode {
36 const NAME: &'static str = "TriQRCode";
37 type Type = super::QRCode;
38 type ParentType = gtk::Widget;
39
40 fn class_init(klass: &mut Self::Class) {
41 klass.set_css_name("qrcode");
42 klass.set_accessible_role(gtk::AccessibleRole::Img);
43 }
44 }
45
46 #[glib::derived_properties]
47 impl ObjectImpl for QRCode {
48 fn constructed(&self) {
49 self.parent_constructed();
50
51 self.obj()
52 .update_property(&[gtk::accessible::Property::Label(&gettext("QR Code"))]);
53 }
54 }
55
56 impl WidgetImpl for QRCode {
57 fn snapshot(&self, snapshot: >k::Snapshot) {
58 let obj = self.obj();
59 let square_width = obj.width() as f32 / self.data.borrow().width as f32;
60 let square_height = obj.height() as f32 / self.data.borrow().height as f32;
61
62 self.data
63 .borrow()
64 .items
65 .iter()
66 .enumerate()
67 .for_each(|(y, line)| {
68 line.iter().enumerate().for_each(|(x, is_dark)| {
69 let color = if *is_dark {
70 gdk::RGBA::BLACK
71 } else {
72 gdk::RGBA::WHITE
73 };
74 let position = graphene::Rect::new(
75 (x as f32) * square_width,
76 (y as f32) * square_height,
77 square_width,
78 square_height,
79 );
80
81 snapshot.append_color(&color, &position);
82 });
83 });
84 }
85
86 fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
87 let stride = i32::try_from(self.obj().block_size()).expect("block size fits into i32");
88
89 let minimum = match orientation {
90 gtk::Orientation::Horizontal => self.data.borrow().width * stride,
91 gtk::Orientation::Vertical => self.data.borrow().height * stride,
92 _ => unreachable!(),
93 };
94 let natural = std::cmp::max(for_size, minimum);
95 (minimum, natural, -1, -1)
96 }
97 }
98
99 impl QRCode {
100 fn set_block_size(&self, block_size: u32) {
102 self.block_size.set(std::cmp::max(block_size, 1));
103
104 let obj = self.obj();
105 obj.queue_draw();
106 obj.queue_resize();
107 }
108 }
109}
110
111glib::wrapper! {
112 pub struct QRCode(ObjectSubclass<imp::QRCode>)
129 @extends gtk::Widget, @implements gtk::Accessible;
130}
131
132impl QRCode {
133 pub fn new() -> Self {
135 glib::Object::new()
136 }
137
138 pub fn from_bytes(bytes: &[u8]) -> Self {
140 let qrcode = Self::new();
141 qrcode.set_bytes(bytes);
142
143 qrcode
144 }
145
146 pub fn set_bytes(&self, bytes: &[u8]) {
148 let data = QRCodeData::try_from(bytes).unwrap_or_else(|_| {
149 glib::g_warning!(None, "Could not load QRCode from bytes");
150 Default::default()
151 });
152 self.imp().data.replace(data);
153
154 self.queue_draw();
155 self.queue_resize();
156 }
157
158 pub fn set_qrcode(&self, qrcode: qrcode::QrCode) {
160 self.imp().data.replace(QRCodeData::from(qrcode));
161
162 self.queue_draw();
163 self.queue_resize();
164 }
165}
166
167impl Default for QRCodeData {
168 fn default() -> Self {
169 Self::try_from("".as_bytes()).unwrap()
170 }
171}
172
173#[derive(Debug, Clone)]
174pub struct QRCodeData {
175 pub width: i32,
176 pub height: i32,
177 pub items: Vec<Vec<bool>>,
178}
179
180impl TryFrom<&[u8]> for QRCodeData {
181 type Error = qrcode::types::QrError;
182
183 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
184 Ok(qrcode::QrCode::new(data)?.into())
185 }
186}
187
188impl From<qrcode::QrCode> for QRCodeData {
189 fn from(code: qrcode::QrCode) -> Self {
190 let items = code
191 .render::<char>()
192 .quiet_zone(false)
193 .module_dimensions(1, 1)
194 .build()
195 .split('\n')
196 .map(|line| {
197 line.chars()
198 .map(|c| !c.is_whitespace())
199 .collect::<Vec<bool>>()
200 })
201 .collect::<Vec<Vec<bool>>>();
202
203 let size = items
204 .len()
205 .try_into()
206 .expect("count of items fits into i32");
207 Self {
208 width: size,
209 height: size,
210 items,
211 }
212 }
213}