pix-engine 0.8.0

A cross-platform graphics/UI engine framework for simple games, visualizations, and graphics demos.
Documentation
//! Shape methods for drawing.
//!
//! Methods for drawing and interacting with shapes such as points, lines, rectangles, etc.
//!
//! Provided traits:
//!
//! - [Contains]: Defines [`contains`] for shapes containing other shapes.
//! - [Intersects]: Defines [`intersects`] for shapes intersecting other shapes.
//!
//! Provided [`PixState`] methods;
//!
//! - [`PixState::point`]: Draw a [Point] to the current canvas.
//! - [`PixState::line`]: Draw a [Line] to the current canvas.
//! - [`PixState::triangle`]: Draw a [Triangle][Tri] to the current canvas.
//! - [`PixState::square`]: Draw a square [Rect] to the current canvas.
//! - [`PixState::rounded_square`]: Draw a square [Rect] with rounded corners to the current canvas.
//! - [`PixState::rect`]: Draw a [Rect] to the current canvas.
//! - [`PixState::rounded_rect`]: Draw a [Rect] with rounded corners to the current canvas.
//! - [`PixState::quad`]: Draw a [Quad] to the current canvas.
//! - [`PixState::polygon`]: Draw a polygon defined by a set of [Point]s to the current canvas.
//! - [`PixState::wireframe`]: Draw a wireframe defined by a set vertexes to the current canvas.
//! - [`PixState::circle`]: Draw a circle [Ellipse] to the current canvas.
//! - [`PixState::ellipse`]: Draw an [Ellipse] to the current canvas.
//! - [`PixState::arc`]: Draw an arc to the current canvas.
//!
//! [`contains`]: Contains::contains
//! [`intersects`]: Intersects::intersects

use crate::{prelude::*, renderer::Rendering};
use std::iter::Iterator;

#[macro_use]
pub mod ellipse;
#[macro_use]
pub mod line;
#[macro_use]
pub mod point;
#[macro_use]
pub mod rect;
#[macro_use]
pub mod quad;
#[macro_use]
pub mod sphere;
#[macro_use]
pub mod triangle;

#[doc(inline)]
pub use ellipse::*;
#[doc(inline)]
pub use line::*;
#[doc(inline)]
pub use point::*;
#[doc(inline)]
pub use quad::*;
#[doc(inline)]
pub use rect::*;
#[doc(inline)]
pub use sphere::*;
#[doc(inline)]
pub use triangle::*;

/// Trait for shape containing operations.
pub trait Contains<S> {
    /// Returns whether this shape contains a another shape.
    fn contains(&self, shape: S) -> bool;
}

/// Trait for shape intersection operations.
pub trait Intersects<S> {
    /// The result of the intersection.
    type Result;

    /// Returns an intersection result based on the shape or `None` if there is no intersection.
    fn intersects(&self, shape: S) -> Option<Self::Result>;
}

impl PixState {
    /// Draw a [Point] to the current canvas. [`PixState::stroke`] controls whether the point is
    /// drawn or not. [`PixState::stroke_weight`] and [`PixState::fill`] have no effect.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.stroke(Color::RED);
    ///     s.point(s.mouse_pos())?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn point<P>(&mut self, p: P) -> PixResult<()>
    where
        P: Into<Point<i32>>,
    {
        if let Some(stroke) = self.settings.stroke {
            self.renderer.point(p.into(), stroke)?;
        }
        Ok(())
    }

    /// Draw a [Line] to the current canvas. [`PixState::stroke`] controls whether the line is drawn
    /// or not. [`PixState::stroke_weight`] controls the line thickness. [`PixState::fill`] has no
    /// effect.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.stroke(Color::RED);
    ///     s.line([s.pmouse_pos(), s.mouse_pos()])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn line<L>(&mut self, line: L) -> PixResult<()>
    where
        L: Into<Line<i32>>,
    {
        let s = &self.settings;
        if let Some(stroke) = s.stroke {
            self.renderer
                .line(line.into(), s.smooth, s.stroke_weight as u8, stroke)?;
        }
        Ok(())
    }

