pnte 0.3.3

2D Graphics library for Windows in Rust
Documentation
use crate::*;
use windows::Win32::Graphics::{Direct2D::Common::*, Direct2D::*};

pub trait Fill {
    fn fill(&self, dc: &ID2D1DeviceContext5, brush: &ID2D1Brush);
}

pub trait Stroke {
    fn stroke(
        &self,
        dc: &ID2D1DeviceContext5,
        brush: &ID2D1Brush,
        width: f32,
        style: Option<&ID2D1StrokeStyle1>,
    );
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(i32)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CapStyle {
    Flat = D2D1_CAP_STYLE_FLAT.0,
    Square = D2D1_CAP_STYLE_SQUARE.0,
    Round = D2D1_CAP_STYLE_ROUND.0,
    Triangle = D2D1_CAP_STYLE_TRIANGLE.0,
}

impl From<CapStyle> for D2D1_CAP_STYLE {
    #[inline]
    fn from(value: CapStyle) -> Self {
        D2D1_CAP_STYLE(value as i32)
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LineJoin {
    Miter,
    Bevel,
    Round,
    MiterOrBevel(f32),
}

impl LineJoin {
    fn value(&self) -> (D2D1_LINE_JOIN, f32) {
        match self {
            Self::Miter => (D2D1_LINE_JOIN_MITER, 1.0),
            Self::Bevel => (D2D1_LINE_JOIN_BEVEL, 1.0),
            Self::Round => (D2D1_LINE_JOIN_ROUND, 1.0),
            Self::MiterOrBevel(miter_limit) => (D2D1_LINE_JOIN_MITER_OR_BEVEL, *miter_limit),
        }
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DashStyle<'a> {
    Solid,
    Dash,
    Dot,
    DashDot,
    DashDotDot,
    Custom(&'a [f32]),
}

impl DashStyle<'_> {
    fn value(&self) -> (D2D1_DASH_STYLE, Option<&[f32]>) {
        match self {
            Self::Solid => (D2D1_DASH_STYLE_SOLID, None),
            Self::Dash => (D2D1_DASH_STYLE_DASH, None),
            Self::Dot => (D2D1_DASH_STYLE_DOT, None),
            Self::DashDot => (D2D1_DASH_STYLE_DASH_DOT, None),
            Self::DashDotDot => (D2D1_DASH_STYLE_DASH_DOT_DOT, None),
            Self::Custom(dashes) => (D2D1_DASH_STYLE_CUSTOM, Some(dashes)),
        }
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Dash<'a> {
    pub cap: CapStyle,
    pub style: DashStyle<'a>,
    pub offset: f32,
}

impl Default for Dash<'_> {
    #[inline]
    fn default() -> Self {
        Self {
            cap: CapStyle::Flat,
            style: DashStyle::Solid,
            offset: 0.0,
        }
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StrokeStyleProperties<'a> {
    pub start_cap: CapStyle,
    pub end_cap: CapStyle,
    pub line_join: LineJoin,
    pub dash: Option<Dash<'a>>,
}

impl Default for StrokeStyleProperties<'_> {
    #[inline]
    fn default() -> Self {
        Self {
            start_cap: CapStyle::Flat,
            end_cap: CapStyle::Flat,
            line_join: LineJoin::Miter,
            dash: None,
        }
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct StrokeStyle(ID2D1StrokeStyle1);

impl StrokeStyle {
    pub fn new<T>(ctx: &Context<T>, props: &StrokeStyleProperties) -> Result<Self>
    where
        T: Backend,
    {
        let (line_join, miter_limit) = props.line_join.value();
        let (dash_cap, dash_style, dash_offset, dashes) = match props.dash.as_ref() {
            Some(dash) => {
                let (style, dashes) = dash.style.value();
                (dash.cap.into(), style, dash.offset, dashes)
            }
            None => (D2D1_CAP_STYLE_FLAT, D2D1_DASH_STYLE_SOLID, 0.0, None),
        };
        let props = D2D1_STROKE_STYLE_PROPERTIES1 {
            startCap: props.start_cap.into(),
            endCap: props.end_cap.into(),
            dashCap: dash_cap,
            lineJoin: line_join,
            miterLimit: miter_limit,
            dashStyle: dash_style,
            dashOffset: dash_offset,
            transformType: D2D1_STROKE_TRANSFORM_TYPE_NORMAL,
        };
        let handle = unsafe {
            ctx.backend
                .d2d1_factory()
                .CreateStrokeStyle(&props, dashes)?
        };
        Ok(Self(handle))
    }
}

pub struct DrawCommand<'a, T: Backend> {
    ctx: &'a Context<T>,
}

impl<'a, T: Backend> DrawCommand<'a, T> {
    pub(crate) fn new(ctx: &'a Context<T>) -> Self {
        Self { ctx }
    }

    #[inline]
    pub fn context(&self) -> &Context<T> {
        self.ctx
    }

    #[inline]
    pub fn clear(&self, color: impl Into<Rgba>) {
        unsafe {
            let color = D2D1_COLOR_F::from(color.into());
            self.ctx.d2d1_device_context.Clear(Some(&color));
        }
    }

    #[inline]
    pub fn fill(&self, object: &impl Fill, brush: &impl Brush) {
        let dc = &self.ctx.d2d1_device_context;
        object.fill(dc, brush.handle());
    }

    #[inline]
    pub fn stroke(
        &self,
        object: &impl Stroke,
        brush: &impl Brush,
        width: f32,
        stroke_style: Option<&StrokeStyle>,
    ) {
        let dc = &self.ctx.d2d1_device_context;
        object.stroke(dc, brush.handle(), width, stroke_style.map(|s| &s.0));
    }

    #[inline]
    pub fn draw_text(
        &self,
        text: impl Text,
        position: impl Into<Point<f32>>,
        brush: &impl Brush,
    ) -> Result<()> {
        unsafe {
            let dc = &self.ctx.d2d1_device_context;
            let position: Point<f32> = position.into();
            dc.DrawTextLayout(
                position.into(),
                text.layout(self.ctx, &self.ctx.default_text_format)?
                    .handle(),
                brush.handle(),
                None,
                0,
                D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT | D2D1_DRAW_TEXT_OPTIONS_CLIP,
            );
        }
        Ok(())
    }

    #[inline]
    pub fn draw_image(
        &self,
        image: &Image,
        src_rect: Option<Rect<f32>>,
        dest_rect: impl Into<Rect<f32>>,
        opacity: Option<f32>,
        interpolation: Interpolation,
    ) {
        let src: Option<D2D_RECT_F> = src_rect.map(|src| src.into());
        let dest = D2D_RECT_F::from(dest_rect.into());
        let dc = &self.ctx.d2d1_device_context;
        unsafe {
            dc.DrawBitmap(
                image.handle(),
                Some(&dest),
                opacity.unwrap_or(1.0),
                interpolation.into(),
                src.as_ref().map(|src| src as *const D2D_RECT_F),
                None,
            );
        }
    }

    #[inline]
    pub fn push_clip(&self, rect: impl Into<Rect<f32>>) {
        let rect: Rect<f32> = rect.into();
        let dc = &self.ctx.d2d1_device_context;
        unsafe {
            dc.PushAxisAlignedClip(&rect.into(), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
        }
    }

    #[inline]
    pub fn pop_clip(&self) {
        let dc = &self.ctx.d2d1_device_context;
        unsafe {
            dc.PopAxisAlignedClip();
        }
    }
}