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