Merge pull request #2229 from clarkmoody/custom-qr-style

QR Code Styling
This commit is contained in:
Héctor Ramón 2024-02-10 00:12:54 +01:00 committed by GitHub
commit 7ee00e751a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 187 additions and 73 deletions

View file

@ -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) - `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) - `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 `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) - 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) - `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) - `Stream` support for `Command`. [#2150](https://github.com/iced-rs/iced/pull/2150)
@ -116,6 +117,7 @@ Many thanks to...
- @Calastrophe - @Calastrophe
- @casperstorm - @casperstorm
- @cfrenette - @cfrenette
- @clarkmoody
- @Davidster - @Davidster
- @Decodetalkers - @Decodetalkers
- @derezzedex - @derezzedex

View file

@ -1,6 +1,6 @@
use iced::widget::qr_code::{self, QRCode}; use iced::widget::qr_code::{self, QRCode};
use iced::widget::{column, container, text, text_input}; use iced::widget::{column, container, pick_list, row, text, text_input};
use iced::{Alignment, Color, Element, Length, Sandbox, Settings}; use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
QRGenerator::run(Settings::default()) QRGenerator::run(Settings::default())
@ -9,12 +9,14 @@ pub fn main() -> iced::Result {
#[derive(Default)] #[derive(Default)]
struct QRGenerator { struct QRGenerator {
data: String, data: String,
qr_code: Option<qr_code::State>, qr_code: Option<qr_code::Data>,
theme: Theme,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
DataChanged(String), DataChanged(String),
ThemeChanged(Theme),
} }
impl Sandbox for QRGenerator { impl Sandbox for QRGenerator {
@ -36,18 +38,19 @@ impl Sandbox for QRGenerator {
self.qr_code = if data.is_empty() { self.qr_code = if data.is_empty() {
None None
} else { } else {
qr_code::State::new(&data).ok() qr_code::Data::new(&data).ok()
}; };
self.data = data; self.data = data;
} }
Message::ThemeChanged(theme) => {
self.theme = theme;
}
} }
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
let title = text("QR Code Generator") let title = text("QR Code Generator").size(70);
.size(70)
.style(Color::from([0.5, 0.5, 0.5]));
let input = let input =
text_input("Type the data of your QR code here...", &self.data) text_input("Type the data of your QR code here...", &self.data)
@ -55,7 +58,18 @@ impl Sandbox for QRGenerator {
.size(30) .size(30)
.padding(15); .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) .width(700)
.spacing(20) .spacing(20)
.align_items(Alignment::Center); .align_items(Alignment::Center);
@ -72,4 +86,8 @@ impl Sandbox for QRGenerator {
.center_y() .center_y()
.into() .into()
} }
fn theme(&self) -> Theme {
self.theme.clone()
}
} }

View file

@ -24,6 +24,7 @@ pub mod menu;
pub mod pane_grid; pub mod pane_grid;
pub mod pick_list; pub mod pick_list;
pub mod progress_bar; pub mod progress_bar;
pub mod qr_code;
pub mod radio; pub mod radio;
pub mod rule; pub mod rule;
pub mod scrollable; pub mod scrollable;

20
style/src/qr_code.rs Normal file
View file

@ -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;
}

View file

