x-graphics 0.2.1

Graphics framework for X
Documentation
use std::cell::Cell;

use windows::Win32::Graphics::Direct2D::{
    Common::{
        D2D1_BEZIER_SEGMENT, D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_BEGIN_HOLLOW, D2D1_FIGURE_END_CLOSED, D2D1_FIGURE_END_OPEN,
        D2D1_FILL_MODE_ALTERNATE, D2D1_FILL_MODE_WINDING, D2D_POINT_2F, D2D_SIZE_F,
    },
    ID2D1GeometrySink, ID2D1PathGeometry, D2D1_ARC_SEGMENT, D2D1_ARC_SIZE_LARGE, D2D1_ARC_SIZE_SMALL, D2D1_QUADRATIC_BEZIER_SEGMENT,
    D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE,
};

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

#[derive(Clone, Debug)]
pub struct D2DPath {
    path: ID2D1PathGeometry,
    sink: ID2D1GeometrySink,
    closed: Cell<bool>,
    fill_type: FillType,
}

impl PathBackend for D2DPath {
    type DeviceContextType = D2DDeviceContext;

    fn new(context: Option<&Self::DeviceContextType>) -> Result<Self, GraphicsError> {
        let context = context.ok_or(none_param_error!(context))?;
        let path = unsafe { context.factory.CreatePathGeometry().map_err(|err| GraphicsError::CreationFailed(err.to_string()))? };
        let sink = unsafe { path.Open().map_err(|err| GraphicsError::CreationFailed(err.to_string()))? };
        unsafe { sink.SetFillMode(D2D1_FILL_MODE_WINDING) };
        Ok(Self {
            path,
            sink,
            closed: Cell::new(false),
            fill_type: FillType::Winding,
        })
    }

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

    fn set_fill_type(&mut self, fill_type: FillType) {
        let fill_mode = match fill_type {
            FillType::Winding => D2D1_FILL_MODE_WINDING,
            FillType::EvenOdd => D2D1_FILL_MODE_ALTERNATE,
        };
        unsafe { self.sink().SetFillMode(fill_mode) };
        self.fill_type = fill_type;
    }

    fn begin(&mut self) {
        if !self.closed.get() {
            unsafe { self.sink.Close().ok() };
            self.closed.set(true);
        }
        if let Ok(sink) = unsafe { self.path.Open() } {
            self.sink = sink;
        }
    }

    fn close(&mut self) {
        unsafe {
            self.sink().EndFigure(D2D1_FIGURE_END_CLOSED);
        }
    }

    fn move_to(&mut self, x: Float, y: Float) {
        unsafe {
            let point = D2D_POINT_2F {
                x,
                y,
            };
            self.sink().BeginFigure(point, D2D1_FIGURE_BEGIN_FILLED);
        }
    }

    fn line_to(&mut self, x: Float, y: Float) {
        unsafe {
            let point = D2D_POINT_2F {
                x,
                y,
            };
            self.sink().AddLine(point);
        }
    }

    fn arc_to(&mut self, x1: Float, y1: Float, x2: Float, y2: Float, radius: Float) {
        let center_x = x1 + (x2 - x1) / 2.0;
        let center_y = y1 + (y2 - y1) / 2.0;
        let start_angle = (y1 - center_y).atan2(x1 - center_x);
        let end_angle = (y2 - center_y).atan2(x2 - center_x);
        let arc_segment = D2D1_ARC_SEGMENT {
            point: D2D_POINT_2F {
                x: x2,
                y: y2,
            },
            size: D2D_SIZE_F {
                width: radius,
                height: radius,
            },
            rotationAngle: 0.0,
            sweepDirection: D2D1_SWEEP_DIRECTION_CLOCKWISE,
            arcSize: if (end_angle - start_angle).abs() < 180.0 {
                D2D1_ARC_SIZE_SMALL
            } else {
                D2D1_ARC_SIZE_LARGE
            },
        };
        unsafe {
            self.sink().AddArc(&arc_segment);
        }
    }

