Skip to main content

astrelis_geometry/
style.rs

1//! Combined style for geometry rendering.
2//!
3//! A style combines fill and stroke properties for complete geometry styling.
4
5use crate::{Fill, Paint, Stroke, Transform2D};
6use astrelis_render::Color;
7
8/// Complete style for geometry rendering.
9///
10/// A style combines optional fill and stroke properties, along with a transform.
11#[derive(Debug, Clone, PartialEq)]
12pub struct Style {
13    /// Optional fill properties
14    pub fill: Option<Fill>,
15    /// Optional stroke properties
16    pub stroke: Option<Stroke>,
17    /// Transform to apply to the geometry
18    pub transform: Transform2D,
19}
20
21impl Style {
22    /// Create a new empty style (invisible).
23    pub fn new() -> Self {
24        Self {
25            fill: None,
26            stroke: None,
27            transform: Transform2D::IDENTITY,
28        }
29    }
30
31    /// Create a fill-only style.
32    pub fn fill(paint: impl Into<Paint>) -> Self {
33        Self {
34            fill: Some(Fill::from_paint(paint.into())),
35            stroke: None,
36            transform: Transform2D::IDENTITY,
37        }
38    }
39
40    /// Create a fill-only style with a solid color.
41    pub fn fill_color(color: Color) -> Self {
42        Self::fill(Paint::solid(color))
43    }
44
45    /// Create a stroke-only style.
46    pub fn stroke(paint: impl Into<Paint>, width: f32) -> Self {
47        Self {
48            fill: None,
49            stroke: Some(Stroke::from_paint(paint.into(), width)),
50            transform: Transform2D::IDENTITY,
51        }
52    }
53
54    /// Create a stroke-only style with a solid color.
55    pub fn stroke_color(color: Color, width: f32) -> Self {
56        Self::stroke(Paint::solid(color), width)
57    }
58
59    /// Create a style with both fill and stroke.
60    pub fn fill_and_stroke(
61        fill_paint: impl Into<Paint>,
62        stroke_paint: impl Into<Paint>,
63        stroke_width: f32,
64    ) -> Self {
65        Self {
66            fill: Some(Fill::from_paint(fill_paint.into())),
67            stroke: Some(Stroke::from_paint(stroke_paint.into(), stroke_width)),
68            transform: Transform2D::IDENTITY,
69        }
70    }
71
72    /// Set the fill.
73    pub fn with_fill(mut self, fill: Fill) -> Self {
74        self.fill = Some(fill);
75        self
76    }
77
78    /// Set the fill paint.
79    pub fn with_fill_paint(mut self, paint: impl Into<Paint>) -> Self {
80        self.fill = Some(Fill::from_paint(paint.into()));
81        self
82    }
83
84    /// Set the fill color.
85    pub fn with_fill_color(mut self, color: Color) -> Self {
86        self.fill = Some(Fill::solid(color));
87        self
88    }
89
90    /// Set the stroke.
91    pub fn with_stroke(mut self, stroke: Stroke) -> Self {
92        self.stroke = Some(stroke);
93        self
94    }
95
96    /// Set the stroke paint and width.
97    pub fn with_stroke_paint(mut self, paint: impl Into<Paint>, width: f32) -> Self {
98        self.stroke = Some(Stroke::from_paint(paint.into(), width));
99        self
100    }
101
102    /// Set the stroke color and width.
103    pub fn with_stroke_color(mut self, color: Color, width: f32) -> Self {
104        self.stroke = Some(Stroke::solid(color, width));
105        self
106    }
107
108    /// Set the transform.
109    pub fn with_transform(mut self, transform: Transform2D) -> Self {
110        self.transform = transform;
111        self
112    }
113
114    /// Check if this style has a visible fill.
115    pub fn has_fill(&self) -> bool {
116        self.fill.as_ref().is_some_and(|f| f.opacity > 0.0)
117    }
118
119    /// Check if this style has a visible stroke.
120    pub fn has_stroke(&self) -> bool {
121        self.stroke.as_ref().is_some_and(|s| s.is_visible())
122    }
123
124    /// Check if this style is visible (has fill or stroke).
125    pub fn is_visible(&self) -> bool {
126        self.has_fill() || self.has_stroke()
127    }
128
129    /// Get the fill color (for solid fills).
130    pub fn get_fill_color(&self) -> Option<Color> {
131        self.fill.as_ref().and_then(|f| f.effective_color())
132    }
133
134    /// Get the stroke color (for solid strokes).
135    pub fn get_stroke_color(&self) -> Option<Color> {
136        self.stroke.as_ref().and_then(|s| s.effective_color())
137    }
138}
139
140impl Default for Style {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146/// Shorthand for creating common styles.
147pub mod presets {
148    use super::*;
149
150    /// Red fill.
151    pub fn red_fill() -> Style {
152        Style::fill_color(Color::RED)
153    }
154
155    /// Green fill.
156    pub fn green_fill() -> Style {
157        Style::fill_color(Color::GREEN)
158    }
159
160    /// Blue fill.
161    pub fn blue_fill() -> Style {
162        Style::fill_color(Color::BLUE)
163    }
164
165    /// Black stroke (1px).
166    pub fn black_stroke() -> Style {
167        Style::stroke_color(Color::BLACK, 1.0)
168    }
169
170    /// White stroke (1px).
171    pub fn white_stroke() -> Style {
172        Style::stroke_color(Color::WHITE, 1.0)
173    }
174
175    /// Transparent fill with black stroke.
176    pub fn outline() -> Style {
177        Style::stroke_color(Color::BLACK, 1.0)
178    }
179
180    /// Debug style (red fill, blue 2px stroke).
181    pub fn debug() -> Style {
182        Style::fill_and_stroke(Paint::solid(Color::RED), Paint::solid(Color::BLUE), 2.0)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_empty_style() {
192        let style = Style::new();
193        assert!(!style.is_visible());
194        assert!(!style.has_fill());
195        assert!(!style.has_stroke());
196    }
197
198    #[test]
199    fn test_fill_only() {
200        let style = Style::fill_color(Color::RED);
201        assert!(style.is_visible());
202        assert!(style.has_fill());
203        assert!(!style.has_stroke());
204    }
205
206    #[test]
207    fn test_stroke_only() {
208        let style = Style::stroke_color(Color::BLUE, 2.0);
209        assert!(style.is_visible());
210        assert!(!style.has_fill());
211        assert!(style.has_stroke());
212    }
213
214    #[test]
215    fn test_fill_and_stroke() {
216        let style =
217            Style::fill_and_stroke(Paint::solid(Color::RED), Paint::solid(Color::BLACK), 1.0);
218        assert!(style.has_fill());
219        assert!(style.has_stroke());
220    }
221}