diff options
Diffstat (limited to 'graphics/src/widget/canvas')
-rw-r--r-- | graphics/src/widget/canvas/cache.rs | 8 | ||||
-rw-r--r-- | graphics/src/widget/canvas/frame.rs | 69 | ||||
-rw-r--r-- | graphics/src/widget/canvas/geometry.rs | 6 | ||||
-rw-r--r-- | graphics/src/widget/canvas/path.rs | 21 | ||||
-rw-r--r-- | graphics/src/widget/canvas/path/builder.rs | 65 | ||||
-rw-r--r-- | graphics/src/widget/canvas/program.rs | 50 | ||||
-rw-r--r-- | graphics/src/widget/canvas/text.rs | 9 |
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, |