    fn bezier_curve_to(&mut self, cpx1: Float, cpy1: Float, cpx2: Float, cpy2: Float, x: Float, y: Float) {
        let bezier_segment = D2D1_BEZIER_SEGMENT {
            point1: D2D_POINT_2F {
                x: cpx1,
                y: cpy1,
            },
            point2: D2D_POINT_2F {
                x: cpx2,
                y: cpy2,
            },
            point3: D2D_POINT_2F {
                x,
                y,
            },
        };

        unsafe {
            self.sink().AddBezier(&bezier_segment);
        }
    }

    fn quad_curve_to(&mut self, cpx: Float, cpy: Float, x: Float, y: Float) {
        let quad_segment = D2D1_QUADRATIC_BEZIER_SEGMENT {
            point1: D2D_POINT_2F {
                x: cpx,
                y: cpy,
            },
            point2: D2D_POINT_2F {
                x,
                y,
            },
        };

        unsafe {
            self.sink().AddQuadraticBezier(&quad_segment);
        }
    }

    fn add_arc(&mut self, x: Float, y: Float, radius: Float, start_angle: Float, end_angle: Float, clockwise: bool) {
        let start_x = x + radius * start_angle.cos();
        let start_y = y + radius * start_angle.sin();
        let end_x = x + radius * end_angle.cos();
        let end_y = y + radius * end_angle.sin();
        let arc_segment = D2D1_ARC_SEGMENT {
            point: D2D_POINT_2F {
                x: end_x,
                y: end_y,
            },
            size: D2D_SIZE_F {
                width: radius,
                height: radius,
            },
            rotationAngle: 0.0,
            sweepDirection: if clockwise {
                D2D1_SWEEP_DIRECTION_CLOCKWISE
            } else {
                D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE
            },
            arcSize: if (end_angle - start_angle).abs() < std::f32::consts::PI {
                D2D1_ARC_SIZE_SMALL
            } else {
                D2D1_ARC_SIZE_LARGE
            },
        };
        let sink = self.sink();
        unsafe {
            sink.BeginFigure(
                D2D_POINT_2F {
                    x: start_x,
                    y: start_y,
                },
                D2D1_FIGURE_BEGIN_HOLLOW,
            );
            sink.AddArc(&arc_segment);
            sink.EndFigure(D2D1_FIGURE_END_OPEN);
        }
    }

    fn add_rect(&mut self, x: Float, y: Float, width: Float, height: Float) {
        let sink = self.sink();
        unsafe {
            sink.BeginFigure(
                D2D_POINT_2F {
                    x,
                    y,
                },
                D2D1_FIGURE_BEGIN_FILLED,
            );
            sink.AddLine(D2D_POINT_2F {
                x: x + width,
                y,
            });
            sink.AddLine(D2D_POINT_2F {
                x: x + width,
                y: y + height,
            });
            sink.AddLine(D2D_POINT_2F {
                x,
                y: y + height,
            });
            sink.EndFigure(D2D1_FIGURE_END_OPEN);
        }
    }

    fn add_circle(&mut self, x: Float, y: Float, radius: Float) {
        let mut arc_segment = D2D1_ARC_SEGMENT {
            point: D2D_POINT_2F {
                x: x + radius,
                y,
            },
            size: D2D_SIZE_F {
                width: radius,
                height: radius,
            },
            rotationAngle: 0.0,
            sweepDirection: D2D1_SWEEP_DIRECTION_CLOCKWISE,
            arcSize: D2D1_ARC_SIZE_LARGE,
        };
        let sink = self.sink();
        unsafe {
            sink.BeginFigure(
                D2D_POINT_2F {
                    x: x - radius,
                    y,
                },
                D2D1_FIGURE_BEGIN_FILLED,
            );
            sink.AddArc(&arc_segment);
            arc_segment.point = D2D_POINT_2F {
                x: x - radius,
                y,
            };
            sink.AddArc(&arc_segment);
            sink.EndFigure(D2D1_FIGURE_END_OPEN);
        }
    }

