summaryrefslogtreecommitdiffstats
path: root/graphics/src/widget
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/src/widget')
-rw-r--r--graphics/src/widget/button.rs12
-rw-r--r--graphics/src/widget/canvas.rs37
-rw-r--r--graphics/src/widget/canvas/cache.rs10
-rw-r--r--graphics/src/widget/canvas/cursor.rs8
-rw-r--r--graphics/src/widget/canvas/event.rs5
-rw-r--r--graphics/src/widget/canvas/frame.rs39
-rw-r--r--graphics/src/widget/canvas/geometry.rs8
-rw-r--r--graphics/src/widget/canvas/path.rs11
-rw-r--r--graphics/src/widget/canvas/path/arc.rs2
-rw-r--r--graphics/src/widget/canvas/path/builder.rs29
-rw-r--r--graphics/src/widget/canvas/program.rs25
-rw-r--r--graphics/src/widget/canvas/stroke.rs10
-rw-r--r--graphics/src/widget/column.rs12
-rw-r--r--graphics/src/widget/container.rs12
-rw-r--r--graphics/src/widget/image.rs5
-rw-r--r--graphics/src/widget/image/viewer.rs55
-rw-r--r--graphics/src/widget/pane_grid.rs134
-rw-r--r--graphics/src/widget/pick_list.rs21
-rw-r--r--graphics/src/widget/progress_bar.rs15
-rw-r--r--graphics/src/widget/qr_code.rs305
-rw-r--r--graphics/src/widget/radio.rs6
-rw-r--r--graphics/src/widget/row.rs12
-rw-r--r--graphics/src/widget/rule.rs4
-rw-r--r--graphics/src/widget/scrollable.rs50
-rw-r--r--graphics/src/widget/slider.rs25
-rw-r--r--graphics/src/widget/text_input.rs11
-rw-r--r--graphics/src/widget/toggler.rs99
-rw-r--r--graphics/src/widget/tooltip.rs168
28 files changed, 871 insertions, 259 deletions
diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs
index ecabc868..60400ed8 100644
--- a/graphics/src/widget/button.rs
+++ b/graphics/src/widget/button.rs
@@ -1,14 +1,11 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-//!
-//! [`Button`]: type.Button.html
-//! [`State`]: struct.State.html
use crate::defaults::{self, Defaults};
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::{
- Background, Color, Element, Layout, Point, Rectangle, Vector,
+ Background, Color, Element, Layout, Padding, Point, Rectangle, Vector,
};
pub use iced_native::button::State;
@@ -24,7 +21,7 @@ impl<B> iced_native::button::Renderer for Renderer<B>
where
B: Backend,
{
- const DEFAULT_PADDING: u16 = 5;
+ const DEFAULT_PADDING: Padding = Padding::new(5);
type Style = Box<dyn StyleSheet>;
@@ -62,10 +59,11 @@ where
},
content_layout,
cursor_position,
+ &bounds,
);
(
- 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
@@ -92,7 +90,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 bc0802e5..7897c8ec 100644
--- a/graphics/src/widget/canvas.rs
+++ b/graphics/src/widget/canvas.rs
@@ -3,22 +3,21 @@
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more!
-//!
-//! [`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, 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;
@@ -39,8 +38,6 @@ pub use text::Text;
/// A widget capable of drawing 2D graphics.
///
-/// [`Canvas`]: struct.Canvas.html
-///
/// # Examples
/// The repository has a couple of [examples] showcasing how to use a
/// [`Canvas`]:
@@ -106,8 +103,6 @@ impl<Message, P: Program<Message>> Canvas<Message, P> {
const DEFAULT_SIZE: u16 = 100;
/// Creates a new [`Canvas`].
- ///
- /// [`Canvas`]: struct.Canvas.html
pub fn new(program: P) -> Self {
Canvas {
width: Length::Units(Self::DEFAULT_SIZE),
@@ -118,16 +113,12 @@ impl<Message, P: Program<Message>> Canvas<Message, P> {
}
/// Sets the width of the [`Canvas`].
- ///
- /// [`Canvas`]: struct.Canvas.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Canvas`].
- ///
- /// [`Canvas`]: struct.Canvas.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@@ -163,10 +154,10 @@ where
event: iced_native::Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer<B>,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
let bounds = layout.bounds();
let canvas_event = match event {
@@ -182,12 +173,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(
@@ -196,6 +192,7 @@ where
_defaults: &Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) {
let bounds = layout.bounds();
let translation = Vector::new(bounds.x, bounds.y);
diff --git a/graphics/src/widget/canvas/cache.rs b/graphics/src/widget/canvas/cache.rs
index 4b28d164..a469417d 100644
--- a/graphics/src/widget/canvas/cache.rs
+++ b/graphics/src/widget/canvas/cache.rs
@@ -23,10 +23,6 @@ impl Default for State {
///
/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
/// change or it is explicitly cleared.
-///
-/// [`Layer`]: ../trait.Layer.html
-/// [`Cache`]: struct.Cache.html
-/// [`Geometry`]: struct.Geometry.html
#[derive(Debug, Default)]
pub struct Cache {
state: RefCell<State>,
@@ -34,8 +30,6 @@ pub struct Cache {
impl Cache {
/// Creates a new empty [`Cache`].
- ///
- /// [`Cache`]: struct.Cache.html
pub fn new() -> Self {
Cache {
state: Default::default(),
@@ -43,8 +37,6 @@ impl Cache {
}
/// Clears the [`Cache`], forcing a redraw the next time it is used.
- ///
- /// [`Cache`]: struct.Cache.html
pub fn clear(&mut self) {
*self.state.borrow_mut() = State::Empty;
}
@@ -59,8 +51,6 @@ impl Cache {
/// Otherwise, the previously stored [`Geometry`] will be returned. The
/// [`Cache`] is not cleared in this case. In other words, it will keep
/// returning the stored [`Geometry`] if needed.
- ///
- /// [`Cache`]: struct.Cache.html
pub fn draw(&self, bounds: Size, draw_fn: impl Fn(&mut Frame)) -> Geometry {
use std::ops::Deref;
diff --git a/graphics/src/widget/canvas/cursor.rs b/graphics/src/widget/canvas/cursor.rs
index 456760ea..9588d129 100644
--- a/graphics/src/widget/canvas/cursor.rs
+++ b/graphics/src/widget/canvas/cursor.rs
@@ -22,8 +22,6 @@ impl Cursor {
}
/// Returns the absolute position of the [`Cursor`], if available.
- ///
- /// [`Cursor`]: enum.Cursor.html
pub fn position(&self) -> Option<Point> {
match self {
Cursor::Available(position) => Some(*position),
@@ -36,8 +34,6 @@ impl Cursor {
///
/// If the [`Cursor`] is not over the provided bounds, this method will
/// return `None`.
- ///
- /// [`Cursor`]: enum.Cursor.html
pub fn position_in(&self, bounds: &Rectangle) -> Option<Point> {
if self.is_over(bounds) {
self.position_from(bounds.position())
@@ -48,8 +44,6 @@ impl Cursor {
/// Returns the relative position of the [`Cursor`] from the given origin,
/// if available.
- ///
- /// [`Cursor`]: enum.Cursor.html
pub fn position_from(&self, origin: Point) -> Option<Point> {
match self {
Cursor::Available(position) => {
@@ -61,8 +55,6 @@ impl Cursor {
/// Returns whether the [`Cursor`] is currently over the provided bounds
/// or not.
- ///
- /// [`Cursor`]: enum.Cursor.html
pub fn is_over(&self, bounds: &Rectangle) -> bool {
match self {
Cursor::Available(position) => bounds.contains(*position),
diff --git a/graphics/src/widget/canvas/event.rs b/graphics/src/widget/canvas/event.rs
index 0e66f0ff..5bf6f7a6 100644
--- a/graphics/src/widget/canvas/event.rs
+++ b/graphics/src/widget/canvas/event.rs
@@ -1,9 +1,12 @@
+//! 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
+/// [`Canvas`]: crate::widget::Canvas
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Event {
/// A mouse event.
diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs
index b5c6a2b1..5af9d11f 100644
--- a/graphics/src/widget/canvas/frame.rs
+++ b/graphics/src/widget/canvas/frame.rs
@@ -7,7 +7,7 @@ use crate::{
/// The frame of a [`Canvas`].
///
-/// [`Canvas`]: struct.Canvas.html
+/// [`Canvas`]: crate::widget::Canvas
#[derive(Debug)]
pub struct Frame {
size: Size,
@@ -33,8 +33,6 @@ impl Frame {
///
/// The default coordinate system of a [`Frame`] has its origin at the
/// top-left corner of its bounds.
- ///
- /// [`Frame`]: struct.Frame.html
pub fn new(size: Size) -> Frame {
Frame {
size,
@@ -51,32 +49,24 @@ impl Frame {
}
/// Returns the width of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn width(&self) -> f32 {
self.size.width
}
- /// Returns the width of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
+ /// Returns the height of the [`Frame`].
#[inline]
pub fn height(&self) -> f32 {
self.size.height
}
/// Returns the dimensions of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn size(&self) -> Size {
self.size
}
/// Returns the coordinate of the center of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
@@ -84,9 +74,6 @@ impl Frame {
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
/// provided style.
- ///
- /// [`Path`]: path/struct.Path.html
- /// [`Frame`]: struct.Frame.html
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
use lyon::tessellation::{
BuffersBuilder, FillOptions, FillTessellator,
@@ -115,8 +102,6 @@ impl Frame {
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
/// its `Size` on the [`Frame`] by filling it with the provided style.
- ///
- /// [`Frame`]: struct.Frame.html
pub fn fill_rectangle(
&mut self,
top_left: Point,
@@ -152,9 +137,6 @@ impl Frame {
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
/// provided style.
- ///
- /// [`Path`]: path/struct.Path.html
- /// [`Frame`]: struct.Frame.html
pub fn stroke(&mut self, path: &Path, stroke: impl Into<Stroke>) {
use lyon::tessellation::{
BuffersBuilder, StrokeOptions, StrokeTessellator,
@@ -200,9 +182,7 @@ impl Frame {
/// Support for vectorial text is planned, and should address all these
/// limitations.
///
- /// [`Text`]: struct.Text.html
- /// [`Frame`]: struct.Frame.html
- /// [`Canvas`]: struct.Canvas.html
+ /// [`Canvas`]: crate::widget::Canvas
pub fn fill_text(&mut self, text: impl Into<Text>) {
use std::f32;
@@ -240,8 +220,6 @@ impl Frame {
///
/// This method is useful to compose transforms and perform drawing
/// operations in different coordinate systems.
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) {
self.transforms.previous.push(self.transforms.current);
@@ -252,8 +230,6 @@ impl Frame {
}
/// Applies a translation to the current transform of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn translate(&mut self, translation: Vector) {
self.transforms.current.raw = self
@@ -268,21 +244,17 @@ impl Frame {
}
/// Applies a rotation to the current transform of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn rotate(&mut self, angle: f32) {
self.transforms.current.raw = self
.transforms
.current
.raw
- .pre_rotate(lyon::math::Angle::radians(-angle));
+ .pre_rotate(lyon::math::Angle::radians(angle));
self.transforms.current.is_identity = false;
}
/// Applies a scaling to the current transform of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn scale(&mut self, scale: f32) {
self.transforms.current.raw =
@@ -291,9 +263,6 @@ impl Frame {
}
/// Produces the [`Geometry`] representing everything drawn on the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
- /// [`Geometry`]: struct.Geometry.html
pub fn into_geometry(mut self) -> Geometry {
if !self.buffers.indices.is_empty() {
self.primitives.push(Primitive::Mesh2D {
diff --git a/graphics/src/widget/canvas/geometry.rs b/graphics/src/widget/canvas/geometry.rs
index 4cadee39..8915cda1 100644
--- a/graphics/src/widget/canvas/geometry.rs
+++ b/graphics/src/widget/canvas/geometry.rs
@@ -5,9 +5,8 @@ use crate::Primitive;
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
/// [`Cache`].
///
-/// [`Geometry`]: struct.Geometry.html
-/// [`Frame`]: struct.Frame.html
-/// [`Cache`]: struct.Cache.html
+/// [`Frame`]: crate::widget::canvas::Frame
+/// [`Cache`]: crate::widget::canvas::Cache
#[derive(Debug, Clone)]
pub struct Geometry(Primitive);
@@ -19,9 +18,6 @@ impl Geometry {
/// Turns the [`Geometry`] into a [`Primitive`].
///
/// This can be useful if you are building a custom widget.
- ///
- /// [`Geometry`]: struct.Geometry.html
- /// [`Primitive`]: ../enum.Primitive.html
pub fn into_primitive(self) -> Primitive {
self.0
}
diff --git a/graphics/src/widget/canvas/path.rs b/graphics/src/widget/canvas/path.rs
index c26bf187..6de19321 100644
--- a/graphics/src/widget/canvas/path.rs
+++ b/graphics/src/widget/canvas/path.rs
@@ -12,8 +12,6 @@ use iced_native::{Point, Size};
/// An immutable set of points that may or may not be connected.
///
/// A single [`Path`] can represent different kinds of 2D shapes!
-///
-/// [`Path`]: struct.Path.html
#[derive(Debug, Clone)]
pub struct Path {
raw: lyon::path::Path,
@@ -23,9 +21,6 @@ impl Path {
/// Creates a new [`Path`] with the provided closure.
///
/// Use the [`Builder`] to configure your [`Path`].
- ///
- /// [`Path`]: struct.Path.html
- /// [`Builder`]: struct.Builder.html
pub fn new(f: impl FnOnce(&mut Builder)) -> Self {
let mut builder = Builder::new();
@@ -37,8 +32,6 @@ impl Path {
/// Creates a new [`Path`] representing a line segment given its starting
/// and end points.
- ///
- /// [`Path`]: struct.Path.html
pub fn line(from: Point, to: Point) -> Self {
Self::new(|p| {
p.move_to(from);
@@ -48,16 +41,12 @@ impl Path {
/// Creates a new [`Path`] representing a rectangle given its top-left
/// corner coordinate and its `Size`.
- ///
- /// [`Path`]: struct.Path.html
pub fn rectangle(top_left: Point, size: Size) -> Self {
Self::new(|p| p.rectangle(top_left, size))
}
/// Creates a new [`Path`] representing a circle given its center
/// coordinate and its radius.
- ///
- /// [`Path`]: struct.Path.html
pub fn circle(center: Point, radius: f32) -> Self {
Self::new(|p| p.circle(center, radius))
}
diff --git a/graphics/src/widget/canvas/path/arc.rs b/graphics/src/widget/canvas/path/arc.rs
index 343191f1..b8e72daf 100644
--- a/graphics/src/widget/canvas/path/arc.rs
+++ b/graphics/src/widget/canvas/path/arc.rs
@@ -15,8 +15,6 @@ pub struct Arc {
}
/// An elliptical [`Arc`].
-///
-/// [`Arc`]: struct.Arc.html
#[derive(Debug, Clone, Copy)]
pub struct Elliptical {
/// The center of the arc.
diff --git a/graphics/src/widget/canvas/path/builder.rs b/graphics/src/widget/canvas/path/builder.rs
index e0e52845..5ce0e02c 100644
--- a/graphics/src/widget/canvas/path/builder.rs
+++ b/graphics/src/widget/canvas/path/builder.rs
@@ -6,8 +6,6 @@ use lyon::path::builder::{Build, FlatPathBuilder, PathBuilder, SvgBuilder};
/// A [`Path`] builder.
///
/// Once a [`Path`] is built, it can no longer be mutated.
-///
-/// [`Path`]: struct.Path.html
#[allow(missing_debug_implementations)]
pub struct Builder {
raw: lyon::path::builder::SvgPathBuilder<lyon::path::Builder>,
@@ -15,8 +13,6 @@ pub struct Builder {
impl Builder {
/// Creates a new [`Builder`].
- ///
- /// [`Builder`]: struct.Builder.html
pub fn new() -> Builder {
Builder {
raw: lyon::path::Path::builder().with_svg(),
@@ -31,8 +27,6 @@ impl Builder {
/// Connects the last point in the [`Path`] to the given `Point` with a
/// straight line.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn line_to(&mut self, point: Point) {
let _ = self.raw.line_to(lyon::math::Point::new(point.x, point.y));
@@ -40,9 +34,6 @@ impl Builder {
/// Adds an [`Arc`] to the [`Path`] from `start_angle` to `end_angle` in
/// a clockwise direction.
- ///
- /// [`Arc`]: struct.Arc.html
- /// [`Path`]: struct.Path.html
#[inline]
pub fn arc(&mut self, arc: Arc) {
self.ellipse(arc.into());
@@ -53,8 +44,6 @@ impl Builder {
///
/// The arc is connected to the previous point by a straight line, if
/// necessary.
- ///
- /// [`Path`]: struct.Path.html
pub fn arc_to(&mut self, a: Point, b: Point, radius: f32) {
use lyon::{math, path};
@@ -72,10 +61,7 @@ impl Builder {
);
}
- /// Adds an [`Ellipse`] to the [`Path`] using a clockwise direction.
- ///
- /// [`Ellipse`]: struct.Arc.html
- /// [`Path`]: struct.Path.html
+ /// Adds an ellipse to the [`Path`] using a clockwise direction.
pub fn ellipse(&mut self, arc: arc::Elliptical) {
use lyon::{geom, math};
@@ -96,8 +82,6 @@ impl Builder {
/// Adds a cubic Bézier curve to the [`Path`] given its two control points
/// and its end point.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn bezier_curve_to(
&mut self,
@@ -116,8 +100,6 @@ impl Builder {
/// Adds a quadratic Bézier curve to the [`Path`] given its control point
/// and its end point.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn quadratic_curve_to(&mut self, control: Point, to: Point) {
use lyon::math;
@@ -130,8 +112,6 @@ impl Builder {
/// Adds a rectangle to the [`Path`] given its top-left corner coordinate
/// and its `Size`.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn rectangle(&mut self, top_left: Point, size: Size) {
self.move_to(top_left);
@@ -146,8 +126,6 @@ impl Builder {
/// Adds a circle to the [`Path`] given its center coordinate and its
/// radius.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn circle(&mut self, center: Point, radius: f32) {
self.arc(Arc {
@@ -160,17 +138,12 @@ impl Builder {
/// Closes the current sub-path in the [`Path`] with a straight line to
/// the starting point.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn close(&mut self) {
self.raw.close()
}
/// Builds the [`Path`] of this [`Builder`].
- ///
- /// [`Path`]: struct.Path.html
- /// [`Builder`]: struct.Builder.html
#[inline]
pub fn build(self) -> Path {
Path {
diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs
index 725d9d72..85a2f67b 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`].
@@ -6,8 +7,7 @@ use iced_native::{mouse, Rectangle};
/// A [`Program`] can mutate internal state and produce messages for an
/// application.
///
-/// [`Canvas`]: struct.Canvas.html
-/// [`Program`]: trait.Program.html
+/// [`Canvas`]: crate::widget::Canvas
pub trait Program<Message> {
/// Updates the state of the [`Program`].
///
@@ -19,16 +19,14 @@ pub trait Program<Message> {
///
/// By default, this method does and returns nothing.
///
- /// [`Program`]: trait.Program.html
- /// [`Canvas`]: struct.Canvas.html
- /// [`Event`]: enum.Event.html
+ /// [`Canvas`]: crate::widget::Canvas
fn update(
&mut self,
_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`].
@@ -36,10 +34,8 @@ pub trait Program<Message> {
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
/// [`Cache`].
///
- /// [`Program`]: trait.Program.html
- /// [`Geometry`]: struct.Geometry.html
- /// [`Frame`]: struct.Frame.html
- /// [`Cache`]: struct.Cache.html
+ /// [`Frame`]: crate::widget::canvas::Frame
+ /// [`Cache`]: crate::widget::canvas::Cache
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>;
/// Returns the current mouse interaction of the [`Program`].
@@ -47,8 +43,7 @@ pub trait Program<Message> {
/// The interaction returned will be in effect even if the cursor position
/// is out of bounds of the program's [`Canvas`].
///
- /// [`Program`]: trait.Program.html
- /// [`Canvas`]: struct.Canvas.html
+ /// [`Canvas`]: crate::widget::Canvas
fn mouse_interaction(
&self,
_bounds: Rectangle,
@@ -67,7 +62,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/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs
index 5b6fc56a..9f0449d0 100644
--- a/graphics/src/widget/canvas/stroke.rs
+++ b/graphics/src/widget/canvas/stroke.rs
@@ -16,31 +16,21 @@ pub struct Stroke {
impl Stroke {
/// Sets the color of the [`Stroke`].
- ///
- /// [`Stroke`]: struct.Stroke.html
pub fn with_color(self, color: Color) -> Stroke {
Stroke { color, ..self }
}
/// Sets the width of the [`Stroke`].
- ///
- /// [`Stroke`]: struct.Stroke.html
pub fn with_width(self, width: f32) -> Stroke {
Stroke { width, ..self }
}
/// Sets the [`LineCap`] of the [`Stroke`].
- ///
- /// [`LineCap`]: enum.LineCap.html
- /// [`Stroke`]: struct.Stroke.html
pub fn with_line_cap(self, line_cap: LineCap) -> Stroke {
Stroke { line_cap, ..self }
}
/// Sets the [`LineJoin`] of the [`Stroke`].
- ///
- /// [`LineJoin`]: enum.LineJoin.html
- /// [`Stroke`]: struct.Stroke.html
pub fn with_line_join(self, line_join: LineJoin) -> Stroke {
Stroke { line_join, ..self }
}
diff --git a/graphics/src/widget/column.rs b/graphics/src/widget/column.rs
index 6c7235c7..0cf56842 100644
--- a/graphics/src/widget/column.rs
+++ b/graphics/src/widget/column.rs
@@ -1,7 +1,7 @@
use crate::{Backend, Primitive, Renderer};
use iced_native::column;
use iced_native::mouse;
-use iced_native::{Element, Layout, Point};
+use iced_native::{Element, Layout, Point, Rectangle};
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Backend> =
@@ -17,6 +17,7 @@ where
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let mut mouse_interaction = mouse::Interaction::default();
@@ -26,8 +27,13 @@ where
.iter()
.zip(layout.children())
.map(|(child, layout)| {
- let (primitive, new_mouse_interaction) =
- child.draw(self, defaults, layout, cursor_position);
+ let (primitive, new_mouse_interaction) = child.draw(
+ self,
+ defaults,
+ layout,
+ cursor_position,
+ viewport,
+ );
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs
index 5b3a01d2..aae3e1d8 100644
--- a/graphics/src/widget/container.rs
+++ b/graphics/src/widget/container.rs
@@ -24,6 +24,7 @@ where
defaults: &Defaults,
bounds: Rectangle,
cursor_position: Point,
+ viewport: &Rectangle,
style_sheet: &Self::Style,
content: &Element<'_, Message, Self>,
content_layout: Layout<'_>,
@@ -36,8 +37,13 @@ where
},
};
- let (content, mouse_interaction) =
- content.draw(self, &defaults, content_layout, cursor_position);
+ let (content, mouse_interaction) = content.draw(
+ self,
+ &defaults,
+ content_layout,
+ cursor_position,
+ viewport,
+ );
if let Some(background) = background(bounds, &style) {
(
@@ -56,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/image.rs b/graphics/src/widget/image.rs
index 30f446e8..bdf03de3 100644
--- a/graphics/src/widget/image.rs
+++ b/graphics/src/widget/image.rs
@@ -1,11 +1,14 @@
//! Display images in your user interface.
+pub mod viewer;
+
use crate::backend::{self, Backend};
+
use crate::{Primitive, Renderer};
use iced_native::image;
use iced_native::mouse;
use iced_native::Layout;
-pub use iced_native::image::{Handle, Image};
+pub use iced_native::image::{Handle, Image, Viewer};
impl<B> image::Renderer for Renderer<B>
where
diff --git a/graphics/src/widget/image/viewer.rs b/graphics/src/widget/image/viewer.rs
new file mode 100644
index 00000000..28dffc4f
--- /dev/null
+++ b/graphics/src/widget/image/viewer.rs
@@ -0,0 +1,55 @@
+//! Zoom and pan on an image.
+use crate::backend::{self, Backend};
+use crate::{Primitive, Renderer};
+
+use iced_native::image;
+use iced_native::image::viewer;
+use iced_native::mouse;
+use iced_native::{Rectangle, Size, Vector};
+
+impl<B> viewer::Renderer for Renderer<B>
+where
+ B: Backend + backend::Image,
+{
+ fn draw(
+ &mut self,
+ state: &viewer::State,
+ bounds: Rectangle,
+ image_size: Size,
+ translation: Vector,
+ handle: image::Handle,
+ is_mouse_over: bool,
+ ) -> Self::Output {
+ (
+ {
+ Primitive::Clip {
+ bounds,
+ content: Box::new(Primitive::Translate {
+ translation,
+ content: Box::new(Primitive::Image {
+ handle,
+ bounds: Rectangle {
+ x: bounds.x,
+ y: bounds.y,
+ ..Rectangle::with_size(image_size)
+ },
+ }),
+ }),
+ offset: Vector::new(0, 0),
+ }
+ },
+ {
+ if state.is_cursor_grabbed() {
+ mouse::Interaction::Grabbing
+ } else if is_mouse_over
+ && (image_size.width > bounds.width
+ || image_size.height > bounds.height)
+ {
+ mouse::Interaction::Grab
+ } else {
+ mouse::Interaction::Idle
+ }
+ },
+ )
+ }
+}
diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs
index aa8a3f7c..92cdbb77 100644
--- a/graphics/src/widget/pane_grid.rs
+++ b/graphics/src/widget/pane_grid.rs
@@ -6,24 +6,21 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
-//! [`PaneGrid`]: type.PaneGrid.html
-use crate::backend::{self, Backend};
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::defaults;
-use crate::{Primitive, Renderer};
+use crate::{Backend, Color, Primitive, Renderer};
+use iced_native::container;
use iced_native::mouse;
use iced_native::pane_grid;
-use iced_native::text;
-use iced_native::{
- Element, HorizontalAlignment, Layout, Point, Rectangle, Vector,
- VerticalAlignment,
-};
+use iced_native::{Element, Layout, Point, Rectangle, Vector};
pub use iced_native::pane_grid::{
- Axis, Configuration, Content, Direction, DragEvent, Focus, KeyPressEvent,
- Pane, ResizeEvent, Split, State, TitleBar,
+ Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
+ ResizeEvent, Split, State, TitleBar,
};
+pub use iced_style::pane_grid::{Line, StyleSheet};
+
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
@@ -35,16 +32,20 @@ pub type PaneGrid<'a, Message, Backend> =
impl<B> pane_grid::Renderer for Renderer<B>
where
- B: Backend + backend::Text,
+ B: Backend,
{
+ type Style = Box<dyn StyleSheet>;
+
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[(Pane, Content<'_, Message, Self>)],
dragging: Option<(Pane, Point)>,
- resizing: Option<Axis>,
+ resizing: Option<(Axis, Rectangle, bool)>,
layout: Layout<'_>,
+ style_sheet: &<Self as pane_grid::Renderer>::Style,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let pane_cursor_position = if dragging.is_some() {
// TODO: Remove once cursor availability is encoded in the type
@@ -62,8 +63,13 @@ where
.zip(layout.children())
.enumerate()
.map(|(i, ((id, pane), layout))| {
- let (primitive, new_mouse_interaction) =
- pane.draw(self, defaults, layout, pane_cursor_position);
+ let (primitive, new_mouse_interaction) = pane.draw(
+ self,
+ defaults,
+ layout,
+ pane_cursor_position,
+ viewport,
+ );
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
@@ -79,7 +85,8 @@ where
})
.collect();
- let primitives = if let Some((index, layout, origin)) = dragged_pane {
+ let mut primitives = if let Some((index, layout, origin)) = dragged_pane
+ {
let pane = panes.remove(index);
let bounds = layout.bounds();
@@ -109,15 +116,62 @@ where
panes
};
+ let (primitives, mouse_interaction) =
+ if let Some((axis, split_region, is_picked)) = resizing {
+ let highlight = if is_picked {
+ style_sheet.picked_split()
+ } else {
+ style_sheet.hovered_split()
+ };
+
+ if let Some(highlight) = highlight {
+ primitives.push(Primitive::Quad {
+ bounds: match axis {
+ Axis::Horizontal => Rectangle {
+ x: split_region.x,
+ y: (split_region.y
+ + (split_region.height - highlight.width)
+ / 2.0)
+ .round(),
+ width: split_region.width,
+ height: highlight.width,
+ },
+ Axis::Vertical => Rectangle {
+ x: (split_region.x
+ + (split_region.width - highlight.width)
+ / 2.0)
+ .round(),
+ y: split_region.y,
+ width: highlight.width,
+ height: split_region.height,
+ },
+ },
+ background: highlight.color.into(),
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ });
+ }
+
+ (
+ primitives,
+ match axis {
+ Axis::Horizontal => {
+ mouse::Interaction::ResizingVertically
+ }
+ Axis::Vertical => {
+ mouse::Interaction::ResizingHorizontally
+ }
+ },
+ )
+ } else {
+ (primitives, mouse_interaction)
+ };
+
(
Primitive::Group { primitives },
if dragging.is_some() {
mouse::Interaction::Grabbing
- } else if let Some(axis) = resizing {
- match axis {
- Axis::Horizontal => mouse::Interaction::ResizingVertically,
- Axis::Vertical => mouse::Interaction::ResizingHorizontally,
- }
} else {
mouse_interaction
},
@@ -128,16 +182,17 @@ where
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
- style_sheet: &Self::Style,
+ style_sheet: &<Self as container::Renderer>::Style,
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
body: (&Element<'_, Message, Self>, Layout<'_>),
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let style = style_sheet.style();
let (body, body_layout) = body;
let (body_primitive, body_interaction) =
- body.draw(self, defaults, body_layout, cursor_position);
+ body.draw(self, defaults, body_layout, cursor_position, viewport);
let background = crate::widget::container::background(bounds, &style);
@@ -151,6 +206,7 @@ where
defaults,
title_bar_layout,
cursor_position,
+ viewport,
show_controls,
);
@@ -162,10 +218,10 @@ where
body_primitive,
],
},
- if is_over_pick_area {
- mouse::Interaction::Grab
- } else if title_bar_interaction > body_interaction {
+ if title_bar_interaction > body_interaction {
title_bar_interaction
+ } else if is_over_pick_area {
+ mouse::Interaction::Grab
} else {
body_interaction
},
@@ -188,15 +244,14 @@ where
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
- style_sheet: &Self::Style,
- title: &str,
- title_size: u16,
- title_font: Self::Font,
- title_bounds: Rectangle,
+ style_sheet: &<Self as container::Renderer>::Style,
+ content: (&Element<'_, Message, Self>, Layout<'_>),
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let style = style_sheet.style();
+ let (title_content, title_layout) = content;
let defaults = Self::Defaults {
text: defaults::Text {
@@ -206,16 +261,12 @@ where
let background = crate::widget::container::background(bounds, &style);
- let (title_primitive, _) = text::Renderer::draw(
+ let (title_primitive, title_interaction) = title_content.draw(
self,
&defaults,
- title_bounds,
- title,
- title_size,
- title_font,
- None,
- HorizontalAlignment::Left,
- VerticalAlignment::Top,
+ title_layout,
+ cursor_position,
+ viewport,
);
if let Some((controls, controls_layout)) = controls {
@@ -224,6 +275,7 @@ where
&defaults,
controls_layout,
cursor_position,
+ viewport,
);
(
@@ -234,7 +286,7 @@ where
controls_primitive,
],
},
- controls_interaction,
+ controls_interaction.max(title_interaction),
)
} else {
(
@@ -245,7 +297,7 @@ where
} else {
title_primitive
},
- mouse::Interaction::default(),
+ title_interaction,
)
}
}
diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs
index f42a8707..88a590b5 100644
--- a/graphics/src/widget/pick_list.rs
+++ b/graphics/src/widget/pick_list.rs
@@ -2,7 +2,8 @@
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::{
- mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment,
+ mouse, Font, HorizontalAlignment, Padding, Point, Rectangle,
+ VerticalAlignment,
};
use iced_style::menu;
@@ -19,7 +20,7 @@ where
{
type Style = Box<dyn StyleSheet>;
- const DEFAULT_PADDING: u16 = 5;
+ const DEFAULT_PADDING: Padding = Padding::new(5);
fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
style.menu()
@@ -30,12 +31,14 @@ where
bounds: Rectangle,
cursor_position: Point,
selected: Option<String>,
- padding: u16,
+ placeholder: Option<&str>,
+ padding: Padding,
text_size: u16,
font: Font,
style: &Box<dyn StyleSheet>,
) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position);
+ let is_selected = selected.is_some();
let style = if is_mouse_over {
style.hovered()
@@ -56,7 +59,7 @@ where
font: B::ICON_FONT,
size: bounds.height * style.icon_size,
bounds: Rectangle {
- x: bounds.x + bounds.width - f32::from(padding) * 2.0,
+ x: bounds.x + bounds.width - f32::from(padding.horizontal()),
y: bounds.center_y(),
..bounds
},
@@ -67,14 +70,18 @@ where
(
Primitive::Group {
- primitives: if let Some(label) = selected {
+ primitives: if let Some(label) =
+ selected.or_else(|| placeholder.map(str::to_string))
+ {
let label = Primitive::Text {
content: label,
size: f32::from(text_size),
font,
- color: style.text_color,
+ color: is_selected
+ .then(|| style.text_color)
+ .unwrap_or(style.placeholder_color),
bounds: Rectangle {
- x: bounds.x + f32::from(padding),
+ x: bounds.x + f32::from(padding.left),
y: bounds.center_y(),
..bounds
},
diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs
index 48acb3c1..32ee42c6 100644
--- a/graphics/src/widget/progress_bar.rs
+++ b/graphics/src/widget/progress_bar.rs
@@ -2,8 +2,6 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
-//!
-//! [`ProgressBar`]: type.ProgressBar.html
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::progress_bar;
@@ -33,17 +31,20 @@ where
style_sheet: &Self::Style,
) -> Self::Output {
let style = style_sheet.style();
-
let (range_start, range_end) = range.into_inner();
- let active_progress_width = bounds.width
- * ((value - range_start) / (range_end - range_start).max(1.0));
+
+ let active_progress_width = if range_start >= range_end {
+ 0.0
+ } else {
+ bounds.width * (value - range_start) / (range_end - range_start)
+ };
let background = Primitive::Group {
primitives: vec![Primitive::Quad {
bounds: Rectangle { ..bounds },
background: style.background,
border_radius: style.border_radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}],
};
@@ -57,7 +58,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/row.rs b/graphics/src/widget/row.rs
index 4c1dbadc..397d80bf 100644
--- a/graphics/src/widget/row.rs
+++ b/graphics/src/widget/row.rs
@@ -1,7 +1,7 @@
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::row;
-use iced_native::{Element, Layout, Point};
+use iced_native::{Element, Layout, Point, Rectangle};
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Backend> =
@@ -17,6 +17,7 @@ where
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let mut mouse_interaction = mouse::Interaction::default();
@@ -26,8 +27,13 @@ where
.iter()
.zip(layout.children())
.map(|(child, layout)| {
- let (primitive, new_mouse_interaction) =
- child.draw(self, defaults, layout, cursor_position);
+ let (primitive, new_mouse_interaction) = child.draw(
+ self,
+ defaults,
+ layout,
+ cursor_position,
+ viewport,
+ );
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
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 b149db0a..2220e4b8 100644
--- a/graphics/src/widget/scrollable.rs
+++ b/graphics/src/widget/scrollable.rs
@@ -15,9 +15,6 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
pub type Scrollable<'a, Message, Backend> =
iced_native::Scrollable<'a, Message, Renderer<Backend>>;
-const SCROLLBAR_WIDTH: u16 = 10;
-const SCROLLBAR_MARGIN: u16 = 2;
-
impl<B> scrollable::Renderer for Renderer<B>
where
B: Backend,
@@ -29,29 +26,45 @@ where
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
) -> Option<scrollable::Scrollbar> {
if content_bounds.height > bounds.height {
+ let outer_width =
+ scrollbar_width.max(scroller_width) + 2 * scrollbar_margin;
+
+ let outer_bounds = Rectangle {
+ x: bounds.x + bounds.width - outer_width as f32,
+ y: bounds.y,
+ width: outer_width as f32,
+ height: bounds.height,
+ };
+
let scrollbar_bounds = Rectangle {
x: bounds.x + bounds.width
- - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN),
+ - f32::from(outer_width / 2 + scrollbar_width / 2),
y: bounds.y,
- width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN),
+ width: scrollbar_width as f32,
height: bounds.height,
};
let ratio = bounds.height / content_bounds.height;
- let scrollbar_height = bounds.height * ratio;
+ let scroller_height = bounds.height * ratio;
let y_offset = offset as f32 * ratio;
let scroller_bounds = Rectangle {
- x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN),
+ x: bounds.x + bounds.width
+ - f32::from(outer_width / 2 + scroller_width / 2),
y: scrollbar_bounds.y + y_offset,
- width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN),
- height: scrollbar_height,
+ width: scroller_width as f32,
+ height: scroller_height,
};
Some(scrollable::Scrollbar {
+ outer_bounds,
bounds: scrollbar_bounds,
+ margin: scrollbar_margin,
scroller: scrollable::Scroller {
bounds: scroller_bounds,
},
@@ -90,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()
@@ -109,12 +122,7 @@ where
let scrollbar = if is_scrollbar_visible {
Primitive::Quad {
- bounds: Rectangle {
- x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN),
- width: scrollbar.bounds.width
- - f32::from(2 * SCROLLBAR_MARGIN),
- ..scrollbar.bounds
- },
+ bounds: scrollbar.bounds,
background: style
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
@@ -126,8 +134,16 @@ where
Primitive::None
};
+ let scroll = Primitive::Clip {
+ bounds,
+ offset: Vector::new(0, 0),
+ content: Box::new(Primitive::Group {
+ primitives: vec![scrollbar, scroller],
+ }),
+ };
+
Primitive::Group {
- primitives: vec![clip, scrollbar, scroller],
+ primitives: vec![clip, scroll],
}
} else {
content
diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs
index 99f0a098..aeceec3f 100644
--- a/graphics/src/widget/slider.rs
+++ b/graphics/src/widget/slider.rs
@@ -1,9 +1,6 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::slider;
@@ -57,8 +54,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,20 +66,18 @@ 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,
},
);
- let (range_start, range_end) = range.into_inner();
-
let (handle_width, handle_height, handle_border_radius) = match style
.handle
.shape
{
HandleShape::Circle { radius } => {
- (f32::from(radius * 2), f32::from(radius * 2), radius)
+ (radius * 2.0, radius * 2.0, radius)
}
HandleShape::Rectangle {
width,
@@ -90,8 +85,14 @@ where
} => (f32::from(width), f32::from(bounds.height), border_radius),
};
- let handle_offset = (bounds.width - handle_width)
- * ((value - range_start) / (range_end - range_start).max(1.0));
+ let (range_start, range_end) = range.into_inner();
+
+ let handle_offset = if range_start >= range_end {
+ 0.0
+ } else {
+ (bounds.width - handle_width) * (value - range_start)
+ / (range_end - range_start)
+ };
let handle = Primitive::Quad {
bounds: Rectangle {
diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs
index 575d67f5..c269022b 100644
--- a/graphics/src/widget/text_input.rs
+++ b/graphics/src/widget/text_input.rs
@@ -1,9 +1,6 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-//!
-//! [`TextInput`]: struct.TextInput.html
-//! [`State`]: struct.State.html
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::mouse;
@@ -149,8 +146,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 +190,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 {
diff --git a/graphics/src/widget/toggler.rs b/graphics/src/widget/toggler.rs
new file mode 100644
index 00000000..852d18ee
--- /dev/null
+++ b/graphics/src/widget/toggler.rs
@@ -0,0 +1,99 @@
+//! Show toggle controls using togglers.
+use crate::backend::{self, Backend};
+use crate::{Primitive, Renderer};
+use iced_native::mouse;
+use iced_native::toggler;
+use iced_native::Rectangle;
+
+pub use iced_style::toggler::{Style, StyleSheet};
+
+/// Makes sure that the border radius of the toggler looks good at every size.
+const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
+
+/// The space ratio between the background Quad and the Toggler bounds, and
+/// between the background Quad and foreground Quad.
+const SPACE_RATIO: f32 = 0.05;
+
+/// A toggler that can be toggled.
+///
+/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
+pub type Toggler<Message, Backend> =
+ iced_native::Toggler<Message, Renderer<Backend>>;
+
+impl<B> toggler::Renderer for Renderer<B>
+where
+ B: Backend + backend::Text,
+{
+ type Style = Box<dyn StyleSheet>;
+
+ const DEFAULT_SIZE: u16 = 20;
+
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ is_active: bool,
+ is_mouse_over: bool,
+ label: Option<Self::Output>,
+ style_sheet: &Self::Style,
+ ) -> Self::Output {
+ let style = if is_mouse_over {
+ style_sheet.hovered(is_active)
+ } else {
+ style_sheet.active(is_active)
+ };
+
+ let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
+ let space = SPACE_RATIO * bounds.height as f32;
+
+ let toggler_background_bounds = Rectangle {
+ x: bounds.x + space,
+ y: bounds.y + space,
+ width: bounds.width - (2.0 * space),
+ height: bounds.height - (2.0 * space),
+ };
+
+ let toggler_background = Primitive::Quad {
+ bounds: toggler_background_bounds,
+ background: style.background.into(),
+ border_radius,
+ border_width: 1.0,
+ border_color: style.background_border.unwrap_or(style.background),
+ };
+
+ let toggler_foreground_bounds = Rectangle {
+ x: bounds.x
+ + if is_active {
+ bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
+ } else {
+ 2.0 * space
+ },
+ y: bounds.y + (2.0 * space),
+ width: bounds.height - (4.0 * space),
+ height: bounds.height - (4.0 * space),
+ };
+
+ let toggler_foreground = Primitive::Quad {
+ bounds: toggler_foreground_bounds,
+ background: style.foreground.into(),
+ border_radius,
+ border_width: 1.0,
+ border_color: style.foreground_border.unwrap_or(style.foreground),
+ };
+
+ (
+ Primitive::Group {
+ primitives: match label {
+ Some((l, _)) => {
+ vec![l, toggler_background, toggler_foreground]
+ }
+ None => vec![toggler_background, toggler_foreground],
+ },
+ },
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ },
+ )
+ }
+}
diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs
new file mode 100644
index 00000000..493a6389
--- /dev/null
+++ b/graphics/src/widget/tooltip.rs
@@ -0,0 +1,168 @@
+//! Decorate content and apply alignment.
+use crate::backend::{self, Backend};
+use crate::defaults::{self, Defaults};
+use crate::{Primitive, Renderer, Vector};
+
+use iced_native::container;
+use iced_native::layout::{self, Layout};
+use iced_native::{Element, Padding, Point, Rectangle, Size, Text};
+
+/// An element decorating some content.
+///
+/// This is an alias of an `iced_native` tooltip with a default
+/// `Renderer`.
+pub type Tooltip<'a, Message, Backend> =
+ iced_native::Tooltip<'a, Message, Renderer<Backend>>;
+
+pub use iced_native::tooltip::Position;
+
+impl<B> iced_native::tooltip::Renderer for Renderer<B>
+where
+ B: Backend + backend::Text,
+{
+ const DEFAULT_PADDING: u16 = 5;
+
+ fn draw<Message>(
+ &mut self,
+ defaults: &Defaults,
+ cursor_position: Point,
+ content_layout: Layout<'_>,
+ viewport: &Rectangle,
+ content: &Element<'_, Message, Self>,
+ tooltip: &Text<Self>,
+ position: Position,
+ style_sheet: &<Self as container::Renderer>::Style,
+ gap: u16,
+ padding: u16,
+ ) -> Self::Output {
+ let (content, mouse_interaction) = content.draw(
+ self,
+ &defaults,
+ content_layout,
+ cursor_position,
+ viewport,
+ );
+
+ let bounds = content_layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ use iced_native::Widget;
+
+ let gap = f32::from(gap);
+ let style = style_sheet.style();
+
+ let defaults = Defaults {
+ text: defaults::Text {
+ color: style.text_color.unwrap_or(defaults.text.color),
+ },
+ };
+
+ let text_layout = Widget::<(), Self>::layout(
+ tooltip,
+ self,
+ &layout::Limits::new(Size::ZERO, viewport.size())
+ .pad(Padding::new(padding)),
+ );
+
+ let padding = f32::from(padding);
+ let text_bounds = text_layout.bounds();
+ let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
+ let y_center =
+ bounds.y + (bounds.height - text_bounds.height) / 2.0;
+
+ let mut tooltip_bounds = {
+ let offset = match position {
+ Position::Top => Vector::new(
+ x_center,
+ bounds.y - text_bounds.height - gap - padding,
+ ),
+ Position::Bottom => Vector::new(
+ x_center,
+ bounds.y + bounds.height + gap + padding,
+ ),
+ Position::Left => Vector::new(
+ bounds.x - text_bounds.width - gap - padding,
+ y_center,
+ ),
+ Position::Right => Vector::new(
+ bounds.x + bounds.width + gap + padding,
+ y_center,
+ ),
+ Position::FollowCursor => Vector::new(
+ cursor_position.x,
+ cursor_position.y - text_bounds.height,
+ ),
+ };
+
+ Rectangle {
+ x: offset.x - padding,
+ y: offset.y - padding,
+ width: text_bounds.width + padding * 2.0,
+ height: text_bounds.height + padding * 2.0,
+ }
+ };
+
+ if tooltip_bounds.x < viewport.x {
+ tooltip_bounds.x = viewport.x;
+ } else if viewport.x + viewport.width
+ < tooltip_bounds.x + tooltip_bounds.width
+ {
+ tooltip_bounds.x =
+ viewport.x + viewport.width - tooltip_bounds.width;
+ }
+
+ if tooltip_bounds.y < viewport.y {
+ tooltip_bounds.y = viewport.y;
+ } else if viewport.y + viewport.height
+ < tooltip_bounds.y + tooltip_bounds.height
+ {
+ tooltip_bounds.y =
+ viewport.y + viewport.height - tooltip_bounds.height;
+ }
+
+ let (tooltip, _) = Widget::<(), Self>::draw(
+ tooltip,
+ self,
+ &defaults,
+ Layout::with_offset(
+ Vector::new(
+ tooltip_bounds.x + padding,
+ tooltip_bounds.y + padding,
+ ),
+ &text_layout,
+ ),
+ cursor_position,
+ viewport,
+ );
+
+ (
+ Primitive::Group {
+ primitives: vec![
+ content,
+ Primitive::Clip {
+ bounds: *viewport,
+ offset: Vector::new(0, 0),
+ content: Box::new(
+ if let Some(background) =
+ crate::container::background(
+ tooltip_bounds,
+ &style,
+ )
+ {
+ Primitive::Group {
+ primitives: vec![background, tooltip],
+ }
+ } else {
+ tooltip
+ },
+ ),
+ },
+ ],
+ },
+ mouse_interaction,
+ )
+ } else {
+ (content, mouse_interaction)
+ }
+ }
+}