Implement `QRCode` widget
This commit is contained in:
parent
209056e1cd
commit
3296be845c
|
@ -21,13 +21,17 @@ image = ["iced_wgpu/image"]
|
||||||
svg = ["iced_wgpu/svg"]
|
svg = ["iced_wgpu/svg"]
|
||||||
# Enables the `Canvas` widget
|
# Enables the `Canvas` widget
|
||||||
canvas = ["iced_wgpu/canvas"]
|
canvas = ["iced_wgpu/canvas"]
|
||||||
# Enables using system fonts.
|
# Enables the `QRCode` widget
|
||||||
|
qr_code = ["iced_wgpu/qr_code"]
|
||||||
|
# Enables using system fonts
|
||||||
default_system_font = ["iced_wgpu/default_system_font"]
|
default_system_font = ["iced_wgpu/default_system_font"]
|
||||||
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
|
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
|
||||||
glow = ["iced_glow", "iced_glutin"]
|
glow = ["iced_glow", "iced_glutin"]
|
||||||
# Enables the `Canvas` widget for `iced_glow`
|
# Enables the `Canvas` widget for `iced_glow`
|
||||||
glow_canvas = ["iced_glow/canvas"]
|
glow_canvas = ["iced_glow/canvas"]
|
||||||
# Enables using system fonts for `iced_glow`.
|
# Enables the `QRCode` widget for `iced_glow`
|
||||||
|
glow_qr_code = ["iced_glow/qr_code"]
|
||||||
|
# Enables using system fonts for `iced_glow`
|
||||||
glow_default_system_font = ["iced_glow/default_system_font"]
|
glow_default_system_font = ["iced_glow/default_system_font"]
|
||||||
# Enables a debug view in native platforms (press F12)
|
# Enables a debug view in native platforms (press F12)
|
||||||
debug = ["iced_winit/debug"]
|
debug = ["iced_winit/debug"]
|
||||||
|
@ -67,6 +71,7 @@ members = [
|
||||||
"examples/pick_list",
|
"examples/pick_list",
|
||||||
"examples/pokedex",
|
"examples/pokedex",
|
||||||
"examples/progress_bar",
|
"examples/progress_bar",
|
||||||
|
"examples/qr_code",
|
||||||
"examples/scrollable",
|
"examples/scrollable",
|
||||||
"examples/solar_system",
|
"examples/solar_system",
|
||||||
"examples/stopwatch",
|
"examples/stopwatch",
|
||||||
|
|
|
@ -9,6 +9,7 @@ repository = "https://github.com/hecrj/iced"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
canvas = ["iced_graphics/canvas"]
|
canvas = ["iced_graphics/canvas"]
|
||||||
|
qr_code = ["iced_graphics/qr_code"]
|
||||||
default_system_font = ["iced_graphics/font-source"]
|
default_system_font = ["iced_graphics/font-source"]
|
||||||
# Not supported yet!
|
# Not supported yet!
|
||||||
image = []
|
image = []
|
||||||
|
|
|
@ -52,6 +52,14 @@ pub mod canvas;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use canvas::Canvas;
|
pub use canvas::Canvas;
|
||||||
|
|
||||||
|
#[cfg(feature = "qr_code")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))]
|
||||||
|
pub mod qr_code;
|
||||||
|
|
||||||
|
#[cfg(feature = "qr_code")]
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use qr_code::QRCode;
|
||||||
|
|
||||||
pub use iced_native::{Image, Space};
|
pub use iced_native::{Image, Space};
|
||||||
|
|
||||||
/// A container that distributes its contents vertically.
|
/// A container that distributes its contents vertically.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
//! Encode and display information in a QR code.
|
||||||
|
pub use iced_graphics::qr_code::*;
|
|
@ -6,6 +6,7 @@ edition = "2018"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
canvas = ["lyon"]
|
canvas = ["lyon"]
|
||||||
|
qr_code = ["qrcode", "canvas"]
|
||||||
font-source = ["font-kit"]
|
font-source = ["font-kit"]
|
||||||
font-fallback = []
|
font-fallback = []
|
||||||
font-icons = []
|
font-icons = []
|
||||||
|
@ -32,6 +33,10 @@ path = "../style"
|
||||||
version = "0.16"
|
version = "0.16"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.qrcode]
|
||||||
|
version = "0.12"
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dependencies.font-kit]
|
[dependencies.font-kit]
|
||||||
version = "0.8"
|
version = "0.8"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
|
@ -63,3 +63,11 @@ pub mod canvas;
|
||||||
#[cfg(feature = "canvas")]
|
#[cfg(feature = "canvas")]
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use canvas::Canvas;
|
pub use canvas::Canvas;
|
||||||
|
|
||||||
|
#[cfg(feature = "qr_code")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))]
|
||||||
|
pub mod qr_code;
|
||||||
|
|
||||||
|
#[cfg(feature = "qr_code")]
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use qr_code::QRCode;
|
||||||
|
|
|
@ -0,0 +1,305 @@
|
||||||
|
//! Encode and display information in a QR code.
|
||||||
|
use crate::canvas;
|
||||||
|
use crate::{Backend, Defaults, Primitive, Renderer, Vector};
|
||||||
|
|
||||||
|
use iced_native::{
|
||||||
|
layout, mouse, Color, Element, Hasher, Layout, Length, Point, Rectangle,
|
||||||
|
Size, Widget,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
const DEFAULT_CELL_SIZE: u16 = 4;
|
||||||
|
const QUIET_ZONE: usize = 2;
|
||||||
|
|
||||||
|
/// A type of matrix barcode consisting of squares arranged in a grid which
|
||||||
|
/// can be read by an imaging device, such as a camera.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct QRCode<'a> {
|
||||||
|
state: &'a State,
|
||||||
|
dark: Color,
|
||||||
|
light: Color,
|
||||||
|
cell_size: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> QRCode<'a> {
|
||||||
|
/// Creates a new [`QRCode`] with the provided [`State`].
|
||||||
|
pub fn new(state: &'a State) -> Self {
|
||||||
|
Self {
|
||||||
|
cell_size: DEFAULT_CELL_SIZE,
|
||||||
|
dark: Color::BLACK,
|
||||||
|
light: Color::WHITE,
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets both the dark and light [`Color`]s of the [`QRCode`].
|
||||||
|
pub fn color(mut self, dark: Color, light: Color) -> Self {
|
||||||
|
self.dark = dark;
|
||||||
|
self.light = light;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the size of the squares of the grid cell of the [`QRCode`].
|
||||||
|
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 hash_layout(&self, state: &mut Hasher) {
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
self.state.contents.hash(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_renderer: &mut Renderer<B>,
|
||||||
|
_defaults: &Defaults,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
_cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) -> (Primitive, mouse::Interaction) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let side_length = self.state.width + 2 * QUIET_ZONE;
|
||||||
|
|
||||||
|
// Reuse cache if possible
|
||||||
|
let geometry = self.state.cache.draw(bounds.size(), |frame| {
|
||||||
|
// Scale units to cell size
|
||||||
|
frame.scale(f32::from(self.cell_size));
|
||||||
|
|
||||||
|
// Draw background
|
||||||
|
frame.fill_rectangle(
|
||||||
|
Point::ORIGIN,
|
||||||
|
Size::new(side_length as f32, side_length as f32),
|
||||||
|
self.light,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Avoid drawing on the quiet zone
|
||||||
|
frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
|
||||||
|
|
||||||
|
// Draw contents
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
(
|
||||||
|
Primitive::Translate {
|
||||||
|
translation: Vector::new(bounds.x, bounds.y),
|
||||||
|
content: Box::new(geometry.into_primitive()),
|
||||||
|
},
|
||||||
|
mouse::Interaction::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The state of a [`QRCode`].
|
||||||
|
///
|
||||||
|
/// It stores the data that will be displayed.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct State {
|
||||||
|
contents: Vec<qrcode::Color>,
|
||||||
|
width: usize,
|
||||||
|
cache: canvas::Cache,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// Creates a new [`State`] with the provided data.
|
||||||
|
///
|
||||||
|
/// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest
|
||||||
|
/// size to display the data.
|
||||||
|
pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> {
|
||||||
|
let encoded = qrcode::QrCode::new(data)?;
|
||||||
|
|
||||||
|
Ok(Self::build(encoded))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`State`] with the provided [`ErrorCorrection`].
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`State`] with the provided [`Version`] and
|
||||||
|
/// [`ErrorCorrection`].
|
||||||
|
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)]
|
||||||
|
/// The size of a [`QRCode`].
|
||||||
|
///
|
||||||
|
/// The higher the version the larger the grid of cells, and therefore the more
|
||||||
|
/// information the [`QRCode`] can carry.
|
||||||
|
pub enum Version {
|
||||||
|
/// A normal QR code version. It should be between 1 and 40.
|
||||||
|
Normal(u8),
|
||||||
|
|
||||||
|
/// A micro QR code version. It should be between 1 and 4.
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error correction level.
|
||||||
|
///
|
||||||
|
/// It controls the amount of data that can be damaged while still being able
|
||||||
|
/// to recover the original information.
|
||||||
|
///
|
||||||
|
/// A higher error correction level allows for more corrupted data.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ErrorCorrection {
|
||||||
|
/// Low error correction. 7% of the data can be restored.
|
||||||
|
Low,
|
||||||
|
/// Medium error correction. 15% of the data can be restored.
|
||||||
|
Medium,
|
||||||
|
/// Quartile error correction. 25% of the data can be restored.
|
||||||
|
Quartile,
|
||||||
|
/// High error correction. 30% of the data can be restored.
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that occurred when building a [`State`] for a [`QRCode`].
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// The data is too long to encode in a QR code for the chosen [`Version`].
|
||||||
|
#[error(
|
||||||
|
"The data is too long to encode in a QR code for the chosen version"
|
||||||
|
)]
|
||||||
|
DataTooLong,
|
||||||
|
|
||||||
|
/// The chosen [`Version`] and [`ErrorCorrection`] combination is invalid.
|
||||||
|
#[error(
|
||||||
|
"The chosen version and error correction level combination is invalid."
|
||||||
|
)]
|
||||||
|
InvalidVersion,
|
||||||
|
|
||||||
|
/// One or more characters in the provided data are not supported by the
|
||||||
|
/// chosen [`Version`].
|
||||||
|
#[error(
|
||||||
|
"One or more characters in the provided data are not supported by the \
|
||||||
|
chosen version"
|
||||||
|
)]
|
||||||
|
UnsupportedCharacterSet,
|
||||||
|
|
||||||
|
/// The chosen ECI designator is invalid. A valid designator should be
|
||||||
|
/// between 0 and 999999.
|
||||||
|
#[error(
|
||||||
|
"The chosen ECI designator is invalid. A valid designator should be \
|
||||||
|
between 0 and 999999."
|
||||||
|
)]
|
||||||
|
InvalidEciDesignator,
|
||||||
|
|
||||||
|
/// A character that does not belong to the character set was found.
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,13 @@ mod platform {
|
||||||
)]
|
)]
|
||||||
pub use crate::renderer::widget::canvas;
|
pub use crate::renderer::widget::canvas;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "qr_code", feature = "glow_qr_code"))]
|
||||||
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "qr_code", feature = "glow_qr_code")))
|
||||||
|
)]
|
||||||
|
pub use crate::renderer::widget::qr_code;
|
||||||
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
|
||||||
pub mod image {
|
pub mod image {
|
||||||
//! Display images in your user interface.
|
//! Display images in your user interface.
|
||||||
|
@ -53,6 +60,10 @@ mod platform {
|
||||||
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
|
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use canvas::Canvas;
|
pub use canvas::Canvas;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "qr_code", feature = "glow_qr_code"))]
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use qr_code::QRCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
|
|
@ -10,6 +10,7 @@ repository = "https://github.com/hecrj/iced"
|
||||||
[features]
|
[features]
|
||||||
svg = ["resvg"]
|
svg = ["resvg"]
|
||||||
canvas = ["iced_graphics/canvas"]
|
canvas = ["iced_graphics/canvas"]
|
||||||
|
qr_code = ["iced_graphics/qr_code"]
|
||||||
default_system_font = ["iced_graphics/font-source"]
|
default_system_font = ["iced_graphics/font-source"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
@ -52,6 +52,14 @@ pub mod canvas;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use canvas::Canvas;
|
pub use canvas::Canvas;
|
||||||
|
|
||||||
|
#[cfg(feature = "qr_code")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))]
|
||||||
|
pub mod qr_code;
|
||||||
|
|
||||||
|
#[cfg(feature = "qr_code")]
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use qr_code::QRCode;
|
||||||
|
|
||||||
pub use iced_native::Space;
|
pub use iced_native::Space;
|
||||||
|
|
||||||
/// A container that distributes its contents vertically.
|
/// A container that distributes its contents vertically.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
//! Encode and display information in a QR code.
|
||||||
|
pub use iced_graphics::qr_code::*;
|
Loading…
Reference in New Issue