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, Vector, Widget,
31};
32
33use std::cell::RefCell;
34use thiserror::Error;
35
36const DEFAULT_CELL_SIZE: f32 = 4.0;
37const QUIET_ZONE: usize = 2;
38
39pub struct QRCode<'a, Theme = crate::Theme>
63where
64 Theme: Catalog,
65{
66 data: &'a Data,
67 cell_size: f32,
68 class: Theme::Class<'a>,
69}
70
71impl<'a, Theme> QRCode<'a, Theme>
72where
73 Theme: Catalog,
74{
75 pub fn new(data: &'a Data) -> Self {
77 Self {
78 data,
79 cell_size: DEFAULT_CELL_SIZE,
80 class: Theme::default(),
81 }
82 }
83
84 pub fn cell_size(mut self, cell_size: impl Into<Pixels>) -> Self {
86 self.cell_size = cell_size.into().0;
87 self
88 }
89
90 pub fn total_size(mut self, total_size: impl Into<Pixels>) -> Self {
92 self.cell_size = total_size.into().0 / (self.data.width + 2 * QUIET_ZONE) as f32;
93
94 self
95 }
96
97 #[must_use]
99 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
100 where
101 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
102 {
103 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
104 self
105 }
106
107 #[cfg(feature = "advanced")]
109 #[must_use]
110 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
111 self.class = class.into();
112 self
113 }
114}
115
116impl<Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'_, Theme>
117where
118 Theme: Catalog,
119{
120 fn tag(&self) -> tree::Tag {
121 tree::Tag::of::<State>()
122 }
123
124 fn state(&self) -> tree::State {
125 tree::State::new(State::default())
126 }
127
128 fn size(&self) -> Size<Length> {
129 Size {
130 width: Length::Shrink,
131 height: Length::Shrink,
132 }
133 }
134
135 fn layout(
136 &mut self,
137 _tree: &mut Tree,
138 _renderer: &Renderer,
139 _limits: &layout::Limits,
140 ) -> layout::Node {
141 let side_length = (self.data.width + 2 * QUIET_ZONE) as f32 * self.cell_size;
142
143 layout::Node::new(Size::new(side_length, side_length))
144 }
145
146 fn draw(
147 &self,
148 tree: &Tree,
149 renderer: &mut Renderer,
150 theme: &Theme,
151 _style: &renderer::Style,
152 layout: Layout<'_>,
153 _cursor: mouse::Cursor,
154 _viewport: &Rectangle,
155 ) {
156 let state = tree.state.downcast_ref::<State>();
157
158 let bounds = layout.bounds();
159 let side_length = self.data.width + 2 * QUIET_ZONE;
160
161 let style = theme.style(&self.class);
162 let mut last_style = state.last_style.borrow_mut();
163
164 if Some(style) != *last_style {
165 self.data.cache.clear();
166
167 *last_style = Some(style);
168 }
169
170 let geometry = self.data.cache.draw(renderer, bounds.size(), |frame| {
172 frame.scale(self.cell_size);
174
175 frame.fill_rectangle(
177 Point::ORIGIN,
178 Size::new(side_length as f32, side_length as f32),
179 style.background,
180 );
181
182 frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
184
185 self.data
187 .contents
188 .iter()
189 .enumerate()
190 .filter(|(_, value)| **value == qrcode::Color::Dark)
191 .for_each(|(index, _)| {
192 let row = index / self.data.width;
193 let column = index % self.data.width;
194
195 frame.fill_rectangle(
196 Point::new(column as f32, row as f32),
197 Size::UNIT,
198 style.cell,
199 );
200 });
201 });
202
203 renderer.with_translation(bounds.position() - Point::ORIGIN, |renderer| {
204 use crate::graphics::geometry::Renderer as _;
205
206 renderer.draw_geometry(geometry);
207 });
208 }
209}
210
211impl<'a, Message, Theme> From<QRCode<'a, Theme>> for Element<'a, Message, Theme, Renderer>
212where
213 Theme: Catalog + 'a,
214{
215 fn from(qr_code: QRCode<'a, Theme>) -> Self {
216 Self::new(qr_code)
217 }
218}
219
220#[derive(Debug)]
224pub struct Data {
225 contents: Vec<qrcode::Color>,
226 width: usize,
227 cache: canvas::Cache<Renderer>,
228}
229
230impl Data {
231 pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> {
236 let encoded = qrcode::QrCode::new(data)?;
237
238 Ok(Self::build(encoded))
239 }
240
241 pub fn with_error_correction(
243 data: impl AsRef<[u8]>,
244 error_correction: ErrorCorrection,
245 ) -> Result<Self, Error> {
246 let encoded = qrcode::QrCode::with_error_correction_level(data, error_correction.into())?;
247
248 Ok(Self::build(encoded))
249 }
250
251 pub fn with_version(
254 data: impl AsRef<[u8]>,
255 version: Version,
256 error_correction: ErrorCorrection,
257 ) -> Result<Self, Error> {
258 let encoded = qrcode::QrCode::with_version(data, version.into(), error_correction.into())?;
259
260 Ok(Self::build(encoded))
261 }
262
263 fn build(encoded: qrcode::QrCode) -> Self {
264 let width = encoded.width();
265 let contents = encoded.into_colors();
266
267 Self {
268 contents,
269 width,
270 cache: canvas::Cache::new(),
271 }
272 }
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum Version {
281 Normal(u8),
283
284 Micro(u8),
286}
287
288impl From<Version> for qrcode::Version {
289 fn from(version: Version) -> Self {
290 match version {
291 Version::Normal(v) => qrcode::Version::Normal(i16::from(v)),
292 Version::Micro(v) => qrcode::Version::Micro(i16::from(v)),
293 }
294 }
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub enum ErrorCorrection {
305 Low,
307 Medium,
309 Quartile,
311 High,
313}
314
315impl From<ErrorCorrection> for qrcode::EcLevel {
316 fn from(ec_level: ErrorCorrection) -> Self {
317 match ec_level {
318 ErrorCorrection::Low => qrcode::EcLevel::L,
319 ErrorCorrection::Medium => qrcode::EcLevel::M,
320 ErrorCorrection::Quartile => qrcode::EcLevel::Q,
321 ErrorCorrection::High => qrcode::EcLevel::H,
322 }
323 }
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
328pub enum Error {
329 #[error("The data is too long to encode in a QR code for the chosen version")]
331 DataTooLong,
332
333 #[error("The chosen version and error correction level combination is invalid.")]
335 InvalidVersion,
336
337 #[error(
340 "One or more characters in the provided data are not supported by the \
341 chosen version"
342 )]
343 UnsupportedCharacterSet,
344
345 #[error(
348 "The chosen ECI designator is invalid. A valid designator should be \
349 between 0 and 999999."
350 )]
351 InvalidEciDesignator,
352
353 #[error("A character that does not belong to the character set was found")]
355 InvalidCharacter,
356}
357
358impl From<qrcode::types::QrError> for Error {
359 fn from(error: qrcode::types::QrError) -> Self {
360 use qrcode::types::QrError;
361
362 match error {
363 QrError::DataTooLong => Error::DataTooLong,
364 QrError::InvalidVersion => Error::InvalidVersion,
365 QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet,
366 QrError::InvalidEciDesignator => Error::InvalidEciDesignator,
367 QrError::InvalidCharacter => Error::InvalidCharacter,
368 }
369 }
370}
371
372#[derive(Default)]
373struct State {
374 last_style: RefCell<Option<Style>>,
375}
376
377#[derive(Debug, Clone, Copy, PartialEq)]
379pub struct Style {
380 pub cell: Color,
382 pub background: Color,
384}
385
386pub trait Catalog {
388 type Class<'a>;
390
391 fn default<'a>() -> Self::Class<'a>;
393
394 fn style(&self, class: &Self::Class<'_>) -> Style;
396}
397
398pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
400
401impl Catalog for Theme {
402 type Class<'a> = StyleFn<'a, Self>;
403
404 fn default<'a>() -> Self::Class<'a> {
405 Box::new(default)
406 }
407
408 fn style(&self, class: &Self::Class<'_>) -> Style {
409 class(self)
410 }
411}
412
413pub fn default(theme: &Theme) -> Style {
415 let palette = theme.palette();
416
417 Style {
418 cell: palette.text,
419 background: palette.background,
420 }
421}