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}