Skip to main content

incular_rendering/
paint.rs

1use crate::effects::{BlendMode, ColorFilter};
2use crate::geometry::CornerRadii;
3use crate::gradients::{Brush, Shader};
4use incular_core::finite_non_negative;
5use incular_core::{Color, Lerp, Offset};
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct Border {
9    pub width: f32,
10    pub color: Color,
11}
12impl Border {
13    #[must_use]
14    pub fn new(width: f32, color: Color) -> Self {
15        Self {
16            width: width.max(0.),
17            color,
18        }
19    }
20}
21#[derive(Clone, Debug, Default, PartialEq)]
22pub struct Decoration {
23    pub background: Option<Brush>,
24    pub border: Option<Border>,
25    pub border_radius: CornerRadii,
26}
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum FillRule {
29    NonZero,
30    EvenOdd,
31}
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub enum LineCap {
34    Butt,
35    Round,
36    Square,
37}
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub enum LineJoin {
40    Miter,
41    Round,
42    Bevel,
43}
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub struct Stroke {
46    pub width: f32,
47    pub cap: LineCap,
48    pub join: LineJoin,
49    pub miter_limit: f32,
50}
51impl Default for Stroke {
52    fn default() -> Self {
53        Self {
54            width: 1.,
55            cap: LineCap::Butt,
56            join: LineJoin::Miter,
57            miter_limit: 4.,
58        }
59    }
60}
61
62/// Whether a [`Paint`] covers the interior or the outline of a path.
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
64pub enum PaintStyle {
65    #[default]
66    Fill,
67    Stroke,
68}
69
70/// A renderer-neutral paint description.
71///
72/// `Paint` is deliberately a small immutable builder around the authoritative
73/// [`Brush`], [`Stroke`], [`BlendMode`], and [`ColorFilter`] types. It does not
74/// introduce a second display command model; the canvas path API lowers it
75/// directly to the existing fill/stroke commands.
76#[derive(Clone, Debug, PartialEq)]
77pub struct Paint {
78    brush: Brush,
79    style: PaintStyle,
80    stroke: Stroke,
81    blend_mode: BlendMode,
82    color_filter: Option<ColorFilter>,
83}
84
85impl Default for Paint {
86    fn default() -> Self {
87        Self {
88            brush: Brush::Solid(Color::BLACK),
89            style: PaintStyle::Fill,
90            stroke: Stroke::default(),
91            blend_mode: BlendMode::SrcOver,
92            color_filter: None,
93        }
94    }
95}
96
97impl Paint {
98    #[must_use]
99    pub fn new() -> Self {
100        Self::default()
101    }
102
103    #[must_use]
104    pub fn brush(mut self, brush: impl Into<Brush>) -> Self {
105        self.brush = brush.into();
106        self
107    }
108
109    #[must_use]
110    pub fn shader(self, shader: impl Into<Shader>) -> Self {
111        self.brush(shader)
112    }
113
114    #[must_use]
115    pub fn color(self, color: Color) -> Self {
116        self.brush(color)
117    }
118
119    #[must_use]
120    pub fn style(mut self, style: PaintStyle) -> Self {
121        self.style = style;
122        self
123    }
124
125    #[must_use]
126    pub fn fill(self) -> Self {
127        self.style(PaintStyle::Fill)
128    }
129
130    #[must_use]
131    pub fn stroke(self) -> Self {
132        self.style(PaintStyle::Stroke)
133    }
134
135    #[must_use]
136    pub fn stroke_settings(mut self, stroke: Stroke) -> Self {
137        self.stroke = Stroke {
138            width: finite_non_negative(stroke.width),
139            cap: stroke.cap,
140            join: stroke.join,
141            miter_limit: finite_non_negative(stroke.miter_limit),
142        };
143        self
144    }
145
146    #[must_use]
147    pub fn stroke_width(mut self, width: f32) -> Self {
148        self.stroke.width = finite_non_negative(width);
149        self
150    }
151
152    #[must_use]
153    pub fn blend_mode(mut self, blend_mode: BlendMode) -> Self {
154        self.blend_mode = blend_mode;
155        self
156    }
157
158    #[must_use]
159    pub fn color_filter(mut self, color_filter: Option<ColorFilter>) -> Self {
160        self.color_filter = color_filter;
161        self
162    }
163
164    #[must_use]
165    pub fn brush_value(&self) -> &Brush {
166        &self.brush
167    }
168
169    #[must_use]
170    pub const fn style_value(&self) -> PaintStyle {
171        self.style
172    }
173
174    #[must_use]
175    pub const fn stroke_value(&self) -> Stroke {
176        self.stroke
177    }
178
179    #[must_use]
180    pub const fn blend_mode_value(&self) -> BlendMode {
181        self.blend_mode
182    }
183
184    #[must_use]
185    pub fn color_filter_value(&self) -> Option<ColorFilter> {
186        self.color_filter
187    }
188}
189
190/// Canonical single-shadow value used by text and other paint-producing
191/// domains. Subsystems should use this type instead of defining a parallel
192/// color/offset/blur tuple.
193#[derive(Clone, Copy, Debug, Default, PartialEq)]
194pub struct Shadow {
195    pub color: Color,
196    pub offset: Offset,
197    pub blur_radius: f32,
198}
199
200impl Shadow {
201    #[must_use]
202    pub const fn new(color: Color, offset: Offset, blur_radius: f32) -> Self {
203        Self {
204            color,
205            offset,
206            blur_radius: if blur_radius.is_finite() && blur_radius > 0. {
207                blur_radius
208            } else {
209                0.
210            },
211        }
212    }
213}
214
215impl Lerp for Shadow {
216    fn lerp(&self, other: &Self, t: f32) -> Self {
217        Self::new(
218            self.color.lerp(&other.color, t),
219            self.offset.lerp(&other.offset, t),
220            self.blur_radius.lerp(&other.blur_radius, t),
221        )
222    }
223}