summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml9
-rw-r--r--glow/Cargo.toml1
-rw-r--r--glow/src/widget.rs8
-rw-r--r--glow/src/widget/qr_code.rs2
-rw-r--r--graphics/Cargo.toml5
-rw-r--r--graphics/src/widget.rs8
-rw-r--r--graphics/src/widget/qr_code.rs305
-rw-r--r--src/widget.rs11
-rw-r--r--wgpu/Cargo.toml1
-rw-r--r--wgpu/src/widget.rs8
-rw-r--r--wgpu/src/widget/qr_code.rs2
11 files changed, 358 insertions, 2 deletions
diff --git a/Cargo.toml b/Cargo.toml
index e201a4b6..b829c702 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,13 +21,17 @@ image = ["iced_wgpu/image"]
svg = ["iced_wgpu/svg"]
# Enables the `Canvas` widget
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"]
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
glow = ["iced_glow", "iced_glutin"]
# Enables the `Canvas` widget for `iced_glow`
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"]
# Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug"]
@@ -67,6 +71,7 @@ members = [
"examples/pick_list",
"examples/pokedex",
"examples/progress_bar",
+ "examples/qr_code",
"examples/scrollable",
"examples/solar_system",
"examples/stopwatch",
diff --git a/glow/Cargo.toml b/glow/Cargo.toml
index 8c15be04..0178f9f7 100644
--- a/glow/Cargo.toml
+++ b/glow/Cargo.toml
@@ -9,6 +9,7 @@ repository = "https://github.com/hecrj/iced"
[features]
canvas = ["iced_graphics/canvas"]
+qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
# Not supported yet!
image = []
diff --git a/glow/src/widget.rs b/glow/src/widget.rs
index 0e33909d..b5c84c56 100644
--- a/glow/src/widget.rs
+++ b/glow/src/widget.rs
@@ -52,6 +52,14 @@ pub mod canvas;
#[doc(no_inline)]
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};
/// A container that distributes its contents vertically.
diff --git a/glow/src/widget/qr_code.rs b/glow/src/widget/qr_code.rs
new file mode 100644
index 00000000..7b1c2408
--- /dev/null
+++ b/glow/src/widget/qr_code.rs
@@ -0,0 +1,2 @@
+//! Encode and display information in a QR code.
+pub use iced_graphics::qr_code::*;
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index 1511f8b0..4ce50e5a 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2018"
[features]
canvas = ["lyon"]
+qr_code = ["qrcode", "canvas"]
font-source = ["font-kit"]
font-fallback = []
font-icons = []
@@ -32,6 +33,10 @@ path = "../style"
version = "0.16"
optional = true
+[dependencies.qrcode]
+version = "0.12"
+optional = true
+
[dependencies.font-kit]
version = "0.8"
optional = true
diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs
index f87b558a..159ca91b 100644
--- a/graphics/src/widget.rs
+++ b/graphics/src/widget.rs
@@ -63,3 +63,11 @@ pub mod canvas;
#[cfg(feature = "canvas")]
#[doc(no_inline)]
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;
diff --git a/graphics/src/widget/qr_code.rs b/graphics/src/widget/qr_code.rs
new file mode 100644
index 00000000..b3a01dd7
--- /dev/null
+++ b/graphics/src/widget/qr_code.rs
@@ -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,
+ }
+ }
+}
diff --git a/src/widget.rs b/src/widget.rs
index e8fff9cc..fdef89d6 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -30,6 +30,13 @@ mod platform {
)]
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")))]
pub mod image {
//! Display images in your user interface.
@@ -53,6 +60,10 @@ mod platform {
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
#[doc(no_inline)]
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")]
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 01fd720b..4d9f9ada 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -10,6 +10,7 @@ repository = "https://github.com/hecrj/iced"
[features]
svg = ["resvg"]
canvas = ["iced_graphics/canvas"]
+qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
[dependencies]
diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs
index 1dae26f5..177ae1b6 100644
--- a/wgpu/src/widget.rs
+++ b/wgpu/src/widget.rs
@@ -52,6 +52,14 @@ pub mod canvas;
#[doc(no_inline)]
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;
/// A container that distributes its contents vertically.
diff --git a/wgpu/src/widget/qr_code.rs b/wgpu/src/widget/qr_code.rs
new file mode 100644
index 00000000..7b1c2408
--- /dev/null
+++ b/wgpu/src/widget/qr_code.rs
@@ -0,0 +1,2 @@
+//! Encode and display information in a QR code.
+pub use iced_graphics::qr_code::*;