x-graphics 0.2.1

Graphics framework for X
Documentation
use windows::{
    core::Interface,
    Foundation::Numerics::Matrix3x2,
    Win32::Graphics::Direct2D::{
        Common::D2D_POINT_2F, ID2D1Brush, ID2D1RenderTarget, D2D1_BRUSH_PROPERTIES, D2D1_EXTEND_MODE_CLAMP, D2D1_GAMMA_2_2,
        D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES, D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES,
    },
};

use super::gradient::D2DGradient;
use crate::{
    brush::BrushBackend,
    gradient::GradientData,
    paint::{Color, BLACK},
    Float,
};

#[derive(Clone, Debug)]
enum BrushData {
    Solid(Color),
    Gradient(D2DGradient),
}

impl Default for BrushData {
    fn default() -> Self {
        Self::Solid(BLACK)
    }
}

#[derive(Clone, Debug, Default)]
pub struct D2DBrush {
    data: BrushData,
    brush: Option<ID2D1Brush>,
    render_target_identity: Option<usize>,
}

impl BrushBackend for D2DBrush {
    type GradientType = D2DGradient;

    fn new_solid(color: Color) -> Self {
        Self {
            data: BrushData::Solid(color),
            brush: None,
            render_target_identity: None,
        }
    }

    fn new_gradient(gradient: Self::GradientType) -> Self {
        Self {
            data: BrushData::Gradient(gradient),
            brush: None,
            render_target_identity: None,
        }
    }
}

impl D2DBrush {
    pub(super) fn d2d_brush(&mut self, render_target: &ID2D1RenderTarget, opacity: Float) -> Option<ID2D1Brush> {
        if self.render_target_identity != Some(render_target.as_raw() as usize) {
            self.brush = None;
        }

        self.brush
            .as_ref()
            .map(|brush| {
                unsafe {
                    brush.SetOpacity(opacity);
                }
                brush.clone()
            })
            .or_else(|| {
                let brush_properties = D2D1_BRUSH_PROPERTIES {
                    opacity,
                    transform: Matrix3x2::identity(),
                };
                let brush: Option<ID2D1Brush> = match &self.data {
                    BrushData::Solid(color) => {
                        let brush = unsafe { render_target.CreateSolidColorBrush(&(*color).into(), Some(&brush_properties)).ok() };
                        brush.map(|brush| brush.into())
                    }
                    BrushData::Gradient(gradient) => {
                        let color_stops = unsafe {
                            render_target.CreateGradientStopCollection(gradient.color_stops(), D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP).ok().unwrap()
                        };
                        match gradient.data() {
                            GradientData::Linear((start, end)) => unsafe {
                                render_target
                                    .CreateLinearGradientBrush(
                                        &D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES {
                                            startPoint: (*start).into(),
                                            endPoint: (*end).into(),
                                        },
                                        Some(&brush_properties),
                                        &color_stops,
                                    )
                                    .ok()
                                    .map(|brush| brush.into())
                            },
                            GradientData::Radial((start, end, radius)) => unsafe {
                                render_target
                                    .CreateRadialGradientBrush(
                                        &D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES {
                                            center: (*start).into(),
                                            gradientOriginOffset: D2D_POINT_2F {
                                                x: end.x - start.x,
                                                y: end.y - start.y,
                                            },
                                            radiusX: *radius,
                                            radiusY: *radius,
                                        },
                                        Some(&brush_properties),
                                        &color_stops,
                                    )
                                    .ok()
                                    .map(|brush| brush.into())
                            },
                        }
                    }
                };
                self.brush = brush.clone();
                self.render_target_identity = Some(render_target.as_raw() as usize);
                brush
            })
    }

    pub(super) fn set_opacity(&mut self, opacity: Float) {
        if let Some(brush) = self.brush.as_ref() {
            unsafe {
                brush.SetOpacity(opacity);
            }
        }
    }
}