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::tree::{self, Tree};
29use crate::core::{
30 Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme,
31 Vector, Widget,
32};
33
34use std::cell::RefCell;
35use thiserror::Error;
36
37const DEFAULT_CELL_SIZE: f32 = 4.0;
38const QUIET_ZONE: usize = 2;
39
40pub struct QRCode<'a, Theme = crate::Theme>
64where
65 Theme: Catalog,
66{
67 data: &'a Data,
68 cell_size: f32,
69 class: Theme::Class<'a>,
70}
71
72impl<'a, Theme> QRCode<'a, Theme>
73where
74 Theme: Catalog,
75{
76 pub fn new(data: &'a Data) -> Self {
78 Self {
79 data,
80 cell_size: DEFAULT_CELL_SIZE,
81 class: Theme::default(),
82 }
83 }
84
85 pub fn cell_size(mut self, cell_size: impl Into<Pixels>) -> Self {
87 self.cell_size = cell_size.into().0;
88 self
89 }
90
91 pub fn total_size(mut self, total_size: impl Into<Pixels>) -> Self {
93 self.cell_size =
94 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 =
144 (self.data.width + 2 * QUIET_ZONE) as f32 * self.cell_size;
145
146 layout::Node::new(Size::new(side_length, side_length))
147 }
148
149 fn draw(
150 &self,
151 tree: &Tree,
152 renderer: &mut Renderer,
153 theme: &Theme,
154 _style: &renderer::Style,
155 layout: Layout<'_>,
156 _cursor: mouse::Cursor,
157 _viewport: &Rectangle,
158 ) {
159 let state = tree.state.downcast_ref::<State>();
160
161 let bounds = layout.bounds();
162 let side_length = self.data.width + 2 * QUIET_ZONE;
163
164 let style = theme.style(&self.class);
165 let mut last_style = state.last_style.borrow_mut();
166
167 if Some(style) != *last_style {
168 self.data.cache.clear();
169
170 *last_style = Some(style);
171 }
172
173 let geometry = self.data.cache.draw(renderer, bounds.size(), |frame| {
175 frame.scale(self.cell_size);
177
178 frame.fill_rectangle(
180 Point::ORIGIN,
181 Size::new(side_length as f32, side_length as f32),
182 style.background,
183 );
184
185 frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
187
188 self.data
190 .contents
191 .iter()
192 .enumerate()
193 .filter(|(_, value)| **value == qrcode::Color::Dark)
194 .for_each(|(index, _)| {
195 let row = index / self.data.width;
196 let column = index % self.data.width;
197
198 frame.fill_rectangle(
199 Point::new(column as f32, row as f32),
200 Size::UNIT,
201 style.cell,
202 );
203 });
204 });
205
206 renderer.with_translation(
207 bounds.position() - Point::ORIGIN,
208 |renderer| {
209 use crate::graphics::geometry::Renderer as _;
210
211 renderer.draw_geometry(geometry);
212 },
213 );
214 }
215}
216
217impl<'a, Message, Theme> From<QRCode<'a, Theme>>
218 for Element<'a, Message, Theme, Renderer>
219where
220 Theme: Catalog + 'a,
221{
222 fn from(qr_code: QRCode<'a, Theme>) -> Self {
223 Self::new(qr_code)
224 }
225}
226
227#[derive(Debug)]
231pub struct Data {
232 contents: Vec<qrcode::Color>,
233 width: usize,
234 cache: canvas::Cache<Renderer>,
235}
236
237impl Data {
238 pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> {
243 let encoded = qrcode::QrCode::new(data)?;
244
245 Ok(Self::build(encoded))
246 }
247
248 pub fn with_error_correction(
250 data: impl AsRef<[u8]>,
251 error_correction: ErrorCorrection,
252 ) -> Result<Self, Error> {
253 let encoded = qrcode::QrCode::with_error_correction_level(
254 data,
255 error_correction.into(),
256 )?;
257
258 Ok(Self::build(encoded))
259 }
260
261 pub fn with_version(
264 data: impl AsRef<[u8]>,
265 version: Version,
266 error_correction: ErrorCorrection,
267 ) -> Result<Self, Error> {
268 let encoded = qrcode::QrCode::with_version(
269 data,
270 version.into(),
271 error_correction.into(),
272 )?;
273
274 Ok(Self::build(encoded))
275 }
276
277 fn build(encoded: qrcode::QrCode) -> Self {
278 let width = encoded.width();
279 let contents = encoded.into_colors();
280
281 Self {
282 contents,
283 width,
284 cache: canvas::Cache::new(),
285 }
286 }
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub enum Version {
295 Normal(u8),
297
298 Micro(u8),
300}
301
302impl From<Version> for qrcode::Version {
303 fn from(version: Version) -> Self {
304 match version {
305 Version::Normal(v) => qrcode::Version::Normal(i16::from(v)),
306 Version::Micro(v) => qrcode::Version::Micro(i16::from(v)),
307 }
308 }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
318pub enum ErrorCorrection {
319 Low,
321 Medium,
323 Quartile,
325 High,
327}
328
329impl From<ErrorCorrection> for qrcode::EcLevel {
330 fn from(ec_level: ErrorCorrection) -> Self {
331 match ec_level {
332 ErrorCorrection::Low => qrcode::EcLevel::L,
333 ErrorCorrection::Medium => qrcode::EcLevel::M,
334 ErrorCorrection::Quartile => qrcode::EcLevel::Q,
335 ErrorCorrection::High => qrcode::EcLevel::H,
336 }
337 }
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
342pub enum Error {
343 #[error(
345 "The data is too long to encode in a QR code for the chosen version"
346 )]
347 DataTooLong,
348
349 #[error(
351 "The chosen version and error correction level combination is invalid."
352 )]
353 InvalidVersion,
354
355 #[error(
358 "One or more characters in the provided data are not supported by the \
359 chosen version"
360 )]
361 UnsupportedCharacterSet,
362
363 #[error(
366 "The chosen ECI designator is invalid. A valid designator should be \
367 between 0 and 999999."
368 )]
369 InvalidEciDesignator,
370
371 #[error("A character that does not belong to the character set was found")]
373 InvalidCharacter,
374}
375
376impl From<qrcode::types::QrError> for Error {
377 fn from(error: qrcode::types::QrError) -> Self {
378 use qrcode::types::QrError;
379
380 match error {
381 QrError::DataTooLong => Error::DataTooLong,
382 QrError::InvalidVersion => Error::InvalidVersion,
383 QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet,
384 QrError::InvalidEciDesignator => Error::InvalidEciDesignator,
385 QrError::InvalidCharacter => Error::InvalidCharacter,
386 }
387 }
388}
389
390#[derive(Default)]
391struct State {
392 last_style: RefCell<Option<Style>>,
393}
394
395#[derive(Debug, Clone, Copy, PartialEq)]
397pub struct Style {
398 pub cell: Color,
400 pub background: Color,
402}
403
404pub trait Catalog {
406 type Class<'a>;
408
409 fn default<'a>() -> Self::Class<'a>;
411
412 fn style(&self, class: &Self::Class<'_>) -> Style;
414}
415
416pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
418
419impl Catalog for Theme {
420 type Class<'a> = StyleFn<'a, Self>;
421
422 fn default<'a>() -> Self::Class<'a> {
423 Box::new(default)
424 }
425
426 fn style(&self, class: &Self::Class<'_>) -> Style {
427 class(self)
428 }
429}
430
431pub fn default(theme: &Theme) -> Style {
433 let palette = theme.palette();
434
435 Style {
436 cell: palette.text,
437 background: palette.background,
438 }
439}