x-graphics 0.2.1

Graphics framework for X
Documentation
use std::f64::consts::PI;

use core_graphics::{
    geometry::CGRect,
    path::{CGMutablePath, CGPath},
};
use os_ver::if_greater_than;

use super::device::CoreGraphicsDeviceContext;
use crate::{
    error::GraphicsError,
    geometry::FRect,
    path::{FillType, PathBackend},
    Float,
};

#[derive(Clone, Debug)]
pub struct CoreGraphicsPath {
    path: CGMutablePath,
    fill_type: FillType,
}

impl PathBackend for CoreGraphicsPath {
    type DeviceContextType = CoreGraphicsDeviceContext;

    fn new(_context: Option<&Self::DeviceContextType>) -> Result<Self, GraphicsError> {
        Ok(Self {
            path: CGMutablePath::new(),
            fill_type: FillType::Winding,
        })
    }

    fn get_fill_type(&self) -> FillType {
        self.fill_type
    }

    fn set_fill_type(&mut self, fill_type: FillType) {
        self.fill_type = fill_type;
    }

    fn begin(&mut self) {
        self.path = CGMutablePath::new();
    }

    fn close(&mut self) {
        self.path.close_subpath();
    }

    fn move_to(&mut self, x: Float, y: Float) {
        if x.is_nan() || y.is_nan() {
            return;
        }
        self.path.move_to_point(None, x, y);
    }

    fn line_to(&mut self, x: Float, y: Float) {
        if x.is_nan() || y.is_nan() {
            return;
        }
        self.path.add_line_to_point(None, x, y);
    }

    fn arc_to(&mut self, x1: Float, y1: Float, x2: Float, y2: Float, radius: Float) {
        if x1.is_nan() || y1.is_nan() || x2.is_nan() || y2.is_nan() || radius.is_nan() {
            return;
        }
        self.path.add_arc_to_point(None, x1, y1, x2, y2, radius);
    }

    fn bezier_curve_to(&mut self, cpx1: Float, cpy1: Float, cpx2: Float, cpy2: Float, x: Float, y: Float) {
        if cpx1.is_nan() || cpy1.is_nan() || cpx2.is_nan() || cpy2.is_nan() || x.is_nan() || y.is_nan() {
            return;
        }
        self.path.add_curve_to_point(None, cpx1, cpy1, cpx2, cpy2, x, y);
    }

    fn quad_curve_to(&mut self, cpx: Float, cpy: Float, x: Float, y: Float) {
        if cpx.is_nan() || cpy.is_nan() || x.is_nan() || y.is_nan() {
            return;
        }
        self.path.add_quad_curve_to_point(None, cpx, cpy, x, y);
    }

    fn add_arc(&mut self, x: Float, y: Float, radius: Float, start_angle: Float, end_angle: Float, clockwise: bool) {
        if x.is_nan() || y.is_nan() || radius.is_nan() || start_angle.is_nan() || end_angle.is_nan() {
            return;
        }
        self.path.add_arc(None, x, y, radius, start_angle, end_angle, clockwise);
    }

    fn add_rect(&mut self, x: Float, y: Float, width: Float, height: Float) {
        if x.is_nan() || y.is_nan() || width.is_nan() || height.is_nan() {
            return;
        }
        self.path.add_rect(None, CGRect::new(x, y, width, height));
    }

    fn add_circle(&mut self, x: Float, y: Float, radius: Float) {
        if x.is_nan() || y.is_nan() || radius.is_nan() {
            return;
        }
        self.path.add_ellipse_in_rect(None, CGRect::new(x - radius, y - radius, radius * 2.0, radius * 2.0));
    }

    fn add_ellipse(&mut self, x: Float, y: Float, width: Float, height: Float) {
        if x.is_nan() || y.is_nan() || width.is_nan() || height.is_nan() {
            return;
        }
        self.path.add_ellipse_in_rect(None, CGRect::new(x, y, width, height));
    }

    fn add_rounded_rect(&mut self, x: Float, y: Float, width: Float, height: Float, radius: Float) {
        if x.is_nan() || y.is_nan() || width.is_nan() || height.is_nan() || radius.is_nan() {
            return;
        }
        #[cfg(target_os = "macos")]
        if_greater_than! {(10, 9) => {
            self.path.add_rounded_rect(None, CGRect::new(x, y, width, height), radius, radius);
        } else {
            self.path.move_to_point(None, x + radius, y);
            self.path.add_arc_to_point(None, x + width, y, x + width, y + radius, radius);
            self.path.add_arc_to_point(None, x + width, y + height, x + width - radius, y + height, radius);
            self.path.add_arc_to_point(None, x, y + height, x, y + height - radius, radius);
            self.path.add_arc_to_point(None, x, y, x + radius, y, radius);
        }};
        #[cfg(target_os = "ios")]
        if_greater_than! {(7) => {
            self.path.add_rounded_rect(None, CGRect::new(x, y, width, height), radius, radius);
        } else {
            self.path.move_to_point(None, x + radius, y);
            self.path.add_arc_to_point(None, x + width, y, x + width, y + radius, radius);
            self.path.add_arc_to_point(None, x + width, y + height, x + width - radius, y + height, radius);
            self.path.add_arc_to_point(None, x, y + height, x, y + height - radius, radius);
            self.path.add_arc_to_point(None, x, y, x + radius, y, radius);
        }};
    }

    fn bounds(&self) -> FRect {
        let bounds = self.path.to_immutable().path_bounding_box();
        FRect::new(bounds.origin.x.floor(), bounds.origin.y.floor(), bounds.size.width.ceil(), bounds.size.height.ceil())
    }

    fn is_empty(&self) -> bool {
        self.path.to_immutable().is_empty()
    }
}

impl CoreGraphicsPath {
    pub(super) fn path(&self) -> CGPath {
        self.path.to_immutable()
    }
}