@ -12,6 +12,7 @@ use crate::menu;
use crate::pane_grid; use crate::pane_grid;
use crate::pick_list; use crate::pick_list;
use crate::progress_bar; use crate::progress_bar;
use crate::qr_code;
use crate::radio; use crate::radio;
use crate::rule; use crate::rule;
use crate::scrollable; 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. /// The style of a rule.
#[derive(Default)] #[derive(Default)]
pub enum Rule { pub enum Rule {

View file

@ -3,53 +3,71 @@ use crate::canvas;
use crate::core::layout; use crate::core::layout;
use crate::core::mouse; use crate::core::mouse;
use crate::core::renderer::{self, Renderer as _}; use crate::core::renderer::{self, Renderer as _};
use crate::core::widget::Tree; use crate::core::widget::tree::{self, Tree};
use crate::core::{ 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::graphics::geometry::Renderer as _;
use crate::Renderer; use crate::Renderer;
use std::cell::RefCell;
use thiserror::Error; use thiserror::Error;
pub use crate::style::qr_code::{Appearance, StyleSheet};
const DEFAULT_CELL_SIZE: u16 = 4; const DEFAULT_CELL_SIZE: u16 = 4;
const QUIET_ZONE: usize = 2; const QUIET_ZONE: usize = 2;
/// A type of matrix barcode consisting of squares arranged in a grid which /// A type of matrix barcode consisting of squares arranged in a grid which
/// can be read by an imaging device, such as a camera. /// can be read by an imaging device, such as a camera.
#[derive(Debug)] #[derive(Debug)]
pub struct QRCode<'a> { pub struct QRCode<'a, Theme = crate::Theme>
state: &'a State, where
dark: Color, Theme: StyleSheet,
light: Color, {
data: &'a Data,
cell_size: u16, cell_size: u16,
style: Theme::Style,
} }
impl<'a> QRCode<'a> { impl<'a, Theme> QRCode<'a, Theme>
/// Creates a new [`QRCode`] with the provided [`State`]. where
pub fn new(state: &'a State) -> Self { Theme: StyleSheet,
{
/// Creates a new [`QRCode`] with the provided [`Data`].
pub fn new(data: &'a Data) -> Self {
Self { Self {
data,
cell_size: DEFAULT_CELL_SIZE, cell_size: DEFAULT_CELL_SIZE,
dark: Color::BLACK, style: Default::default(),
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`]. /// Sets the size of the squares of the grid cell of the [`QRCode`].
pub fn cell_size(mut self, cell_size: u16) -> Self { pub fn cell_size(mut self, cell_size: u16) -> Self {
self.cell_size = cell_size; self.cell_size = cell_size;
self 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> { fn size(&self) -> Size<Length> {
Size { Size {
width: Length::Shrink, width: Length::Shrink,
@ -63,7 +81,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a> {
_renderer: &Renderer, _renderer: &Renderer,
_limits: &layout::Limits, _limits: &layout::Limits,
) -> layout::Node { ) -> 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); * f32::from(self.cell_size);
layout::Node::new(Size::new(side_length, side_length)) 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( fn draw(
&self, &self,
_state: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
_theme: &Theme, theme: &Theme,
_style: &renderer::Style, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor: mouse::Cursor, _cursor: mouse::Cursor,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds(); 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 // Reuse cache if possible
let geometry = let geometry = self.data.cache.draw(renderer, bounds.size(), |frame| {
self.state.cache.draw(renderer, bounds.size(), |frame| { // Scale units to cell size
// Scale units to cell size frame.scale(self.cell_size);
frame.scale(self.cell_size);
// Draw background // Draw background
frame.fill_rectangle( frame.fill_rectangle(
Point::ORIGIN, Point::ORIGIN,
Size::new(side_length as f32, side_length as f32), Size::new(side_length as f32, side_length as f32),
self.light, appearance.background,
); );
// Avoid drawing on the quiet zone // Avoid drawing on the quiet zone
frame.translate(Vector::new( frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
QUIET_ZONE as f32,
QUIET_ZONE as f32,
));
// Draw contents // Draw contents
self.state self.data
.contents .contents
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, value)| **value == qrcode::Color::Dark) .filter(|(_, value)| **value == qrcode::Color::Dark)
.for_each(|(index, _)| { .for_each(|(index, _)| {
let row = index / self.state.width; let row = index / self.data.width;
let column = index % self.state.width; let column = index % self.data.width;
frame.fill_rectangle( frame.fill_rectangle(
Point::new(column as f32, row as f32), Point::new(column as f32, row as f32),
Size::UNIT, Size::UNIT,
self.dark, appearance.cell,
); );
}); });
}); });
renderer.with_translation( renderer.with_translation(
bounds.position() - Point::ORIGIN, 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> 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) 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)] #[derive(Debug)]
pub struct State { pub struct Data {
contents: Vec<qrcode::Color>, contents: Vec<qrcode::Color>,
width: usize, width: usize,
cache: canvas::Cache, cache: canvas::Cache,
} }
impl State { impl Data {
/// Creates a new [`State`] with the provided data. /// Creates a new [`Data`] with the provided data.
/// ///
/// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest /// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest
/// size to display the data. /// size to display the data.
@ -157,7 +184,7 @@ impl State {
Ok(Self::build(encoded)) 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( pub fn with_error_correction(
data: impl AsRef<[u8]>, data: impl AsRef<[u8]>,
error_correction: ErrorCorrection, error_correction: ErrorCorrection,
@ -170,7 +197,7 @@ impl State {
Ok(Self::build(encoded)) Ok(Self::build(encoded))
} }
/// Creates a new [`State`] with the provided [`Version`] and /// Creates a new [`Data`] with the provided [`Version`] and
/// [`ErrorCorrection`]. /// [`ErrorCorrection`].
pub fn with_version( pub fn with_version(
data: impl AsRef<[u8]>, 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum Error { pub enum Error {
/// The data is too long to encode in a QR code for the chosen [`Version`]. /// 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>>,
}