use crate::canvas;
use crate::renderer::{self, Renderer};
use crate::Backend;
use iced_native::layout;
use iced_native::{
Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
};
use thiserror::Error;
const DEFAULT_CELL_SIZE: u16 = 4;
const QUIET_ZONE: usize = 2;
#[derive(Debug)]
pub struct QRCode<'a> {
state: &'a State,
dark: Color,
light: Color,
cell_size: u16,
}
impl<'a> QRCode<'a> {
pub fn new(state: &'a State) -> Self {
Self {
cell_size: DEFAULT_CELL_SIZE,
dark: Color::BLACK,
light: Color::WHITE,
state,
}
}
pub fn color(mut self, dark: Color, light: Color) -> Self {
self.dark = dark;
self.light = light;
self
}
pub fn cell_size(mut self, cell_size: u16) -> Self {
self.cell_size = cell_size;
self
}
}
impl<'a, Message, B> Widget<Message, Renderer<B>> for QRCode<'a>
where
B: Backend,
{
fn width(&self) -> Length {
Length::Shrink
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
_renderer: &Renderer<B>,
_limits: &layout::Limits,
) -> layout::Node {
let side_length = (self.state.width + 2 * QUIET_ZONE) as f32
* f32::from(self.cell_size);
layout::Node::new(Size::new(
f32::from(side_length),
f32::from(side_length),
))
}
fn draw(
&self,
renderer: &mut Renderer<B>,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
use iced_native::Renderer as _;
let bounds = layout.bounds();
let side_length = self.state.width + 2 * QUIET_ZONE;
let geometry = self.state.cache.draw(bounds.size(), |frame| {
frame.scale(f32::from(self.cell_size));
frame.fill_rectangle(
Point::ORIGIN,
Size::new(side_length as f32, side_length as f32),
self.light,
);
frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
self.state
.contents
.iter()
.enumerate()
.filter(|(_, value)| **value == qrcode::Color::Dark)
.for_each(|(index, _)| {
let row = index / self.state.width;
let column = index % self.state.width;
frame.fill_rectangle(
Point::new(column as f32, row as f32),
Size::UNIT,
self.dark,
);
});
});
let translation = Vector::new(bounds.x, bounds.y);
renderer.with_translation(translation, |renderer| {
renderer.draw_primitive(geometry.into_primitive());
});
}
}
impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for QRCode<'a>
where
B: Backend,
{
fn into(self) -> Element<'a, Message, Renderer<B>> {
Element::new(self)
}
}
#[derive(Debug)]
pub struct State {
contents: Vec<qrcode::Color>,
width: usize,
cache: canvas::Cache,
}
impl State {
pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> {
let encoded = qrcode::QrCode::new(data)?;
Ok(Self::build(encoded))
}
pub fn with_error_correction(
data: impl AsRef<[u8]>,
error_correction: ErrorCorrection,
) -> Result<Self, Error> {
let encoded = qrcode::QrCode::with_error_correction_level(
data,
error_correction.into(),
)?;
Ok(Self::build(encoded))
}
pub fn with_version(
data: impl AsRef<[u8]>,
version: Version,
error_correction: ErrorCorrection,
) -> Result<Self, Error> {
let encoded = qrcode::QrCode::with_version(
data,
version.into(),
error_correction.into(),
)?;
Ok(Self::build(encoded))
}
fn build(encoded: qrcode::QrCode) -> Self {
let width = encoded.width();
let contents = encoded.into_colors();
Self {
contents,
width,
cache: canvas::Cache::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Version {
Normal(u8),
Micro(u8),
}
impl From<Version> for qrcode::Version {
fn from(version: Version) -> Self {
match version {
Version::Normal(v) => qrcode::Version::Normal(i16::from(v)),
Version::Micro(v) => qrcode::Version::Micro(i16::from(v)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCorrection {
Low,
Medium,
Quartile,
High,
}
impl From<ErrorCorrection> for qrcode::EcLevel {
fn from(ec_level: ErrorCorrection) -> Self {
match ec_level {
ErrorCorrection::Low => qrcode::EcLevel::L,
ErrorCorrection::Medium => qrcode::EcLevel::M,
ErrorCorrection::Quartile => qrcode::EcLevel::Q,
ErrorCorrection::High => qrcode::EcLevel::H,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum Error {
#[error(
"The data is too long to encode in a QR code for the chosen version"
)]
DataTooLong,
#[error(
"The chosen version and error correction level combination is invalid."
)]
InvalidVersion,
#[error(
"One or more characters in the provided data are not supported by the \
chosen version"
)]
UnsupportedCharacterSet,
#[error(
"The chosen ECI designator is invalid. A valid designator should be \
between 0 and 999999."
)]
InvalidEciDesignator,
#[error("A character that does not belong to the character set was found")]
InvalidCharacter,
}
impl From<qrcode::types::QrError> for Error {
fn from(error: qrcode::types::QrError) -> Self {
use qrcode::types::QrError;
match error {
QrError::DataTooLong => Error::DataTooLong,
QrError::InvalidVersion => Error::InvalidVersion,
QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet,
QrError::InvalidEciDesignator => Error::InvalidEciDesignator,
QrError::InvalidCharacter => Error::InvalidCharacter,
}
}
}