summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector@hecrj.dev>2024-02-10 00:12:54 +0100
committerLibravatar GitHub <noreply@github.com>2024-02-10 00:12:54 +0100
commit7ee00e751a8fe5cedadeeb478c6f0e7d6a5dc0dc (patch)
treeb8929e4c9c61af800f1dba8664f19a15c0dc13a2
parent99a3f25c5f95a144fee7d25b4607f7275abcfaa2 (diff)
parent564ad95806efd1259a2244a685b648346b460abf (diff)
downloadiced-7ee00e751a8fe5cedadeeb478c6f0e7d6a5dc0dc.tar.gz
iced-7ee00e751a8fe5cedadeeb478c6f0e7d6a5dc0dc.tar.bz2
iced-7ee00e751a8fe5cedadeeb478c6f0e7d6a5dc0dc.zip
Merge pull request #2229 from clarkmoody/custom-qr-style
QR Code Styling
-rw-r--r--CHANGELOG.md2
-rw-r--r--examples/qr_code/src/main.rs34
-rw-r--r--style/src/lib.rs1
-rw-r--r--style/src/qr_code.rs20
-rw-r--r--style/src/theme.rs41
-rw-r--r--widget/src/qr_code.rs170
6 files changed, 191 insertions, 77 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8660cc5a..8f0f96a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `hovered` styling for `Svg` widget. [#2163](https://github.com/iced-rs/iced/pull/2163)
- `height` method for `TextEditor`. [#2221](https://github.com/iced-rs/iced/pull/2221)
- Customizable style for `TextEditor`. [#2159](https://github.com/iced-rs/iced/pull/2159)
+- Customizable style for `QRCode`. [#2229](https://github.com/iced-rs/iced/pull/2229)
- Border width styling for `Toggler`. [#2219](https://github.com/iced-rs/iced/pull/2219)
- `RawText` variant for `Primitive` in `iced_graphics`. [#2158](https://github.com/iced-rs/iced/pull/2158)
- `Stream` support for `Command`. [#2150](https://github.com/iced-rs/iced/pull/2150)
@@ -116,6 +117,7 @@ Many thanks to...
- @Calastrophe
- @casperstorm
- @cfrenette
+- @clarkmoody
- @Davidster
- @Decodetalkers
- @derezzedex
diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs
index 867ebfa4..8b2e9500 100644
--- a/examples/qr_code/src/main.rs
+++ b/examples/qr_code/src/main.rs
@@ -1,6 +1,6 @@
use iced::widget::qr_code::{self, QRCode};
-use iced::widget::{column, container, text, text_input};
-use iced::{Alignment, Color, Element, Length, Sandbox, Settings};
+use iced::widget::{column, container, pick_list, row, text, text_input};
+use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
pub fn main() -> iced::Result {
QRGenerator::run(Settings::default())
@@ -9,12 +9,14 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct QRGenerator {
data: String,
- qr_code: Option<qr_code::State>,
+ qr_code: Option<qr_code::Data>,
+ theme: Theme,
}
#[derive(Debug, Clone)]
enum Message {
DataChanged(String),
+ ThemeChanged(Theme),
}
impl Sandbox for QRGenerator {
@@ -36,18 +38,19 @@ impl Sandbox for QRGenerator {
self.qr_code = if data.is_empty() {
None
} else {
- qr_code::State::new(&data).ok()
+ qr_code::Data::new(&data).ok()
};
self.data = data;
}
+ Message::ThemeChanged(theme) => {
+ self.theme = theme;
+ }
}
}
fn view(&self) -> Element<Message> {
- let title = text("QR Code Generator")
- .size(70)
- .style(Color::from([0.5, 0.5, 0.5]));
+ let title = text("QR Code Generator").size(70);
let input =
text_input("Type the data of your QR code here...", &self.data)
@@ -55,7 +58,18 @@ impl Sandbox for QRGenerator {
.size(30)
.padding(15);
- let mut content = column![title, input]
+ let choose_theme = row![
+ text("Theme:"),
+ pick_list(
+ Theme::ALL,
+ Some(self.theme.clone()),
+ Message::ThemeChanged,
+ )
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ let mut content = column![title, input, choose_theme]
.width(700)
.spacing(20)
.align_items(Alignment::Center);
@@ -72,4 +86,8 @@ impl Sandbox for QRGenerator {
.center_y()
.into()
}
+
+ fn theme(&self) -> Theme {
+ self.theme.clone()
+ }
}
diff --git a/style/src/lib.rs b/style/src/lib.rs
index e4097434..3c2865eb 100644
--- a/style/src/lib.rs
+++ b/style/src/lib.rs
@@ -24,6 +24,7 @@ pub mod menu;
pub mod pane_grid;
pub mod pick_list;
pub mod progress_bar;
+pub mod qr_code;
pub mod radio;
pub mod rule;
pub mod scrollable;
diff --git a/style/src/qr_code.rs b/style/src/qr_code.rs
new file mode 100644
index 00000000..02c4709a
--- /dev/null
+++ b/style/src/qr_code.rs
@@ -0,0 +1,20 @@
+//! Change the appearance of a QR code.
+use crate::core::Color;
+
+/// The appearance of a QR code.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Appearance {
+ /// The color of the QR code data cells
+ pub cell: Color,
+ /// The color of the QR code background
+ pub background: Color,
+}
+
+/// A set of rules that dictate the style of a QR code.
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
+
+ /// Produces the style of a QR code.
+ fn appearance(&self, style: &Self::Style) -> Appearance;
+}
diff --git a/style/src/theme.rs b/style/src/theme.rs
index e579a1c2..afb4d027 100644
--- a/style/src/theme.rs
+++ b/style/src/theme.rs
@@ -12,6 +12,7 @@ use crate::menu;
use crate::pane_grid;
use crate::pick_list;
use crate::progress_bar;
+use crate::qr_code;
use crate::radio;
use crate::rule;
use crate::scrollable;
@@ -956,6 +957,46 @@ impl<T: Fn(&Theme) -> progress_bar::Appearance> progress_bar::StyleSheet for T {
}
}
+/// The style of a QR Code.
+#[derive(Default)]
+pub enum QRCode {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(Box<dyn qr_code::StyleSheet<Style = Theme>>),
+}
+
+impl<T: Fn(&Theme) -> qr_code::Appearance + 'static> From<T> for QRCode {
+ fn from(f: T) -> Self {
+ Self::Custom(Box::new(f))
+ }
+}
+
+impl qr_code::StyleSheet for Theme {
+ type Style = QRCode;
+
+ fn appearance(&self, style: &Self::Style) -> qr_code::Appearance {
+ let palette = self.palette();
+
+ match style {
+ QRCode::Default => qr_code::Appearance {
+ cell: palette.text,
+ background: palette.background,
+ },
+ QRCode::Custom(custom) => custom.appearance(self),
+ }
+ }
+}
+
+impl<T: Fn(&Theme) -> qr_code::Appearance> qr_code::StyleSheet for T {
+ type Style = Theme;
+
+ fn appearance(&self, style: &Self::Style) -> qr_code::Appearance {
+ (self)(style)
+ }
+}
+
/// The style of a rule.
#[derive(Default)]
pub enum Rule {
diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs
index 6a748e63..eeb1526f 100644
--- a/widget/src/qr_code.rs
+++ b/widget/src/qr_code.rs
@@ -3,53 +3,71 @@ use crate::canvas;
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer::{self, Renderer as _};
-use crate::core::widget::Tree;
+use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
};
use crate::graphics::geometry::Renderer as _;
use crate::Renderer;
+
+use std::cell::RefCell;
use thiserror::Error;
+pub use crate::style::qr_code::{Appearance, StyleSheet};
+
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,
+pub struct QRCode<'a, Theme = crate::Theme>
+where
+ Theme: StyleSheet,
+{
+ data: &'a Data,
cell_size: u16,
+ style: Theme::Style,
}
-impl<'a> QRCode<'a> {
- /// Creates a new [`QRCode`] with the provided [`State`].
- pub fn new(state: &'a State) -> Self {
+impl<'a, Theme> QRCode<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ /// Creates a new [`QRCode`] with the provided [`Data`].
+ pub fn new(data: &'a Data) -> Self {
Self {
+ data,
cell_size: DEFAULT_CELL_SIZE,
- dark: Color::BLACK,
- light: Color::WHITE,
- state,
+ style: Default::default(),
}
}
- /// 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
}
+
+ /// Sets the style of the [`QRCode`].
+ pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
+ self.style = style.into();
+ self
+ }
}
-impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a> {
+impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a, Theme>
+where
+ Theme: StyleSheet,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::default())
+ }
+
fn size(&self) -> Size<Length> {
Size {
width: Length::Shrink,
@@ -63,7 +81,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a> {
_renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
- let side_length = (self.state.width + 2 * QUIET_ZONE) as f32
+ let side_length = (self.data.width + 2 * QUIET_ZONE) as f32
* f32::from(self.cell_size);
layout::Node::new(Size::new(side_length, side_length))
@@ -71,53 +89,60 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a> {
fn draw(
&self,
- _state: &Tree,
+ tree: &Tree,
renderer: &mut Renderer,
- _theme: &Theme,
+ theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
+ let state = tree.state.downcast_ref::<State>();
+
let bounds = layout.bounds();
- let side_length = self.state.width + 2 * QUIET_ZONE;
+ let side_length = self.data.width + 2 * QUIET_ZONE;
+
+ let appearance = theme.appearance(&self.style);
+ let mut last_appearance = state.last_appearance.borrow_mut();
+
+ if Some(appearance) != *last_appearance {
+ self.data.cache.clear();
+
+ *last_appearance = Some(appearance);
+ }
// Reuse cache if possible
- let geometry =
- self.state.cache.draw(renderer, bounds.size(), |frame| {
- // Scale units to cell size
- frame.scale(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,
- );
- });
- });
+ let geometry = self.data.cache.draw(renderer, bounds.size(), |frame| {
+ // Scale units to cell size
+ frame.scale(self.cell_size);
+
+ // Draw background
+ frame.fill_rectangle(
+ Point::ORIGIN,
+ Size::new(side_length as f32, side_length as f32),
+ appearance.background,
+ );
+
+ // Avoid drawing on the quiet zone
+ frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
+
+ // Draw contents
+ self.data
+ .contents
+ .iter()
+ .enumerate()
+ .filter(|(_, value)| **value == qrcode::Color::Dark)
+ .for_each(|(index, _)| {
+ let row = index / self.data.width;
+ let column = index % self.data.width;
+
+ frame.fill_rectangle(
+ Point::new(column as f32, row as f32),
+ Size::UNIT,
+ appearance.cell,
+ );
+ });
+ });
renderer.with_translation(
bounds.position() - Point::ORIGIN,
@@ -128,26 +153,28 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a> {
}
}
-impl<'a, Message, Theme> From<QRCode<'a>>
+impl<'a, Message, Theme> From<QRCode<'a, Theme>>
for Element<'a, Message, Theme, Renderer>
+where
+ Theme: StyleSheet + 'a,
{
- fn from(qr_code: QRCode<'a>) -> Self {
+ fn from(qr_code: QRCode<'a, Theme>) -> Self {
Self::new(qr_code)
}
}
-/// The state of a [`QRCode`].
+/// The data of a [`QRCode`].
///
-/// It stores the data that will be displayed.
+/// It stores the contents that will be displayed.
#[derive(Debug)]
-pub struct State {
+pub struct Data {
contents: Vec<qrcode::Color>,
width: usize,
cache: canvas::Cache,
}
-impl State {
- /// Creates a new [`State`] with the provided data.
+impl Data {
+ /// Creates a new [`Data`] with the provided data.
///
/// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest
/// size to display the data.
@@ -157,7 +184,7 @@ impl State {
Ok(Self::build(encoded))
}
- /// Creates a new [`State`] with the provided [`ErrorCorrection`].
+ /// Creates a new [`Data`] with the provided [`ErrorCorrection`].
pub fn with_error_correction(
data: impl AsRef<[u8]>,
error_correction: ErrorCorrection,
@@ -170,7 +197,7 @@ impl State {
Ok(Self::build(encoded))
}
- /// Creates a new [`State`] with the provided [`Version`] and
+ /// Creates a new [`Data`] with the provided [`Version`] and
/// [`ErrorCorrection`].
pub fn with_version(
data: impl AsRef<[u8]>,
@@ -249,7 +276,7 @@ impl From<ErrorCorrection> for qrcode::EcLevel {
}
}
-/// An error that occurred when building a [`State`] for a [`QRCode`].
+/// An error that occurred when building a [`Data`] 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`].
@@ -298,3 +325,8 @@ impl From<qrcode::types::QrError> for Error {
}
}
}
+
+#[derive(Default)]
+struct State {
+ last_appearance: RefCell<Option<Appearance>>,
+}