    /// Draw a cubic Bezier curve to the current canvas. [`PixState::stroke`] controls whether the
    /// line is drawn or not. [`PixState::bezier_detail`] controls the resolution of the
    /// curve. [`PixState::fill`] has no effect.
    ///
    /// The first and last points are the anchor points of the curve, while the middle points are
    /// the control points that "pull" the curve towards them.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.stroke(Color::RED);
    ///     s.bezier([[85, 20], [10, 10], [90, 90], [15, 80]])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn bezier<P, I>(&mut self, points: I) -> PixResult<()>
    where
        P: Into<Point<i32>>,
        I: IntoIterator<Item = P>,
    {
        let s = &self.settings;
        self.renderer.bezier(
            points.into_iter().map(Into::into),
            s.bezier_detail,
            s.stroke,
        )
    }

    /// Draw a [Triangle][Tri] to the current canvas. [`PixState::fill`] and [`PixState::stroke`]
    /// control whether the triangle is filled or outlined.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.triangle(tri!([10, 0], [-5, 5], [5, 5]))?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn triangle<T>(&mut self, tri: T) -> PixResult<()>
    where
        T: Into<Tri<i32>>,
    {
        let s = &self.settings;
        self.renderer
            .triangle(tri.into(), s.smooth, s.fill, s.stroke)
    }

    /// Draw a square [Rect] to the current canvas. [`PixState::fill`] and [`PixState::stroke`] control
    /// whether the square is filled or outlined. [`RectMode`] controls how the `(x, y)` position is
    /// interpreted.
    ///
    /// Alias for [`PixState::rect`].
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.square(square![s.mouse_pos(), 100])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[doc(alias = "rect")]
    pub fn square<R>(&mut self, square: R) -> PixResult<()>
    where
        R: Into<Rect<i32>>,
    {
        self.rect(square)
    }

    /// Draw a rounded [Square](Rect) to the current canvas. [`PixState::fill`] and
    /// [`PixState::stroke`] control whether the square is filled or outlined. [`RectMode`] controls
    /// how the `(x, y)` position is interpreted.
    ///
    /// Alias for [`PixState::rounded_rect`].
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.rounded_square(square![s.mouse_pos(), 100], 10)?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[doc(alias = "rounded_rect")]
    pub fn rounded_square<R>(&mut self, square: R, radius: i32) -> PixResult<()>
    where
        R: Into<Rect<i32>>,
    {
        self.rounded_rect(square, radius)
    }

    /// Draw a [Rectangle](Rect) to the current canvas. [`PixState::fill`] and [`PixState::stroke`]
    /// control whether the rect is filled or outlined. [`RectMode`] controls how the `(x, y)`
    /// position is interpreted.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.rect(rect![s.mouse_pos(), 100, 100])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn rect<R>(&mut self, rect: R) -> PixResult<()>
    where
        R: Into<Rect<i32>>,
    {
        let s = &self.settings;
        let rect = self.get_rect(rect);
        self.renderer.rect(rect, None, s.fill, s.stroke)
    }

    /// Draw a rounded [Rectangle](Rect) to the current canvas. [`PixState::fill`] and
    /// [`PixState::stroke`] control whether the rect is filled or outlined. [`RectMode`] controls how
    /// the `(x, y)` position is interpreted.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.rounded_rect(rect![s.mouse_pos(), 100, 100], 10)?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn rounded_rect<R>(&mut self, rect: R, radius: i32) -> PixResult<()>
    where
        R: Into<Rect<i32>>,
    {
        let s = &self.settings;
        let rect = self.get_rect(rect);
        self.renderer.rect(rect, Some(radius), s.fill, s.stroke)
    }

    /// Draw a [Quadrilateral](Quad) to the current canvas. [`PixState::fill`] and
    /// [`PixState::stroke`] control whether the quad is filled or outlined. [`RectMode`] has no
    /// effect.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.quad(quad![10, 20, 30, 10, 20, 25, 15, 15])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn quad<Q>(&mut self, quad: Q) -> PixResult<()>
    where
        Q: Into<Quad<i32>>,
    {
        let s = &self.settings;
        self.renderer.quad(quad.into(), s.smooth, s.fill, s.stroke)
    }