    fn add_ellipse(&mut self, x: Float, y: Float, width: Float, height: Float) {
        let start_x = x + width;
        let start_y = y + height / 2.0;
        let mut arc_segment = D2D1_ARC_SEGMENT {
            point: D2D_POINT_2F {
                x,
                y: start_y,
            },
            size: D2D_SIZE_F {
                width: width / 2.0,
                height: height / 2.0,
            },
            rotationAngle: 0.0,
            sweepDirection: D2D1_SWEEP_DIRECTION_CLOCKWISE,
            arcSize: D2D1_ARC_SIZE_LARGE,
        };
        let sink = self.sink();
        unsafe {
            sink.BeginFigure(
                D2D_POINT_2F {
                    x: start_x,
                    y: start_y,
                },
                D2D1_FIGURE_BEGIN_FILLED,
            );
            sink.AddArc(&arc_segment);
            arc_segment.point = D2D_POINT_2F {
                x: start_x,
                y: start_y,
            };
            sink.AddArc(&arc_segment);
            sink.EndFigure(D2D1_FIGURE_END_OPEN);
        }
    }

    fn add_rounded_rect(&mut self, x: Float, y: Float, width: Float, height: Float, radius: Float) {
        let sink = self.sink();
        unsafe {
            sink.BeginFigure(
                D2D_POINT_2F {
                    x: x + radius,
                    y,
                },
                D2D1_FIGURE_BEGIN_FILLED,
            );
            sink.AddLine(D2D_POINT_2F {
                x: x + width - radius,
                y,
            });
            Self::add_corner_arc(sink, x + width, y + radius, radius);
            sink.AddLine(D2D_POINT_2F {
                x: x + width,
                y: y + height - radius,
            });
            Self::add_corner_arc(sink, x + width - radius, y + height, radius);
            sink.AddLine(D2D_POINT_2F {
                x: x + radius,
                y: y + height,
            });
            Self::add_corner_arc(sink, x, y + height - radius, radius);
            sink.AddLine(D2D_POINT_2F {
                x,
                y: y + radius,
            });
            Self::add_corner_arc(sink, x + radius, y, radius);
            sink.EndFigure(D2D1_FIGURE_END_OPEN);
        }
    }

    fn bounds(&self) -> FRect {
        let rect = unsafe { self.path().GetBounds(None).unwrap_or_default() };
        rect.into()
    }

    fn is_empty(&self) -> bool {
        unsafe { self.path().GetSegmentCount() }.unwrap_or(0) == 0
    }
}

impl D2DPath {
    pub(super) fn path(&self) -> &ID2D1PathGeometry {
        if !self.closed.get() {
            unsafe {
                self.sink.Close().ok();
            }
            self.closed.set(true);
        }
        &self.path
    }

    fn add_corner_arc(sink: &ID2D1GeometrySink, x: Float, y: Float, radius: Float) {
        let arc_segment = D2D1_ARC_SEGMENT {
            point: D2D_POINT_2F {
                x,
                y,
            },
            size: D2D_SIZE_F {
                width: radius,
                height: radius,
            },
            rotationAngle: 0.0,
            sweepDirection: D2D1_SWEEP_DIRECTION_CLOCKWISE,
            arcSize: D2D1_ARC_SIZE_SMALL,
        };
        unsafe {
            sink.AddArc(&arc_segment);
        }
    }

    fn sink(&mut self) -> &ID2D1GeometrySink {
        if self.closed.get() {
            if let Ok(sink) = unsafe { self.path.Open() } {
                self.sink = sink;
                unsafe {
                    self.path.Stream(&self.sink).ok();
                }
            }
        }
        &self.sink
    }
}

impl Drop for D2DPath {
    fn drop(&mut self) {
        unsafe {
            if !self.closed.get() {
                self.sink.Close().ok();
            }
        }
    }
}