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
40#[allow(missing_debug_implementations)]
64pub 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 =
95 total_size.into().0 / (self.data.width + 2 * QUIET_ZONE) as f32;
96
97 self
98 }
99
100 #[must_use]
102 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
103 where
104 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
105 {
106 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
107 self
108 }
109
110 #[cfg(feature = "advanced")]
112 #[must_use]
113 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
114 self.class = class.into();
115 self
116 }
117}
118
119impl<Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'_, Theme>
120where
121 Theme: Catalog,
122{
123 fn tag(&self) -> tree::Tag {
124 tree::Tag::of::<State>()
125 }
126
127 fn state(&self) -> tree::State {
128 tree::State::new(State::default())
129 }
130
131 fn size(&self) -> Size<Length> {
132 Size {
133 width: Length::Shrink,
134 height: Length::Shrink,
135 }
136 }
137
138 fn layout(
139 &self,
140 _tree: &mut Tree,
141 _renderer: &Renderer,
142 _limits: &layout::Limits,
143 ) -> layout::Node {
144 let side_length =
145 (self.data.width + 2 * QUIET_ZONE) as f32 * self.cell_size;
146
147 layout::Node::new(Size::new(side_length, side_length))
148 }
149
150 fn draw(
151 &self,
152 tree: &Tree,
153 renderer: &mut Renderer,
154 theme: &Theme,
155 _style: &renderer::Style,
156 layout: Layout<'_>,
157 _cursor: mouse::Cursor,
158 _viewport: &Rectangle,
159 ) {
160 let state = tree.state.downcast_ref::<State>();
161
162 let bounds = layout.bounds();
163 let side_length = self.data.width + 2 * QUIET_ZONE;
164
165 let style = theme.style(&self.class);
166 let mut last_style = state.last_style.borrow_mut();
167
168 if Some(style) != *last_style {
169 self.data.cache.clear();
170
171 *last_style = Some(style);
172 }
173
174 let geometry = self.data.cache.draw(renderer, bounds.size(), |frame| {
176 frame.scale(self.cell_size);
178
179 frame.fill_rectangle(
181 Point::ORIGIN,
182 Size::new(side_length as f32, side_length as f32),
183 style.background,
184 );
185
186 frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
188
189 self.data
191 .contents
192 .iter()
193 .enumerate()
194 .filter(|(_, value)| **value == qrcode::Color::Dark)
195 .for_each(|(index, _)| {
196 let row = index / self.data.width;
197 let column = index % self.data.width;
198
199 frame.fill_rectangle(
200 Point::new(column as f32, row as f32),
201 Size::UNIT,
202 style.cell,
203 );
204 });
205 });
206
207 renderer.with_translation(
208 bounds.position() - Point::ORIGIN,
209 |renderer| {
210 use crate::graphics::geometry::Renderer as _;
211
212 renderer.draw_geometry(geometry);
213 },
214 );
215 }
216}
217
218impl<'a, Message, Theme> From<QRCode<'a, Theme>>
219 for Element<'a, Message, Theme, Renderer>
220where
221 Theme: Catalog + 'a,
222{
223 fn from(qr_code: QRCode<'a, Theme>) -> Self {
224 Self::new(qr_code)
225 }
226}
227
228#[derive(Debug)]
232pub struct Data {
233 contents: Vec<qrcode::Color>,
234 width: usize,
235 cache: canvas::Cache<Renderer>,
236}
237
238impl Data {
239 pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> {
244 let encoded = qrcode::QrCode::new(data)?;
245
246 Ok(Self::build(encoded))
247 }
248
249 pub fn with_error_correction(
251 data: impl AsRef<[u8]>,
252 error_correction: ErrorCorrection,
253 ) -> Result<Self, Error> {
254 let encoded = qrcode::QrCode::with_error_correction_level(
255 data,
256 error_correction.into(),
257 )?;
258
259 Ok(Self::build(encoded))
260 }
261
262 pub fn with_version(
265 data: impl AsRef<[u8]>,
266 version: Version,
267 error_correction: ErrorCorrection,
268 ) -> Result<Self, Error> {
269 let encoded = qrcode::QrCode::with_version(
270 data,
271 version.into(),
272 error_correction.into(),
273 )?;
274
275 Ok(Self::build(encoded))
276 }
277
278 fn build(encoded: qrcode::QrCode) -> Self {
279 let width = encoded.width();
280 let contents = encoded.into_colors();
281
282 Self {
283 contents,
284 width,
285 cache: canvas::Cache::new(),
286 }
287 }
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub enum Version {
296 Normal(u8),
298
299 Micro(u8),
301}
302
303impl From<Version> for qrcode::Version {
304 fn from(version: Version) -> Self {
305 match version {
306 Version::Normal(v) => qrcode::Version::Normal(i16::from(v)),
307 Version::Micro(v) => qrcode::Version::Micro(i16::from(v)),
308 }
309 }
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319pub enum ErrorCorrection {
320 Low,
322 Medium,
324 Quartile,
326 High,
328}
329
330impl From<ErrorCorrection> for qrcode::EcLevel {
331 fn from(ec_level: ErrorCorrection) -> Self {
332 match ec_level {
333 ErrorCorrection::Low => qrcode::EcLevel::L,
334 ErrorCorrection::Medium => qrcode::EcLevel::M,
335 ErrorCorrection::Quartile => qrcode::EcLevel::Q,
336 ErrorCorrection::High => qrcode::EcLevel::H,
337 }
338 }
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
343pub enum Error {
344 #[error(
346 "The data is too long to encode in a QR code for the chosen version"
347 )]
348 DataTooLong,
349
350 #[error(
352 "The chosen version and error correction level combination is invalid."
353 )]
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.palette();
435
436 Style {
437 cell: palette.text,
438 background: palette.background,
439 }
440}