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,
130 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
131}
132
133impl QRCode {
134 pub fn new() -> Self {
136 glib::Object::new()
137 }
138
139 pub fn from_bytes(bytes: &[u8]) -> Self {
141 let qrcode = Self::new();
142 qrcode.set_bytes(bytes);
143
144 qrcode
145 }
146
147 pub fn set_bytes(&self, bytes: &[u8]) {
149 let data = QRCodeData::try_from(bytes).unwrap_or_else(|_| {
150 glib::g_warning!(None, "Could not load QRCode from bytes");
151 Default::default()
152 });
153 self.imp().data.replace(data);
154
155 self.queue_draw();
156 self.queue_resize();
157 }
158
159 pub fn set_qrcode(&self, qrcode: qrcode::QrCode) {
161 self.imp().data.replace(QRCodeData::from(qrcode));
162
163 self.queue_draw();
164 self.queue_resize();
165 }
166}
167
168impl Default for QRCodeData {
169 fn default() -> Self {
170 Self::try_from("".as_bytes()).unwrap()
171 }
172}
173
174#[derive(Debug, Clone)]
175pub struct QRCodeData {
176 pub width: i32,
177 pub height: i32,
178 pub items: Vec<Vec<bool>>,
179}
180
181impl TryFrom<&[u8]> for QRCodeData {
182 type Error = qrcode::types::QrError;
183
184 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
185 Ok(qrcode::QrCode::new(data)?.into())
186 }
187}
188
189impl From<qrcode::QrCode> for QRCodeData {
190 fn from(code: qrcode::QrCode) -> Self {
191 let items = code
192 .render::<char>()
193 .quiet_zone(false)
194 .module_dimensions(1, 1)
195 .build()
196 .split('\n')
197 .map(|line| {
198 line.chars()
199 .map(|c| !c.is_whitespace())
200 .collect::<Vec<bool>>()
201 })
202 .collect::<Vec<Vec<bool>>>();
203
204 let size = items
205 .len()
206 .try_into()
207 .expect("count of items fits into i32");
208 Self {
209 width: size,
210 height: size,
211 items,
212 }
213 }
214}