sketchbook 0.0.2

Interactive visual applications in Rust
Documentation
use super::{Context, FromWithContext};
use crate::{Point2, Real, ToReal, Size2};

/// Radius of rectangle's corners.
///
/// ```
/// # use sketchbook::aspects::draw::CornerRadius;
/// #
/// // Example of creating a per-corner.
/// let radius = CornerRadius::PerCorner {
///     top_left: 1.0,
///     top_right: 2.0,
///     bottom_right: 3.0,
///     bottom_left: 4.0,
/// };
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum CornerRadius<Num> {
    /// Uniform radius for all corners.
    Uniform(Num),

    /// Radius for each corner.
    PerCorner {
        /// Top left corner.
        top_left: Num,
        /// Top right corner.
        top_right: Num,
        /// Bottom right corner.
        bottom_right: Num,
        /// Bottom left corner.
        bottom_left: Num,
    },
}

/// A rectangle to draw.
///
/// ```
/// # use sketchbook::aspects::draw::Rectangle;
/// # use sketchbook::aspects::draw::CornerRadius;
/// # use sketchbook::Point2;
/// # use sketchbook::Size2;
/// #
/// // Example of creating a rectangle.
/// let rectangle = Rectangle {
///     center: Point2 { x: 1.0, y: 2.0 },
///     size: Size2 { width: 3.0, height: 4.0 },
///     fill: (255, 0, 0),
///     stroke: (0, 255, 0),
///     weight: 23.0,
///     rotation: 2.0,
///     corner_radius: Some(CornerRadius::Uniform(4.0)),
/// };
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Rectangle<Num, Color> {
    /// Position of rectangle's center
    pub center: Point2<Num>,

    /// Rectangle's size
    pub size: Size2<Num>,

    /// Radius of corners
    pub corner_radius: Option<CornerRadius<Num>>,

    /// Color to fill rectangle with
    pub fill: Color,

    /// Color for border
    pub stroke: Color,

    /// Width of the border
    pub weight: Num,

    /// Angle of rotation
    pub rotation: Num,
}

/// Convert from `(x, y, width, height)` to a rectangle with the given context.
///
/// The interpretation of `x`, `y`, `width`, and `height` are dependant on the context's `rectangle_mode`.
///
/// ```
/// # use sketchbook::aspects::draw::*;
/// # use sketchbook::Point2;
/// # use sketchbook::Size2;
/// #
/// // Create context to create rectangle in releation to.
/// let context = Context {
///     rectangle_mode: Mode::Center,
///     fill: (255, 0, 0),
///     stroke: (0, 0, 255),
///     weight: 4.0,
///     ..Context::default()
/// };
///
/// // Create rectangle.
/// let rect: Rectangle<f32, (u8, u8, u8)>
///     = (1.0, 2.0, 3.0, 4.0).into_with_context(&context);
///
/// // Created circle matches what is expected given the context.
/// assert_eq!(
///     rect,
///     Rectangle {
///         center: Point2 { x: 1.0, y: 2.0 },
///         size: Size2 { width: 3.0, height: 4.0 },
///         fill: (255, 0, 0),
///         stroke: (0, 0, 255),
///         weight: 4.0,
///         rotation: 0.0,
///         corner_radius: None,
///     }
/// );
/// ```
impl<X, Y, Width, Height, Num, Color> FromWithContext<(X, Y, Width, Height), Num, Color>
    for Rectangle<Num, Color>
where
    X: ToReal<Num>,
    Y: ToReal<Num>,
    Width: ToReal<Num>,
    Height: ToReal<Num>,
    Num: Real,
    Color: Clone,
{
    fn from_with_context(data: (X, Y, Width, Height), context: &Context<Num, Color>) -> Self {
        let (x, y, width, height) = data;
        let (x, y, width, height) = (x.to_real(), y.to_real(), width.to_real(), height.to_real());

        let (center, size) = context.rectangle_mode.interpret(x, y, width, height);

        Rectangle {
            center,
            size,
            corner_radius: None,
            fill: context.fill.clone(),
            stroke: context.stroke.clone(),
            weight: context.weight,
            rotation: context.rotation,
        }
    }
}

