diff options
Diffstat (limited to 'graphics')
| -rw-r--r-- | graphics/Cargo.toml | 6 | ||||
| -rw-r--r-- | graphics/src/backend.rs | 2 | ||||
| -rw-r--r-- | graphics/src/geometry.rs | 36 | ||||
| -rw-r--r-- | graphics/src/geometry/fill.rs | 63 | ||||
| -rw-r--r-- | graphics/src/geometry/path.rs | 67 | ||||
| -rw-r--r-- | graphics/src/geometry/path/arc.rs | 42 | ||||
| -rw-r--r-- | graphics/src/geometry/path/builder.rs | 192 | ||||
| -rw-r--r-- | graphics/src/geometry/stroke.rs | 106 | ||||
| -rw-r--r-- | graphics/src/geometry/style.rs | 23 | ||||
| -rw-r--r-- | graphics/src/geometry/text.rs | 57 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 6 | ||||
| -rw-r--r-- | graphics/src/renderer.rs | 8 | 
12 files changed, 601 insertions, 7 deletions
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 62e67cf8..98e6f474 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -24,7 +24,7 @@ bmp = ["image_rs/bmp"]  hdr = ["image_rs/hdr"]  dds = ["image_rs/dds"]  farbfeld = ["image_rs/farbfeld"] -canvas = ["iced_native/canvas"] +geometry = ["lyon_path"]  opengl = []  image_rs = ["kamadak-exif"] @@ -65,6 +65,10 @@ optional = true  version = "0.5"  optional = true +[dependencies.lyon_path] +version = "1" +optional = true +  [package.metadata.docs.rs]  rustdoc-args = ["--cfg", "docsrs"]  all-features = true diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index c44372e8..8658cffe 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -10,8 +10,6 @@ use std::borrow::Cow;  ///  /// [`Renderer`]: crate::Renderer  pub trait Backend { -    type Geometry: Into<crate::Primitive>; -      /// Trims the measurements cache.      ///      /// This method is currently necessary to properly trim the text cache in diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs new file mode 100644 index 00000000..29ac84d6 --- /dev/null +++ b/graphics/src/geometry.rs @@ -0,0 +1,36 @@ +//! Draw 2D graphics for your users. +//! +//! 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! +pub mod fill; +pub mod path; +pub mod stroke; + +mod style; +mod text; + +pub use fill::Fill; +pub use path::Path; +pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; +pub use style::Style; +pub use text::Text; + +pub use iced_native::gradient::{self, Gradient}; + +use crate::Primitive; + +#[derive(Debug, Clone)] +pub struct Geometry(pub Primitive); + +impl From<Geometry> for Primitive { +    fn from(geometry: Geometry) -> Self { +        geometry.0 +    } +} + +pub trait Renderer: iced_native::Renderer { +    type Geometry; + +    fn draw(&mut self, geometry: Vec<Self::Geometry>); +} diff --git a/graphics/src/geometry/fill.rs b/graphics/src/geometry/fill.rs new file mode 100644 index 00000000..109d5e99 --- /dev/null +++ b/graphics/src/geometry/fill.rs @@ -0,0 +1,63 @@ +//! Fill [crate::widget::canvas::Geometry] with a certain style. +use crate::{Color, Gradient}; + +pub use crate::geometry::Style; + +/// The style used to fill geometry. +#[derive(Debug, Clone)] +pub struct Fill { +    /// The color or gradient of the fill. +    /// +    /// By default, it is set to [`Style::Solid`] with [`Color::BLACK`]. +    pub style: Style, + +    /// The fill rule defines how to determine what is inside and what is +    /// outside of a shape. +    /// +    /// See the [SVG specification][1] for more details. +    /// +    /// By default, it is set to `NonZero`. +    /// +    /// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty +    pub rule: Rule, +} + +impl Default for Fill { +    fn default() -> Self { +        Self { +            style: Style::Solid(Color::BLACK), +            rule: Rule::NonZero, +        } +    } +} + +impl From<Color> for Fill { +    fn from(color: Color) -> Fill { +        Fill { +            style: Style::Solid(color), +            ..Fill::default() +        } +    } +} + +impl From<Gradient> for Fill { +    fn from(gradient: Gradient) -> Self { +        Fill { +            style: Style::Gradient(gradient), +            ..Default::default() +        } +    } +} + +/// The fill rule defines how to determine what is inside and what is outside of +/// a shape. +/// +/// See the [SVG specification][1]. +/// +/// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(missing_docs)] +pub enum Rule { +    NonZero, +    EvenOdd, +} diff --git a/graphics/src/geometry/path.rs b/graphics/src/geometry/path.rs new file mode 100644 index 00000000..30c387c5 --- /dev/null +++ b/graphics/src/geometry/path.rs @@ -0,0 +1,67 @@ +//! Build different kinds of 2D shapes. +pub mod arc; + +mod builder; + +#[doc(no_inline)] +pub use arc::Arc; +pub use builder::Builder; + +pub use lyon_path; + +use crate::{Point, Size}; + +/// An immutable set of points that may or may not be connected. +/// +/// A single [`Path`] can represent different kinds of 2D shapes! +#[derive(Debug, Clone)] +pub struct Path { +    raw: lyon_path::Path, +} + +impl Path { +    /// Creates a new [`Path`] with the provided closure. +    /// +    /// Use the [`Builder`] to configure your [`Path`]. +    pub fn new(f: impl FnOnce(&mut Builder)) -> Self { +        let mut builder = Builder::new(); + +        // TODO: Make it pure instead of side-effect-based (?) +        f(&mut builder); + +        builder.build() +    } + +    /// Creates a new [`Path`] representing a line segment given its starting +    /// and end points. +    pub fn line(from: Point, to: Point) -> Self { +        Self::new(|p| { +            p.move_to(from); +            p.line_to(to); +        }) +    } + +    /// Creates a new [`Path`] representing a rectangle given its top-left +    /// corner coordinate and its `Size`. +    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. +    pub fn circle(center: Point, radius: f32) -> Self { +        Self::new(|p| p.circle(center, radius)) +    } + +    #[inline] +    pub fn raw(&self) -> &lyon_path::Path { +        &self.raw +    } + +    #[inline] +    pub fn transform(&self, transform: &lyon_path::math::Transform) -> Path { +        Path { +            raw: self.raw.clone().transformed(transform), +        } +    } +} diff --git a/graphics/src/geometry/path/arc.rs b/graphics/src/geometry/path/arc.rs new file mode 100644 index 00000000..e0747d3e --- /dev/null +++ b/graphics/src/geometry/path/arc.rs @@ -0,0 +1,42 @@ +//! Build and draw curves. +use crate::{Point, Vector}; + +/// A segment of a differentiable curve. +#[derive(Debug, Clone, Copy)] +pub struct Arc { +    /// The center of the arc. +    pub center: Point, +    /// The radius of the arc. +    pub radius: f32, +    /// The start of the segment's angle, clockwise rotation. +    pub start_angle: f32, +    /// The end of the segment's angle, clockwise rotation. +    pub end_angle: f32, +} + +/// An elliptical [`Arc`]. +#[derive(Debug, Clone, Copy)] +pub struct Elliptical { +    /// The center of the arc. +    pub center: Point, +    /// The radii of the arc's ellipse, defining its axes. +    pub radii: Vector, +    /// The rotation of the arc's ellipse. +    pub rotation: f32, +    /// The start of the segment's angle, clockwise rotation. +    pub start_angle: f32, +    /// The end of the segment's angle, clockwise rotation. +    pub end_angle: f32, +} + +impl From<Arc> for Elliptical { +    fn from(arc: Arc) -> Elliptical { +        Elliptical { +            center: arc.center, +            radii: Vector::new(arc.radius, arc.radius), +            rotation: 0.0, +            start_angle: arc.start_angle, +            end_angle: arc.end_angle, +        } +    } +} diff --git a/graphics/src/geometry/path/builder.rs b/graphics/src/geometry/path/builder.rs new file mode 100644 index 00000000..4a9c5e36 --- /dev/null +++ b/graphics/src/geometry/path/builder.rs @@ -0,0 +1,192 @@ +use crate::geometry::path::{arc, Arc, Path}; +use crate::{Point, Size}; + +use lyon_path::builder::{self, SvgPathBuilder}; +use lyon_path::geom; +use lyon_path::math; + +/// A [`Path`] builder. +/// +/// Once a [`Path`] is built, it can no longer be mutated. +#[allow(missing_debug_implementations)] +pub struct Builder { +    raw: builder::WithSvg<lyon_path::path::BuilderImpl>, +} + +impl Builder { +    /// Creates a new [`Builder`]. +    pub fn new() -> Builder { +        Builder { +            raw: lyon_path::Path::builder().with_svg(), +        } +    } + +    /// Moves the starting point of a new sub-path to the given `Point`. +    #[inline] +    pub fn move_to(&mut self, point: Point) { +        let _ = self.raw.move_to(math::Point::new(point.x, point.y)); +    } + +    /// Connects the last point in the [`Path`] to the given `Point` with a +    /// straight line. +    #[inline] +    pub fn line_to(&mut self, point: Point) { +        let _ = self.raw.line_to(math::Point::new(point.x, point.y)); +    } + +    /// Adds an [`Arc`] to the [`Path`] from `start_angle` to `end_angle` in +    /// a clockwise direction. +    #[inline] +    pub fn arc(&mut self, arc: Arc) { +        self.ellipse(arc.into()); +    } + +    /// Adds a circular arc to the [`Path`] with the given control points and +    /// radius. +    /// +    /// 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) { +        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 double_area == 0.0 { +            let _ = self.raw.line_to(mid); +            return; +        } + +        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), +            lyon_path::ArcFlags { +                large_arc: false, +                sweep, +            }, +            arc_end, +        ); +    } + +    /// Adds an ellipse to the [`Path`] using a clockwise direction. +    pub fn ellipse(&mut self, arc: arc::Elliptical) { +        let arc = geom::Arc { +            center: math::Point::new(arc.center.x, arc.center.y), +            radii: math::Vector::new(arc.radii.x, arc.radii.y), +            x_rotation: math::Angle::radians(arc.rotation), +            start_angle: math::Angle::radians(arc.start_angle), +            sweep_angle: math::Angle::radians(arc.end_angle - arc.start_angle), +        }; + +        let _ = self.raw.move_to(arc.sample(0.0)); + +        arc.for_each_quadratic_bezier(&mut |curve| { +            let _ = self.raw.quadratic_bezier_to(curve.ctrl, curve.to); +        }); +    } + +    /// Adds a cubic Bézier curve to the [`Path`] given its two control points +    /// and its end point. +    #[inline] +    pub fn bezier_curve_to( +        &mut self, +        control_a: Point, +        control_b: Point, +        to: Point, +    ) { +        let _ = self.raw.cubic_bezier_to( +            math::Point::new(control_a.x, control_a.y), +            math::Point::new(control_b.x, control_b.y), +            math::Point::new(to.x, to.y), +        ); +    } + +    /// Adds a quadratic Bézier curve to the [`Path`] given its control point +    /// and its end point. +    #[inline] +    pub fn quadratic_curve_to(&mut self, control: Point, to: Point) { +        let _ = self.raw.quadratic_bezier_to( +            math::Point::new(control.x, control.y), +            math::Point::new(to.x, to.y), +        ); +    } + +    /// Adds a rectangle to the [`Path`] given its top-left corner coordinate +    /// and its `Size`. +    #[inline] +    pub fn rectangle(&mut self, top_left: Point, size: Size) { +        self.move_to(top_left); +        self.line_to(Point::new(top_left.x + size.width, top_left.y)); +        self.line_to(Point::new( +            top_left.x + size.width, +            top_left.y + size.height, +        )); +        self.line_to(Point::new(top_left.x, top_left.y + size.height)); +        self.close(); +    } + +    /// Adds a circle to the [`Path`] given its center coordinate and its +    /// radius. +    #[inline] +    pub fn circle(&mut self, center: Point, radius: f32) { +        self.arc(Arc { +            center, +            radius, +            start_angle: 0.0, +            end_angle: 2.0 * std::f32::consts::PI, +        }); +    } + +    /// Closes the current sub-path in the [`Path`] with a straight line to +    /// the starting point. +    #[inline] +    pub fn close(&mut self) { +        self.raw.close() +    } + +    /// Builds the [`Path`] of this [`Builder`]. +    #[inline] +    pub fn build(self) -> Path { +        Path { +            raw: self.raw.build(), +        } +    } +} + +impl Default for Builder { +    fn default() -> Self { +        Self::new() +    } +} diff --git a/graphics/src/geometry/stroke.rs b/graphics/src/geometry/stroke.rs new file mode 100644 index 00000000..b551a9c9 --- /dev/null +++ b/graphics/src/geometry/stroke.rs @@ -0,0 +1,106 @@ +//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. +pub use crate::geometry::Style; + +use crate::Color; + +/// The style of a stroke. +#[derive(Debug, Clone)] +pub struct Stroke<'a> { +    /// The color or gradient of the stroke. +    /// +    /// By default, it is set to a [`Style::Solid`] with [`Color::BLACK`]. +    pub style: Style, +    /// The distance between the two edges of the stroke. +    pub width: f32, +    /// The shape to be used at the end of open subpaths when they are stroked. +    pub line_cap: LineCap, +    /// The shape to be used at the corners of paths or basic shapes when they +    /// are stroked. +    pub line_join: LineJoin, +    /// The dash pattern used when stroking the line. +    pub line_dash: LineDash<'a>, +} + +impl<'a> Stroke<'a> { +    /// Sets the color of the [`Stroke`]. +    pub fn with_color(self, color: Color) -> Self { +        Stroke { +            style: Style::Solid(color), +            ..self +        } +    } + +    /// Sets the width of the [`Stroke`]. +    pub fn with_width(self, width: f32) -> Self { +        Stroke { width, ..self } +    } + +    /// Sets the [`LineCap`] of the [`Stroke`]. +    pub fn with_line_cap(self, line_cap: LineCap) -> Self { +        Stroke { line_cap, ..self } +    } + +    /// Sets the [`LineJoin`] of the [`Stroke`]. +    pub fn with_line_join(self, line_join: LineJoin) -> Self { +        Stroke { line_join, ..self } +    } +} + +impl<'a> Default for Stroke<'a> { +    fn default() -> Self { +        Stroke { +            style: Style::Solid(Color::BLACK), +            width: 1.0, +            line_cap: LineCap::default(), +            line_join: LineJoin::default(), +            line_dash: LineDash::default(), +        } +    } +} + +/// The shape used at the end of open subpaths when they are stroked. +#[derive(Debug, Clone, Copy)] +pub enum LineCap { +    /// The stroke for each sub-path does not extend beyond its two endpoints. +    Butt, +    /// At the end of each sub-path, the shape representing the stroke will be +    /// extended by a square. +    Square, +    /// At the end of each sub-path, the shape representing the stroke will be +    /// extended by a semicircle. +    Round, +} + +impl Default for LineCap { +    fn default() -> LineCap { +        LineCap::Butt +    } +} + +/// The shape used at the corners of paths or basic shapes when they are +/// stroked. +#[derive(Debug, Clone, Copy)] +pub enum LineJoin { +    /// A sharp corner. +    Miter, +    /// A round corner. +    Round, +    /// A bevelled corner. +    Bevel, +} + +impl Default for LineJoin { +    fn default() -> LineJoin { +        LineJoin::Miter +    } +} + +/// The dash pattern used when stroking the line. +#[derive(Debug, Clone, Copy, Default)] +pub struct LineDash<'a> { +    /// The alternating lengths of lines and gaps which describe the pattern. +    pub segments: &'a [f32], + +    /// The offset of [`LineDash::segments`] to start the pattern. +    pub offset: usize, +} diff --git a/graphics/src/geometry/style.rs b/graphics/src/geometry/style.rs new file mode 100644 index 00000000..6794f2e7 --- /dev/null +++ b/graphics/src/geometry/style.rs @@ -0,0 +1,23 @@ +use crate::{Color, Gradient}; + +/// The coloring style of some drawing. +#[derive(Debug, Clone, PartialEq)] +pub enum Style { +    /// A solid [`Color`]. +    Solid(Color), + +    /// A [`Gradient`] color. +    Gradient(Gradient), +} + +impl From<Color> for Style { +    fn from(color: Color) -> Self { +        Self::Solid(color) +    } +} + +impl From<Gradient> for Style { +    fn from(gradient: Gradient) -> Self { +        Self::Gradient(gradient) +    } +} diff --git a/graphics/src/geometry/text.rs b/graphics/src/geometry/text.rs new file mode 100644 index 00000000..8c0b2dfb --- /dev/null +++ b/graphics/src/geometry/text.rs @@ -0,0 +1,57 @@ +use crate::alignment; +use crate::{Color, Font, Point}; + +/// A bunch of text that can be drawn to a canvas +#[derive(Debug, Clone)] +pub struct Text { +    /// The contents of the text +    pub content: String, +    /// 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, +    /// The size of the text +    pub size: f32, +    /// The font of the text +    pub font: Font, +    /// The horizontal alignment of the text +    pub horizontal_alignment: alignment::Horizontal, +    /// The vertical alignment of the text +    pub vertical_alignment: alignment::Vertical, +} + +impl Default for Text { +    fn default() -> Text { +        Text { +            content: String::new(), +            position: Point::ORIGIN, +            color: Color::BLACK, +            size: 16.0, +            font: Font::SansSerif, +            horizontal_alignment: alignment::Horizontal::Left, +            vertical_alignment: alignment::Vertical::Top, +        } +    } +} + +impl From<String> for Text { +    fn from(content: String) -> Text { +        Text { +            content, +            ..Default::default() +        } +    } +} + +impl From<&str> for Text { +    fn from(content: &str) -> Text { +        String::from(content).into() +    } +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 576b2d78..e56f8ad8 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -33,6 +33,9 @@ pub mod primitive;  pub mod renderer;  pub mod window; +#[cfg(feature = "geometry")] +pub mod geometry; +  pub use antialiasing::Antialiasing;  pub use backend::Backend;  pub use error::Error; @@ -41,6 +44,9 @@ pub use renderer::Renderer;  pub use transformation::Transformation;  pub use viewport::Viewport; +#[cfg(feature = "geometry")] +pub use geometry::Geometry; +  pub use iced_native::alignment;  pub use iced_native::text;  pub use iced_native::{ diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 793ee7d7..cb57f429 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -215,15 +215,15 @@ where      }  } -#[cfg(feature = "canvas")] -impl<B, T> iced_native::widget::canvas::Renderer for Renderer<B, T> +#[cfg(feature = "geometry")] +impl<B, T> crate::geometry::Renderer for Renderer<B, T>  where      B: Backend,  { -    type Geometry = B::Geometry; +    type Geometry = crate::Geometry;      fn draw(&mut self, layers: Vec<Self::Geometry>) {          self.primitives -            .extend(layers.into_iter().map(B::Geometry::into)); +            .extend(layers.into_iter().map(crate::Geometry::into));      }  }  | 
