1use crate::Renderer;
24use crate::canvas;
25use crate::core::layout;
26use crate::core::mouse;
27use crate::core::renderer::{self, Renderer as _};
28use crate::core::widget;
29use crate::core::widget::operation::accessible::{Accessible, Role};
30use crate::core::widget::tree::{self, Tree};
31use crate::core::{
32 Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme, Vector, Widget,
33};
34
35use std::cell::RefCell;
36use thiserror::Error;
37
38const DEFAULT_CELL_SIZE: f32 = 4.0;
39const QUIET_ZONE: usize = 2;
40
41pub struct QRCode<'a, Theme = crate::Theme>
65where
66 Theme: Catalog,
67{
68 data: &'a Data,
69 cell_size: f32,
70 class: Theme::Class<'a>,
71}
72
73impl<'a, Theme> QRCode<'a, Theme>
74where
75 Theme: Catalog,
76{
77 pub fn new(data: &'a Data) -> Self {
79 Self {
80 data,
81 cell_size: DEFAULT_CELL_SIZE,
82 class: Theme::default(),
83 }
84 }
85
86 pub fn cell_size(mut self, cell_size: impl Into<Pixels>) -> Self {
88 self.cell_size = cell_size.into().0;
89 self
90 }
91
92 pub fn total_size(mut self, total_size: impl Into<Pixels>) -> Self {
94 self.cell_size = total_size.into().0 / (self.data.width + 2 * QUIET_ZONE) as f32;
95
96 self
97 }
98
99 #[must_use]
101 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
102 where
103 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
104 {
105 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
106 self
107 }
108
109 #[cfg(feature = "advanced")]
111 #[must_use]
112 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
113 self.class = class.into();
114 self
115 }
116}
117
118impl<Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'_, Theme>
119where
120 Theme: Catalog,
121{
122 fn tag(&self) -> tree::Tag {
123 tree::Tag::of::<State>()
124 }
125
126 fn state(&self) -> tree::State {
127 tree::State::new(State::default())
128 }
129
130 fn size(&self) -> Size<Length> {
131 Size {
132 width: Length::Shrink,
133 height: Length::Shrink,
134 }
135 }
136
137 fn layout(
138 &mut self,
139 _tree: &mut Tree,
140 _renderer: &Renderer,
141 _limits: &layout::Limits,
142 ) -> layout::Node {
143 let side_length = (self.data.width + 2 * QUIET_ZONE) as f32 * self.cell_size;
144
145 layout::Node::new(Size::new(side_length, side_length))
146 }
147
148 fn draw(
149 &self,
150 tree: &Tree,
151 renderer: &mut Renderer,
152 theme: &Theme,
153 _style: &renderer::Style,
154 layout: Layout<'_>,
155 _cursor: mouse::Cursor,
156 _viewport: &Rectangle,
157 ) {
158 let state = tree.state.downcast_ref::<State>();
159
160 let bounds = layout.bounds();
161 let side_length = self.data.width + 2 * QUIET_ZONE;
162
163 let style = theme.style(&self.class);
164 let mut last_style = state.last_style.borrow_mut();
165
166 if Some(style) != *last_style {
167 self.data.cache.clear();
168
169 *last_style = Some(style);
170 }
171
172 let geometry = self.data.cache.draw(renderer, bounds.size(), |frame| {
174 frame.scale(self.cell_size);
176
177 frame.fill_rectangle(
179 Point::ORIGIN,
180 Size::new(side_length as f32, side_length as f32),
181 style.background,
182 );
183
184 frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
186
187 self.data
189 .contents
190 .iter()
191 .enumerate()
192 .filter(|(_, value)| **value == qrcode::Color::Dark)
193 .for_each(|(index, _)| {
194 let row = index / self.data.width;
195 let column = index % self.data.width;
196
197 frame.fill_rectangle(
198 Point::new(column as f32, row as f32),
199 Size::UNIT,
200 style.cell,
201 );
202 });
203 });
204
205 renderer.with_translation(bounds.position() - Point::ORIGIN, |renderer| {
206 use crate::graphics::geometry::Renderer as _;
207
208 renderer.draw_geometry(geometry);
209 });
210 }
211
212 fn operate(
213 &mut self,
214 _tree: &mut Tree,
215 layout: Layout<'_>,
216 _renderer: &Renderer,
217 operation: &mut dyn widget::Operation,
218 ) {
219 operation.accessible(
220 None,
221 layout.bounds(),
222 &Accessible {
223 role: Role::Image,
224 ..Accessible::default()
225 },
226 );
227 }
228}
229
230impl<'a, Message, Theme> From<QRCode<'a, Theme>> for Element<'a, Message, Theme, Renderer>
231where
232 Theme: Catalog + 'a,
233{
234 fn from(qr_code: QRCode<'a, Theme>) -> Self {
235 Self::new(qr_code)
236 }
237}
238
239#[derive(Debug)]
243pub struct Data {
244 contents: Vec<qrcode::Color>,
245 width: usize,
246 cache: canvas::Cache<Renderer>,
247}
248
249impl Data {
250 pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> {
255 let encoded = qrcode::QrCode::new(data)?;
256
257 Ok(Self::build(encoded))
258 }
259
260 pub fn with_error_correction(
262 data: impl AsRef<[u8]>,
263 error_correction: ErrorCorrection,
264 ) -> Result<Self, Error> {
265 let encoded = qrcode::QrCode::with_error_correction_level(data, error_correction.into())?;
266
267 Ok(Self::build(encoded))
268 }
269
270 pub fn with_version(
273 data: impl AsRef<[u8]>,
274 version: Version,
275 error_correction: ErrorCorrection,
276 ) -> Result<Self, Error> {
277 let encoded = qrcode::QrCode::with_version(data, version.into(), error_correction.into())?;
278
279 Ok(Self::build(encoded))
280 }
281
282 fn build(encoded: qrcode::QrCode) -> Self {
283 let width = encoded.width();
284 let contents = encoded.into_colors();
285
286 Self {
287 contents,
288 width,
289 cache: canvas::Cache::new(),
290 }
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub enum Version {
300 Normal(u8),
302
303 Micro(u8),
305}
306
307impl From<Version> for qrcode::Version {
308 fn from(version: Version) -> Self {
309 match version {
310 Version::Normal(v) => qrcode::Version::Normal(i16::from(v)),
311 Version::Micro(v) => qrcode::Version::Micro(i16::from(v)),
312 }
313 }
314}
315
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323pub enum ErrorCorrection {
324 Low,
326 Medium,
328 Quartile,
330 High,
332}
333
334impl From<ErrorCorrection> for qrcode::EcLevel {
335 fn from(ec_level: ErrorCorrection) -> Self {
336 match ec_level {
337 ErrorCorrection::Low => qrcode::EcLevel::L,
338 ErrorCorrection::Medium => qrcode::EcLevel::M,
339 ErrorCorrection::Quartile => qrcode::EcLevel::Q,
340 ErrorCorrection::High => qrcode::EcLevel::H,
341 }
342 }
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
347pub enum Error {
348 #[error("The data is too long to encode in a QR code for the chosen version")]
350 DataTooLong,
351
352 #[error("The chosen version and error correction level combination is invalid.")]
354 InvalidVersion,
355
356 #[error(
359 "One or more characters in the provided data are not supported by the \
360 chosen version"
361 )]
362 UnsupportedCharacterSet,
363
364 #[error(
367 "The chosen ECI designator is invalid. A valid designator should be \
368 between 0 and 999999."
369 )]
370 InvalidEciDesignator,
371
372 #[error("A character that does not belong to the character set was found")]
374 InvalidCharacter,
375}
376
377impl From<qrcode::types::QrError> for Error {
378 fn from(error: qrcode::types::QrError) -> Self {
379 use qrcode::types::QrError;
380
381 match error {
382 QrError::DataTooLong => Error::DataTooLong,
383 QrError::InvalidVersion => Error::InvalidVersion,
384 QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet,
385 QrError::InvalidEciDesignator => Error::InvalidEciDesignator,
386 QrError::InvalidCharacter => Error::InvalidCharacter,
387 }
388 }
389}
390
391#[derive(Default)]
392struct State {
393 last_style: RefCell<Option<Style>>,
394}
395
396#[derive(Debug, Clone, Copy, PartialEq)]
398pub struct Style {
399 pub cell: Color,
401 pub background: Color,
403}
404
405pub trait Catalog {
407 type Class<'a>;
409
410 fn default<'a>() -> Self::Class<'a>;
412
413 fn style(&self, class: &Self::Class<'_>) -> Style;
415}
416
417pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
419
420impl Catalog for Theme {
421 type Class<'a> = StyleFn<'a, Self>;
422
423 fn default<'a>() -> Self::Class<'a> {
424 Box::new(default)
425 }
426
427 fn style(&self, class: &Self::Class<'_>) -> Style {
428 class(self)
429 }
430}
431
432pub fn default(theme: &Theme) -> Style {
434 let palette = theme.seed();
435
436 Style {
437 cell: palette.text,
438 background: palette.background,
439 }
440}