/// Convert from `(x, y, width, height, corner_radius)` to a rectangle with the given context.
///
/// The interpretation of `x`, `y`, `width`, and `height` are dependant on the context's `rectangle_mode`.
///
/// ```
/// # use sketchbook::aspects::draw::*;
/// # use sketchbook::Point2;
/// # use sketchbook::Size2;
/// #
/// // Create context to create rectangle in releation to.
/// let context = Context {
///     rectangle_mode: Mode::Center,
///     fill: (255, 0, 0),
///     stroke: (0, 0, 255),
///     weight: 4.0,
///     ..Context::default()
/// };
///
/// // Create rectangle.
/// let rect: Rectangle<f32, (u8, u8, u8)>
///     = (1.0, 2.0, 3.0, 4.0, 5.0).into_with_context(&context);
///
/// // Created circle matches what is expected given the context.
/// assert_eq!(
///     rect,
///     Rectangle {
///         center: Point2 { x: 1.0, y: 2.0 },
///         size: Size2 { width: 3.0, height: 4.0 },
///         fill: (255, 0, 0),
///         stroke: (0, 0, 255),
///         weight: 4.0,
///         rotation: 0.0,
///         corner_radius: Some(CornerRadius::Uniform(5.0)),
///     }
/// );
/// ```
impl<X, Y, Width, Height, Radius, Num, Color>
    FromWithContext<(X, Y, Width, Height, Radius), Num, Color> for Rectangle<Num, Color>
where
    X: ToReal<Num>,
    Y: ToReal<Num>,
    Width: ToReal<Num>,
    Height: ToReal<Num>,
    Radius: ToReal<Num>,
    Num: Real,
    Color: Clone,
{
    fn from_with_context(
        data: (X, Y, Width, Height, Radius),
        context: &Context<Num, Color>,
    ) -> Self {
        let (x, y, width, height, radius) = data;
        let (x, y, width, height, radius) = (
            x.to_real(),
            y.to_real(),
            width.to_real(),
            height.to_real(),
            radius.to_real(),
        );

        let (center, size) = context.rectangle_mode.interpret(x, y, width, height);

        Rectangle {
            center,
            size,
            corner_radius: Some(CornerRadius::Uniform(radius)),
            fill: context.fill.clone(),
            stroke: context.stroke.clone(),
            weight: context.weight,
            rotation: context.rotation,
        }
    }
}

/// Convert from `(x, y, width, height, top_left_radius, top_right_radius, bottom_right_radius, bottom_left_radius)` to a rectangle with the given context.
///
/// The interpretation of `x`, `y`, `width`, and `height` are dependant on the context's `rectangle_mode`.
///
/// ```
/// # use sketchbook::aspects::draw::*;
/// # use sketchbook::Point2;
/// # use sketchbook::Size2;
/// #
/// // Create context to create rectangle in releation to.
/// let context = Context {
///     rectangle_mode: Mode::Center,
///     fill: (255, 0, 0),
///     stroke: (0, 0, 255),
///     weight: 4.0,
///     ..Context::default()
/// };
///
/// // Create rectangle.
/// let rect: Rectangle<f32, (u8, u8, u8)>
///     = (1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0).into_with_context(&context);
///
/// // Created circle matches what is expected given the context.
/// assert_eq!(
///     rect,
///     Rectangle {
///         center: Point2 { x: 1.0, y: 2.0 },
///         size: Size2 { width: 3.0, height: 4.0 },
///         fill: (255, 0, 0),
///         stroke: (0, 0, 255),
///         weight: 4.0,
///         rotation: 0.0,
///         corner_radius: Some(CornerRadius::PerCorner {
///             top_left: 5.0,
///             top_right: 6.0,
///             bottom_right: 7.0,
///             bottom_left: 8.0,
///         }),
///     }
/// );
/// ```
impl<X, Y, Width, Height, TL, TR, BR, BL, Num, Color>
    FromWithContext<(X, Y, Width, Height, TL, TR, BR, BL), Num, Color> for Rectangle<Num, Color>
where
    X: ToReal<Num>,
    Y: ToReal<Num>,
    Width: ToReal<Num>,
    Height: ToReal<Num>,
    TL: ToReal<Num>,
    TR: ToReal<Num>,
    BR: ToReal<Num>,
    BL: ToReal<Num>,
    Num: Real,
    Color: Clone,
{
    fn from_with_context(
        data: (X, Y, Width, Height, TL, TR, BR, BL),
        context: &Context<Num, Color>,
    ) -> Self {
        let (x, y, width, height, top_left, top_right, bottom_right, bottom_left) = data;
        let (x, y, width, height, top_left, top_right, bottom_right, bottom_left) = (
            x.to_real(),
            y.to_real(),
            width.to_real(),
            height.to_real(),
            top_left.to_real(),
            top_right.to_real(),
            bottom_right.to_real(),
            bottom_left.to_real(),
        );

        let (center, size) = context.rectangle_mode.interpret(x, y, width, height);

        Rectangle {
            center,
            size,
            corner_radius: Some(CornerRadius::PerCorner {
                top_left,
                top_right,
                bottom_right,
                bottom_left,
            }),
            fill: context.fill.clone(),
            stroke: context.stroke.clone(),
            weight: context.weight,
            rotation: context.rotation,
        }
    }
}