diff options
Diffstat (limited to 'graphics/src/widget')
-rw-r--r-- | graphics/src/widget/button.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/canvas.rs | 21 | ||||
-rw-r--r-- | graphics/src/widget/canvas/event.rs | 3 | ||||
-rw-r--r-- | graphics/src/widget/canvas/frame.rs | 2 | ||||
-rw-r--r-- | graphics/src/widget/canvas/program.rs | 9 | ||||
-rw-r--r-- | graphics/src/widget/container.rs | 2 | ||||
-rw-r--r-- | graphics/src/widget/pane_grid.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/progress_bar.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/qr_code.rs | 305 | ||||
-rw-r--r-- | graphics/src/widget/radio.rs | 6 | ||||
-rw-r--r-- | graphics/src/widget/rule.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/scrollable.rs | 2 | ||||
-rw-r--r-- | graphics/src/widget/slider.rs | 10 | ||||
-rw-r--r-- | graphics/src/widget/text_input.rs | 8 |
14 files changed, 350 insertions, 34 deletions
diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs index a1afc940..87581175 100644 --- a/graphics/src/widget/button.rs +++ b/graphics/src/widget/button.rs @@ -66,7 +66,7 @@ where ); ( - if styling.background.is_some() || styling.border_width > 0 { + if styling.background.is_some() || styling.border_width > 0.0 { let background = Primitive::Quad { bounds, background: styling @@ -93,7 +93,7 @@ where [0.0, 0.0, 0.0, 0.5].into(), ), border_radius: styling.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 73778d16..ae0d87a4 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -7,18 +7,20 @@ //! [`Canvas`]: struct.Canvas.html //! [`Frame`]: struct.Frame.html use crate::{Backend, Defaults, Primitive, Renderer}; +use iced_native::layout; +use iced_native::mouse; use iced_native::{ - layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, - Rectangle, Size, Vector, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, + Widget, }; use std::hash::Hash; use std::marker::PhantomData; +pub mod event; pub mod path; mod cache; mod cursor; -mod event; mod fill; mod frame; mod geometry; @@ -166,7 +168,7 @@ where messages: &mut Vec<Message>, _renderer: &Renderer<B>, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let bounds = layout.bounds(); let canvas_event = match event { @@ -182,12 +184,17 @@ where let cursor = Cursor::from_window_position(cursor_position); if let Some(canvas_event) = canvas_event { - if let Some(message) = - self.program.update(canvas_event, bounds, cursor) - { + let (event_status, message) = + self.program.update(canvas_event, bounds, cursor); + + if let Some(message) = message { messages.push(message); } + + return event_status; } + + event::Status::Ignored } fn draw( diff --git a/graphics/src/widget/canvas/event.rs b/graphics/src/widget/canvas/event.rs index 0e66f0ff..ede2fd73 100644 --- a/graphics/src/widget/canvas/event.rs +++ b/graphics/src/widget/canvas/event.rs @@ -1,6 +1,9 @@ +//! Handle events of a canvas. use iced_native::keyboard; use iced_native::mouse; +pub use iced_native::event::Status; + /// A [`Canvas`] event. /// /// [`Canvas`]: struct.Event.html diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index b5c6a2b1..21e9ec28 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -276,7 +276,7 @@ impl Frame { .transforms .current .raw - .pre_rotate(lyon::math::Angle::radians(-angle)); + .pre_rotate(lyon::math::Angle::radians(angle)); self.transforms.current.is_identity = false; } diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs index 725d9d72..e8f43380 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/graphics/src/widget/canvas/program.rs @@ -1,4 +1,5 @@ -use crate::canvas::{Cursor, Event, Geometry}; +use crate::canvas::event::{self, Event}; +use crate::canvas::{Cursor, Geometry}; use iced_native::{mouse, Rectangle}; /// The state and logic of a [`Canvas`]. @@ -27,8 +28,8 @@ pub trait Program<Message> { _event: Event, _bounds: Rectangle, _cursor: Cursor, - ) -> Option<Message> { - None + ) -> (event::Status, Option<Message>) { + (event::Status::Ignored, None) } /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. @@ -67,7 +68,7 @@ where event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option<Message> { + ) -> (event::Status, Option<Message>) { T::update(self, event, bounds, cursor) } diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs index 4854f589..aae3e1d8 100644 --- a/graphics/src/widget/container.rs +++ b/graphics/src/widget/container.rs @@ -62,7 +62,7 @@ pub(crate) fn background( bounds: Rectangle, style: &container::Style, ) -> Option<Primitive> { - if style.background.is_some() || style.border_width > 0 { + if style.background.is_some() || style.border_width > 0.0 { Some(Primitive::Quad { bounds, background: style diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index 5b0eb391..72a380e4 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -20,8 +20,8 @@ use iced_native::{ }; pub use iced_native::pane_grid::{ - Axis, Configuration, Content, Direction, DragEvent, Focus, KeyPressEvent, - Pane, ResizeEvent, Split, State, TitleBar, + Axis, Configuration, Content, Direction, DragEvent, Pane, ResizeEvent, + Split, State, TitleBar, }; /// A collection of panes distributed using either vertical or horizontal splits diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs index 48acb3c1..c1801a41 100644 --- a/graphics/src/widget/progress_bar.rs +++ b/graphics/src/widget/progress_bar.rs @@ -43,7 +43,7 @@ where bounds: Rectangle { ..bounds }, background: style.background, border_radius: style.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }], }; @@ -57,7 +57,7 @@ where }, background: style.bar, border_radius: style.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; 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/graphics/src/widget/radio.rs b/graphics/src/widget/radio.rs index da41ac47..fd3d8145 100644 --- a/graphics/src/widget/radio.rs +++ b/graphics/src/widget/radio.rs @@ -42,7 +42,7 @@ where let radio = Primitive::Quad { bounds, background: style.background, - border_radius: (size / 2.0) as u16, + border_radius: size / 2.0, border_width: style.border_width, border_color: style.border_color, }; @@ -58,8 +58,8 @@ where height: bounds.height - dot_size, }, background: Background::Color(style.dot_color), - border_radius: (dot_size / 2.0) as u16, - border_width: 0, + border_radius: dot_size / 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; diff --git a/graphics/src/widget/rule.rs b/graphics/src/widget/rule.rs index a7a5d0e7..835ebed8 100644 --- a/graphics/src/widget/rule.rs +++ b/graphics/src/widget/rule.rs @@ -43,7 +43,7 @@ where }, background: Background::Color(style.color), border_radius: style.radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, } } else { @@ -63,7 +63,7 @@ where }, background: Background::Color(style.color), border_radius: style.radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, } }; diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs index fed79c18..57065ba2 100644 --- a/graphics/src/widget/scrollable.rs +++ b/graphics/src/widget/scrollable.rs @@ -103,7 +103,7 @@ where }; let is_scrollbar_visible = - style.background.is_some() || style.border_width > 0; + style.background.is_some() || style.border_width > 0.0; let scroller = if is_mouse_over || state.is_scroller_grabbed() diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs index 99f0a098..051e18b8 100644 --- a/graphics/src/widget/slider.rs +++ b/graphics/src/widget/slider.rs @@ -57,8 +57,8 @@ where height: 2.0, }, background: Background::Color(style.rail_colors.0), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, Primitive::Quad { @@ -69,8 +69,8 @@ where height: 2.0, }, background: Background::Color(style.rail_colors.1), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, ); @@ -82,7 +82,7 @@ where .shape { HandleShape::Circle { radius } => { - (f32::from(radius * 2), f32::from(radius * 2), radius) + (radius * 2.0, radius * 2.0, radius) } HandleShape::Rectangle { width, diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs index 575d67f5..55eb34e4 100644 --- a/graphics/src/widget/text_input.rs +++ b/graphics/src/widget/text_input.rs @@ -149,8 +149,8 @@ where background: Background::Color( style_sheet.value_color(), ), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, offset, @@ -193,8 +193,8 @@ where background: Background::Color( style_sheet.selection_color(), ), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, if end == right { |