colorimetry_plot/
style_attr.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025, Harbers Bik LLC
3
4//! Style attributes for SVG elements in the plotting library
5//!
6//! The `StyleAttr` struct and associated macros are used to manage SVG style attributes such as
7//! CSS classes, inline styles, and IDs. This allows for easy styling of SVG elements in plots.
8
9/// Represents SVG style attributes, including optional class, style, and id fields.
10///
11/// `StyleAttr` is used to conveniently manage and assign SVG styling attributes such as CSS classes,
12/// inline styles, and element IDs. This struct can be used to apply consistent styling to SVG nodes
13/// throughout the plotting library.
14#[derive(Default, Debug, Clone, PartialEq, Eq)]
15pub struct StyleAttr {
16    /// Optional CSS class attribute for the SVG element.
17    pub class: Option<String>,
18    /// Optional inline style attribute for the SVG element.
19    pub style: Option<String>,
20    /// Optional id attribute for the SVG element.
21    pub id: Option<String>,
22}
23
24impl StyleAttr {
25    /// Assigns the style attributes (`class` and `style`) to the given SVG node.
26    ///
27    /// This method sets the `class` and `style` attributes on the provided SVG node if they are
28    /// present in the `StyleAttr` instance. If the `id` is set, it will not be assigned here, as
29    /// this method focuses on styling attributes only. If no `class` or `style` is set,
30    /// this method will not modify the node.
31    ///
32    /// # Arguments * `node` - A mutable reference to an SVG node that implements the `svg::Node`
33    /// trait.
34    pub fn assign<T>(&self, node: &mut T)
35    where
36        T: svg::Node,
37    {
38        if let Some(class) = self.class.clone() {
39            node.assign("class", class);
40        }
41        if let Some(style) = self.style.clone() {
42            node.assign("style", style);
43        }
44    }
45
46    /// Returns the `id` attribute as an optional string slice, if set.
47    ///
48    /// # Returns
49    /// * `Some(&str)` if the `id` is present, or `None` otherwise.
50    pub fn id(&self) -> Option<&str> {
51        self.id.as_deref()
52    }
53}
54
55/// Creates a new `StyleAttr` with the specified class.
56/// This function is a convenience method to create a `StyleAttr` instance with the `class` field set.
57/// # Arguments
58/// * `str` - A string slice representing the CSS class to be assigned.
59/// # Returns
60/// An `Option<StyleAttr>` containing the new `StyleAttr` with the class set, or `None` if the class is empty.
61pub fn class(str: &str) -> Option<StyleAttr> {
62    Some(StyleAttr {
63        class: Some(str.to_string()),
64        style: None,
65        id: None,
66    })
67}
68
69/// Creates a new `StyleAttr` with the specified style.
70/// This function is a convenience method to create a `StyleAttr` instance with the `style` field set.
71/// # Arguments
72/// * `str` - A string slice representing the inline style to be assigned.
73/// # Returns
74/// An `Option<StyleAttr>` containing the new `StyleAttr` with the style set, or `None` if the style is empty.
75pub fn style(str: &str) -> Option<StyleAttr> {
76    Some(StyleAttr {
77        class: None,
78        style: Some(str.to_string()),
79        id: None,
80    })
81}
82
83/// Creates a new `StyleAttr` with the specified id.
84/// This function is a convenience method to create a `StyleAttr` instance with the `id` field set.
85/// # Arguments
86/// * `id_val` - A string slice representing the ID to be assigned.
87/// # Returns
88/// An `Option<StyleAttr>` containing the new `StyleAttr` with the id set, or `None` if the id is empty.
89pub fn id(id_val: &str) -> Option<StyleAttr> {
90    Some(StyleAttr {
91        class: None,
92        style: None,
93        id: Some(id_val.to_string()),
94    })
95}
96
97/// A [`StyleAttr`] struct with either the `class`, `style`, `id` or any combination of fields set.
98/// This macro allows for flexible creation of style attributes for SVG elements.
99/// It supports various combinations of `class`, `style`, and `id` attributes, making it easy to
100/// define the styling of SVG elements in a concise manner.
101// The macro can handle single attributes, combinations of two, or all three attributes.
102// It also provides compile-time checks for unknown keys, ensuring that only valid attributes are used.
103// The macro generates an `Option<StyleAttr>` which can be used directly in SVG rendering contexts.
104// Usage examples:
105// ```
106// css!(class: "my-class");
107// css!(style: "fill: red;");
108// css!(id: "my-id");
109// css!(class: "my-class", style: "fill: red;");
110// css!(class: "my-class", id: "my-id");
111// css!(style: "fill: red;", id: "my-id");
112// css!(class: "my-class", style: "fill: red;", id: "my-id");
113// css!(id: "my-id", class: "my-class", style: "fill: red;");
114// ```
115#[macro_export]
116macro_rules! css {
117
118    // Only class
119    ( $( class : $class_val:expr ),+ $(,)? ) => {
120        Some(
121            $crate::StyleAttr {
122                class: Some([$($class_val),+].join(" ")),
123                style: None,
124                id: None,
125            }
126        )
127    };
128    // Only style
129    ( $( style : $style_val:expr ),+ $(,)? ) => {
130        Some(
131            $crate::StyleAttr {
132                class: None,
133                style: Some([$($style_val),+].join(" ")),
134                id: None,
135            }
136        )
137    };
138    // Only id
139    ( $( id : $id_val:expr ),+ $(,)? ) => {
140        Some(
141            $crate::StyleAttr {
142                class: None,
143                style: None,
144                id: Some([$($id_val),+].join(" ")),
145            }
146        )
147    };
148    // class + style
149    ( class : $class_val:expr, style : $style_val:expr $(,)? ) => {
150        Some(
151            $crate::StyleAttr {
152                class: Some($class_val.to_string()),
153                style: Some($style_val.to_string()),
154                id: None,
155            }
156        )
157    };
158    ( style : $style_val:expr, class : $class_val:expr $(,)? ) => {
159        Some(
160            $crate::StyleAttr {
161                class: Some($class_val.to_string()),
162                style: Some($style_val.to_string()),
163                id: None,
164            }
165        )
166    };
167    // class + id
168    ( class : $class_val:expr, id : $id_val:expr $(,)? ) => {
169        Some(
170            $crate::StyleAttr {
171                class: Some($class_val.to_string()),
172                style: None,
173                id: Some($id_val.to_string()),
174            }
175        )
176    };
177    ( id : $id_val:expr, class : $class_val:expr $(,)? ) => {
178        Some(
179            $crate::StyleAttr {
180                class: Some($class_val.to_string()),
181                style: None,
182                id: Some($id_val.to_string()),
183            }
184        )
185    };
186    // style + id
187    ( style : $style_val:expr, id : $id_val:expr $(,)? ) => {
188        Some(
189            $crate::StyleAttr {
190                class: None,
191                style: Some($style_val.to_string()),
192                id: Some($id_val.to_string()),
193            }
194        )
195    };
196    ( id : $id_val:expr, style : $style_val:expr $(,)? ) => {
197        Some(
198            $crate::StyleAttr {
199                class: None,
200                style: Some($style_val.to_string()),
201                id: Some($id_val.to_string()),
202            }
203        )
204    };
205    // class + style + id (any order)
206    ( class : $class_val:expr, style : $style_val:expr, id : $id_val:expr $(,)? ) => {
207        Some(
208            $crate::StyleAttr {
209                class: Some($class_val.to_string()),
210                style: Some($style_val.to_string()),
211                id: Some($id_val.to_string()),
212            }
213        )
214    };
215    ( class : $class_val:expr, id : $id_val:expr, style : $style_val:expr $(,)? ) => {
216        Some(
217            $crate::StyleAttr {
218                class: Some($class_val.to_string()),
219                style: Some($style_val.to_string()),
220                id: Some($id_val.to_string()),
221            }
222        )
223    };
224    ( style : $style_val:expr, class : $class_val:expr, id : $id_val:expr $(,)? ) => {
225        Some(
226            $crate::StyleAttr {
227                class: Some($class_val.to_string()),
228                style: Some($style_val.to_string()),
229                id: Some($id_val.to_string()),
230            }
231        )
232    };
233    ( style : $style_val:expr, id : $id_val:expr, class : $class_val:expr $(,)? ) => {
234        Some(
235            $crate::StyleAttr {
236                class: Some($class_val.to_string()),
237                style: Some($style_val.to_string()),
238                id: Some($id_val.to_string()),
239            }
240        )
241    };
242    ( id : $id_val:expr, class : $class_val:expr, style : $style_val:expr $(,)? ) => {
243        Some(
244            $crate::StyleAttr {
245                class: Some($class_val.to_string()),
246                style: Some($style_val.to_string()),
247                id: Some($id_val.to_string()),
248            }
249        )
250    };
251    ( id : $id_val:expr, style : $style_val:expr, class : $class_val:expr $(,)? ) => {
252        Some(
253            $crate::StyleAttr {
254                class: Some($class_val.to_string()),
255                style: Some($style_val.to_string()),
256                id: Some($id_val.to_string()),
257            }
258        )
259    };
260    // Catch-all for unknown keys
261    ( $( $key:ident : $val:expr ),+ $(,)? ) => {
262        compile_error!("Unknown key in style_attr! macro. Only 'class', 'style', and 'id' are supported.");
263    };
264}
265
266#[cfg(test)]
267mod tests {
268
269    #[test]
270    fn test_style_attr_macro() {
271        let a = css!(class: "foo");
272        assert_eq!(a.as_ref().unwrap().class.as_deref(), Some("foo"));
273        assert_eq!(a.unwrap().style, None);
274
275        let b = css!(style: "stroke:red;");
276        assert_eq!(b.as_ref().unwrap().class, None);
277        assert_eq!(b.unwrap().style.as_deref(), Some("stroke:red;"));
278
279        let c = css!(class: "bar", style: "fill:blue;");
280        assert_eq!(c.as_ref().unwrap().class.as_deref(), Some("bar"));
281        assert_eq!(c.unwrap().style.as_deref(), Some("fill:blue;"));
282    }
283}