summaryrefslogtreecommitdiffstats
path: root/graphics/src/widget/canvas
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/src/widget/canvas')
-rw-r--r--graphics/src/widget/canvas/cache.rs8
-rw-r--r--graphics/src/widget/canvas/frame.rs69
-rw-r--r--graphics/src/widget/canvas/geometry.rs6
-rw-r--r--graphics/src/widget/canvas/path.rs21
-rw-r--r--graphics/src/widget/canvas/path/builder.rs65
-rw-r--r--graphics/src/widget/canvas/program.rs50
-rw-r--r--graphics/src/widget/canvas/text.rs9
7 files changed, 166 insertions, 62 deletions
diff --git a/graphics/src/widget/canvas/cache.rs b/graphics/src/widget/canvas/cache.rs
index a469417d..49873ac9 100644
--- a/graphics/src/widget/canvas/cache.rs
+++ b/graphics/src/widget/canvas/cache.rs
@@ -1,7 +1,5 @@
-use crate::{
- canvas::{Frame, Geometry},
- Primitive,
-};
+use crate::widget::canvas::{Frame, Geometry};
+use crate::Primitive;
use iced_native::Size;
use std::{cell::RefCell, sync::Arc};
@@ -37,7 +35,7 @@ impl Cache {
}
/// Clears the [`Cache`], forcing a redraw the next time it is used.
- pub fn clear(&mut self) {
+ pub fn clear(&self) {
*self.state.borrow_mut() = State::Empty;
}
diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs
index 357dfa62..516539ca 100644
--- a/graphics/src/widget/canvas/frame.rs
+++ b/graphics/src/widget/canvas/frame.rs
@@ -2,11 +2,10 @@ use std::borrow::Cow;
use iced_native::{Point, Rectangle, Size, Vector};
-use crate::{
- canvas::path,
- canvas::{Fill, Geometry, Path, Stroke, Text},
- triangle, Primitive,
-};
+use crate::triangle;
+use crate::widget::canvas::path;
+use crate::widget::canvas::{Fill, Geometry, Path, Stroke, Text};
+use crate::Primitive;
use lyon::tessellation;
@@ -110,7 +109,7 @@ impl Frame {
)
};
- let _ = result.expect("Tessellate path");
+ result.expect("Tessellate path");
}
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
@@ -141,10 +140,9 @@ impl Frame {
let options =
tessellation::FillOptions::default().with_fill_rule(rule.into());
- let _ = self
- .fill_tessellator
+ self.fill_tessellator
.tessellate_rectangle(
- &lyon::math::Rect::new(top_left, size.into()),
+ &lyon::math::Box2D::new(top_left, top_left + size),
&options,
&mut buffers,
)
@@ -189,7 +187,7 @@ impl Frame {
)
};
- let _ = result.expect("Stroke path");
+ result.expect("Stroke path");
}
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
@@ -253,6 +251,45 @@ impl Frame {
self.transforms.current = self.transforms.previous.pop().unwrap();
}
+ /// Executes the given drawing operations within a [`Rectangle`] region,
+ /// clipping any geometry that overflows its bounds. Any transformations
+ /// performed are local to the provided closure.
+ ///
+ /// This method is useful to perform drawing operations that need to be
+ /// clipped.
+ #[inline]
+ pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) {
+ let mut frame = Frame::new(region.size());
+
+ f(&mut frame);
+
+ let primitives = frame.into_primitives();
+
+ let (text, meshes) = primitives
+ .into_iter()
+ .partition(|primitive| matches!(primitive, Primitive::Text { .. }));
+
+ let translation = Vector::new(region.x, region.y);
+
+ self.primitives.push(Primitive::Group {
+ primitives: vec![
+ Primitive::Translate {
+ translation,
+ content: Box::new(Primitive::Group { primitives: meshes }),
+ },
+ Primitive::Translate {
+ translation,
+ content: Box::new(Primitive::Clip {
+ bounds: Rectangle::with_size(region.size()),
+ content: Box::new(Primitive::Group {
+ primitives: text,
+ }),
+ }),
+ },
+ ],
+ });
+ }
+
/// Applies a translation to the current transform of the [`Frame`].
#[inline]
pub fn translate(&mut self, translation: Vector) {
@@ -287,7 +324,13 @@ impl Frame {
}
/// Produces the [`Geometry`] representing everything drawn on the [`Frame`].
- pub fn into_geometry(mut self) -> Geometry {
+ pub fn into_geometry(self) -> Geometry {
+ Geometry::from_primitive(Primitive::Group {
+ primitives: self.into_primitives(),
+ })
+ }
+
+ fn into_primitives(mut self) -> Vec<Primitive> {
if !self.buffers.indices.is_empty() {
self.primitives.push(Primitive::Mesh2D {
buffers: triangle::Mesh2D {
@@ -298,9 +341,7 @@ impl Frame {
});
}
- Geometry::from_primitive(Primitive::Group {
- primitives: self.primitives,
- })
+ self.primitives
}
}
diff --git a/graphics/src/widget/canvas/geometry.rs b/graphics/src/widget/canvas/geometry.rs
index 8915cda1..e8ac621d 100644
--- a/graphics/src/widget/canvas/geometry.rs
+++ b/graphics/src/widget/canvas/geometry.rs
@@ -22,9 +22,3 @@ impl Geometry {
self.0
}
}
-
-impl From<Geometry> for Primitive {
- fn from(geometry: Geometry) -> Primitive {
- geometry.0
- }
-}
diff --git a/graphics/src/widget/canvas/path.rs b/graphics/src/widget/canvas/path.rs
index 1728f060..aeb2589e 100644
--- a/graphics/src/widget/canvas/path.rs
+++ b/graphics/src/widget/canvas/path.rs
@@ -7,10 +7,10 @@ mod builder;
pub use arc::Arc;
pub use builder::Builder;
-use crate::canvas::LineDash;
+use crate::widget::canvas::LineDash;
use iced_native::{Point, Size};
-use lyon::algorithms::walk::{walk_along_path, RepeatedPattern};
+use lyon::algorithms::walk::{walk_along_path, RepeatedPattern, WalkerEvent};
use lyon::path::iterator::PathIterator;
/// An immutable set of points that may or may not be connected.
@@ -73,22 +73,20 @@ impl Path {
pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
Path::new(|builder| {
- let segments_odd = (line_dash.segments.len() % 2 == 1).then(|| {
- [&line_dash.segments[..], &line_dash.segments[..]].concat()
- });
+ let segments_odd = (line_dash.segments.len() % 2 == 1)
+ .then(|| [line_dash.segments, line_dash.segments].concat());
let mut draw_line = false;
walk_along_path(
path.raw().iter().flattened(0.01),
0.0,
+ lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
&mut RepeatedPattern {
- callback: |position: lyon::algorithms::math::Point,
- _tangent,
- _distance| {
+ callback: |event: WalkerEvent<'_>| {
let point = Point {
- x: position.x,
- y: position.y,
+ x: event.position.x,
+ y: event.position.y,
};
if draw_line {
@@ -103,8 +101,7 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
},
index: line_dash.offset,
intervals: segments_odd
- .as_ref()
- .map(Vec::as_slice)
+ .as_deref()
.unwrap_or(line_dash.segments),
},
);
diff --git a/graphics/src/widget/canvas/path/builder.rs b/graphics/src/widget/canvas/path/builder.rs
index d04dbdde..5121aa68 100644
--- a/graphics/src/widget/canvas/path/builder.rs
+++ b/graphics/src/widget/canvas/path/builder.rs
@@ -1,4 +1,4 @@
-use crate::canvas::path::{arc, Arc, Path};
+use crate::widget::canvas::path::{arc, Arc, Path};
use iced_native::{Point, Size};
use lyon::path::builder::SvgPathBuilder;
@@ -8,7 +8,7 @@ use lyon::path::builder::SvgPathBuilder;
/// Once a [`Path`] is built, it can no longer be mutated.
#[allow(missing_debug_implementations)]
pub struct Builder {
- raw: lyon::path::builder::WithSvg<lyon::path::path::Builder>,
+ raw: lyon::path::builder::WithSvg<lyon::path::path::BuilderImpl>,
}
impl Builder {
@@ -42,22 +42,61 @@ impl Builder {
/// Adds a circular arc to the [`Path`] with the given control points and
/// radius.
///
- /// The arc is connected to the previous point by a straight line, if
- /// necessary.
+ /// This essentially draws a straight line segment from the current
+ /// position to `a`, but fits a circular arc of `radius` tangent to that
+ /// segment and tangent to the line between `a` and `b`.
+ ///
+ /// With another `.line_to(b)`, the result will be a path connecting the
+ /// starting point and `b` with straight line segments towards `a` and a
+ /// circular arc smoothing out the corner at `a`.
+ ///
+ /// See [the HTML5 specification of `arcTo`](https://html.spec.whatwg.org/multipage/canvas.html#building-paths:dom-context-2d-arcto)
+ /// for more details and examples.
pub fn arc_to(&mut self, a: Point, b: Point, radius: f32) {
use lyon::{math, path};
- let a = math::Point::new(a.x, a.y);
+ let start = self.raw.current_position();
+ let mid = math::Point::new(a.x, a.y);
+ let end = math::Point::new(b.x, b.y);
+
+ if start == mid || mid == end || radius == 0.0 {
+ let _ = self.raw.line_to(mid);
+ return;
+ }
+
+ let double_area = start.x * (mid.y - end.y)
+ + mid.x * (end.y - start.y)
+ + end.x * (start.y - mid.y);
- if self.raw.current_position() != a {
- let _ = self.raw.line_to(a);
+ if double_area == 0.0 {
+ let _ = self.raw.line_to(mid);
+ return;
}
- let _ = self.raw.arc_to(
+ let to_start = (start - mid).normalize();
+ let to_end = (end - mid).normalize();
+
+ let inner_angle = to_start.dot(to_end).acos();
+
+ let origin_angle = inner_angle / 2.0;
+
+ let origin_adjacent = radius / origin_angle.tan();
+
+ let arc_start = mid + to_start * origin_adjacent;
+ let arc_end = mid + to_end * origin_adjacent;
+
+ let sweep = to_start.cross(to_end) < 0.0;
+
+ let _ = self.raw.line_to(arc_start);
+
+ self.raw.arc_to(
math::Vector::new(radius, radius),
math::Angle::radians(0.0),
- path::ArcFlags::default(),
- math::Point::new(b.x, b.y),
+ path::ArcFlags {
+ large_arc: false,
+ sweep,
+ },
+ arc_end,
);
}
@@ -151,3 +190,9 @@ impl Builder {
}
}
}
+
+impl Default for Builder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs
index 85a2f67b..656dbfa6 100644
--- a/graphics/src/widget/canvas/program.rs
+++ b/graphics/src/widget/canvas/program.rs
@@ -1,6 +1,7 @@
-use crate::canvas::event::{self, Event};
-use crate::canvas::{Cursor, Geometry};
-use iced_native::{mouse, Rectangle};
+use crate::widget::canvas::event::{self, Event};
+use crate::widget::canvas::mouse;
+use crate::widget::canvas::{Cursor, Geometry};
+use crate::Rectangle;
/// The state and logic of a [`Canvas`].
///
@@ -8,8 +9,11 @@ use iced_native::{mouse, Rectangle};
/// application.
///
/// [`Canvas`]: crate::widget::Canvas
-pub trait Program<Message> {
- /// Updates the state of the [`Program`].
+pub trait Program<Message, Theme = iced_native::Theme> {
+ /// The internal state mutated by the [`Program`].
+ type State: Default + 'static;
+
+ /// Updates the [`State`](Self::State) of the [`Program`].
///
/// When a [`Program`] is used in a [`Canvas`], the runtime will call this
/// method for each [`Event`].
@@ -21,7 +25,8 @@ pub trait Program<Message> {
///
/// [`Canvas`]: crate::widget::Canvas
fn update(
- &mut self,
+ &self,
+ _state: &mut Self::State,
_event: Event,
_bounds: Rectangle,
_cursor: Cursor,
@@ -36,7 +41,13 @@ pub trait Program<Message> {
///
/// [`Frame`]: crate::widget::canvas::Frame
/// [`Cache`]: crate::widget::canvas::Cache
- fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>;
+ fn draw(
+ &self,
+ state: &Self::State,
+ theme: &Theme,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> Vec<Geometry>;
/// Returns the current mouse interaction of the [`Program`].
///
@@ -46,6 +57,7 @@ pub trait Program<Message> {
/// [`Canvas`]: crate::widget::Canvas
fn mouse_interaction(
&self,
+ _state: &Self::State,
_bounds: Rectangle,
_cursor: Cursor,
) -> mouse::Interaction {
@@ -53,28 +65,38 @@ pub trait Program<Message> {
}
}
-impl<T, Message> Program<Message> for &mut T
+impl<Message, Theme, T> Program<Message, Theme> for &T
where
- T: Program<Message>,
+ T: Program<Message, Theme>,
{
+ type State = T::State;
+
fn update(
- &mut self,
+ &self,
+ state: &mut Self::State,
event: Event,
bounds: Rectangle,
cursor: Cursor,
) -> (event::Status, Option<Message>) {
- T::update(self, event, bounds, cursor)
+ T::update(self, state, event, bounds, cursor)
}
- fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> {
- T::draw(self, bounds, cursor)
+ fn draw(
+ &self,
+ state: &Self::State,
+ theme: &Theme,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> Vec<Geometry> {
+ T::draw(self, state, theme, bounds, cursor)
}
fn mouse_interaction(
&self,
+ state: &Self::State,
bounds: Rectangle,
cursor: Cursor,
) -> mouse::Interaction {
- T::mouse_interaction(self, bounds, cursor)
+ T::mouse_interaction(self, state, bounds, cursor)
}
}
diff --git a/graphics/src/widget/canvas/text.rs b/graphics/src/widget/canvas/text.rs
index ab070a70..056f8204 100644
--- a/graphics/src/widget/canvas/text.rs
+++ b/graphics/src/widget/canvas/text.rs
@@ -6,7 +6,14 @@ use crate::{Color, Font, Point};
pub struct Text {
/// The contents of the text
pub content: String,
- /// The position where to begin drawing the text (top-left corner coordinates)
+ /// The position of the text relative to the alignment properties.
+ /// By default, this position will be relative to the top-left corner coordinate meaning that
+ /// if the horizontal and vertical alignments are unchanged, this property will tell where the
+ /// top-left corner of the text should be placed.
+ /// By changing the horizontal_alignment and vertical_alignment properties, you are are able to
+ /// change what part of text is placed at this positions.
+ /// For example, when the horizontal_alignment and vertical_alignment are set to Center, the
+ /// center of the text will be placed at the given position NOT the top-left coordinate.
pub position: Point,
/// The color of the text
pub color: Color,