    /// Draw a polygon to the current canvas. [`PixState::fill`] and [`PixState::stroke`] control
    /// whether the polygon is filled or outlined. [`RectMode`] has no effect.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.polygon([[10, 10], [50, 20], [70, 30], [60, 50], [10, 50]])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn polygon<P, I>(&mut self, points: I) -> PixResult<()>
    where
        P: Into<Point<i32>>,
        I: IntoIterator<Item = P>,
    {
        let s = &self.settings;
        self.renderer.polygon(
            points.into_iter().map(Into::into),
            s.smooth,
            s.fill,
            s.stroke,
        )
    }

    /// Draw a wireframe to the current canvas, translated to a given [Point] and optionally
    /// rotated by `angle` and `scaled`. [`PixState::fill`] and [`PixState::stroke`] control whether
    /// the wireframe is filled or outlined. `angle` can be in either radians or degrees based on
    /// [`AngleMode`].
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     let wireframe = [
    ///         point!(5.0, 0.0),
    ///         point!(-2.5, -2.5),
    ///         point!(-2.5, 2.5)
    ///     ];
    ///     s.fill(Color::CADET_BLUE);
    ///     s.stroke(Color::BLACK);
    ///     s.angle_mode(AngleMode::Degrees);
    ///     s.wireframe(wireframe, [10, 10], 45.0, 2.0)?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn wireframe<V, P1, P2, A, S>(
        &mut self,
        vertexes: V,
        pos: P2,
        angle: A,
        scale: S,
    ) -> PixResult<()>
    where
        V: IntoIterator<Item = P1>,
        P1: Into<Point<f64>>,
        P2: Into<Point<i32>>,
        A: Into<Option<f64>>,
        S: Into<Option<f64>>,
    {
        let s = &self.settings;
        let pos = pos.into();
        let mut angle = angle.into().unwrap_or(0.0);
        if s.angle_mode == AngleMode::Degrees {
            angle = angle.to_radians();
        };
        let scale = scale.into().unwrap_or(1.0);
        let (sin, cos) = angle.sin_cos();
        let (px, py) = (f64::from(pos.x()), f64::from(pos.y()));
        let vs = vertexes.into_iter().map(|v| {
            let v = v.into();
            // rotation * scale + translation
            let x = (v.x() * cos - v.y() * sin).mul_add(scale, px).round() as i32;
            let y = (v.x().mul_add(sin, v.y() * cos)).mul_add(scale, py).round() as i32;
            point![x, y]
        });
        self.polygon(vs)
    }

    /// Draw a circle [Ellipse] to the current canvas. [`PixState::fill`] and [`PixState::stroke`]
    /// control whether the circle is filled or outlined. [`EllipseMode`] controls how the `(x, y)`
    /// position is interpreted.
    ///
    /// Alias for [`PixState::ellipse`].
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.circle(circle![s.mouse_pos(), 100])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[doc(alias = "ellipse")]
    pub fn circle<C>(&mut self, circle: C) -> PixResult<()>
    where
        C: Into<Ellipse<i32>>,
    {
        self.ellipse(circle)
    }

    /// Draw a [Ellipse] to the current canvas. [`PixState::fill`] and [`PixState::stroke`] control
    /// whether the ellipse is filled or outlined. [`EllipseMode`] controls how the `(x, y)` position
    /// is interpreted.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.ellipse(ellipse![s.mouse_pos(), 100, 100])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn ellipse<E>(&mut self, ellipse: E) -> PixResult<()>
    where
        E: Into<Ellipse<i32>>,
    {
        let s = &self.settings;
        let ellipse = self.get_ellipse(ellipse);
        self.renderer.ellipse(ellipse, s.smooth, s.fill, s.stroke)
    }

    /// Draw an arc of a given `radius` and length defined by `start` and `end` to the current
    /// canvas. [`PixState::fill`] and [`PixState::stroke`] control whether the pie is filled or
    /// outlined. [`ArcMode`] changes whether the arc is drawn as an open segment or a pie shape.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.fill(Color::BLACK);
    ///     s.stroke(Color::RED);
    ///     s.arc_mode(ArcMode::Pie);
    ///     s.arc(s.mouse_pos(), 20, 0, 180)?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    pub fn arc<P>(&mut self, p: P, radius: i32, start: i32, end: i32) -> PixResult<()>
    where
        P: Into<Point<i32>>,
    {
        let s = &self.settings;
        let p = p.into();
        self.renderer
            .arc(p, radius, start, end, s.arc_mode, s.fill, s.stroke)
    }
}