euv_core/vdom/attribute/impl.rs
1use crate::*;
2
3/// SAFETY: `InjectedClassesCell` is only used in single-threaded WASM contexts.
4unsafe impl Sync for InjectedClassesCell {}
5
6/// Implementation of attribute value factory methods for reactive and merged values.
7impl AttributeValue {
8 /// Creates a reactive attribute `Self` for conditional attribute values.
9 ///
10 /// This function replaces the inline `Signal::create(...)` + `subscribe_attr(...)`
11 /// boilerplate that was previously generated by the `html!` macro for every
12 /// attribute value containing an `if` condition.
13 ///
14 /// # Arguments
15 ///
16 /// - `Fn() -> String + 'static` - A closure that computes the current attribute value.
17 /// Called on initial render and whenever any signal changes.
18 ///
19 /// # Returns
20 ///
21 /// - `Self` - A `Self::Signal` backed by a `Signal<String>`
22 /// that reactively re-evaluates the attribute value on signal updates.
23 pub fn reactive<F>(compute: F) -> Self
24 where
25 F: Fn() -> String + 'static,
26 {
27 let attr_signal: Signal<String> = Signal::create(compute());
28 Self::subscribe_attr(attr_signal, compute);
29 Self::Signal(attr_signal)
30 }
31
32 /// Merges multiple class attribute values into a single `Self`.
33 ///
34 /// Each input value is adapted into a `Self` via `IntoReactiveValue`.
35 /// `Css` values are injected into the DOM and their names are collected.
36 /// All non-empty class names are joined with spaces into a final `Text` attribute.
37 /// If any value is signal-backed, the result becomes a reactive `Signal` attribute
38 /// that re-evaluates when any constituent signal changes.
39 ///
40 /// # Arguments
41 ///
42 /// - `&[Self]` - The class attribute values to merge.
43 ///
44 /// # Returns
45 ///
46 /// - `Self` - A merged attribute value containing space-separated class names.
47 pub fn merge_class(values: &[Self]) -> Self {
48 let has_signal: bool = values
49 .iter()
50 .any(|value: &Self| matches!(value, Self::Signal(_)));
51 if has_signal {
52 let owned_values: Vec<Self> = values.to_vec();
53 let compute: Box<dyn Fn() -> String> = Box::new(move || {
54 owned_values
55 .iter()
56 .filter_map(|value: &Self| match value {
57 Self::Css(css) => {
58 css.inject_style();
59 Some(css.get_name().to_string())
60 }
61 Self::Text(text_value) => Some(text_value.clone()),
62 Self::Signal(signal) => Some(signal.get()),
63 _ => None,
64 })
65 .filter(|segment: &String| !segment.is_empty())
66 .collect::<Vec<String>>()
67 .join(&CHAR_SPACE.to_string())
68 });
69 let attr_signal: Signal<String> = Signal::create(compute());
70 Self::subscribe_attr(attr_signal, compute);
71 Self::Signal(attr_signal)
72 } else {
73 let result: String = values
74 .iter()
75 .filter_map(|value: &Self| match value {
76 Self::Css(css) => {
77 css.inject_style();
78 Some(css.get_name().to_string())
79 }
80 Self::Text(text_value) => Some(text_value.clone()),
81 _ => None,
82 })
83 .filter(|segment: &String| !segment.is_empty())
84 .collect::<Vec<String>>()
85 .join(&CHAR_SPACE.to_string());
86 Self::Text(result)
87 }
88 }
89
90 /// Merges multiple style attribute values into a single `Self`.
91 ///
92 /// Each input value is expected to be a style string (`Text`) or a reactive
93 /// `Signal<String>` producing a style string. All non-empty style strings are
94 /// joined with spaces into a final combined style attribute.
95 /// If any value is signal-backed, the result becomes a reactive `Signal` attribute.
96 ///
97 /// # Arguments
98 ///
99 /// - `&[Self]` - The style attribute values to merge.
100 ///
101 /// # Returns
102 ///
103 /// - `Self` - A merged attribute value containing the combined CSS style string.
104 pub fn merge_style(values: &[Self]) -> Self {
105 let has_signal: bool = values
106 .iter()
107 .any(|value: &Self| matches!(value, Self::Signal(_)));
108 if has_signal {
109 let owned_values: Vec<Self> = values.to_vec();
110 let compute: Box<dyn Fn() -> String> = Box::new(move || {
111 owned_values
112 .iter()
113 .filter_map(|value: &Self| match value {
114 Self::Text(text_value) => Some(text_value.clone()),
115 Self::Signal(signal) => Some(signal.get()),
116 _ => None,
117 })
118 .filter(|segment: &String| !segment.is_empty())
119 .collect::<Vec<String>>()
120 .join(&CHAR_SPACE.to_string())
121 });
122 let attr_signal: Signal<String> = Signal::create(compute());
123 Self::subscribe_attr(attr_signal, compute);
124 Self::Signal(attr_signal)
125 } else {
126 let result: String = values
127 .iter()
128 .filter_map(|value: &Self| match value {
129 Self::Text(text_value) => Some(text_value.clone()),
130 _ => None,
131 })
132 .filter(|segment: &String| !segment.is_empty())
133 .collect::<Vec<String>>()
134 .join(&CHAR_SPACE.to_string());
135 Self::Text(result)
136 }
137 }
138
139 /// Subscribes an attribute signal to the global signal update dispatch cycle.
140 ///
141 /// Creates a callback that re-computes the attribute value and sets
142 /// it on the signal whenever a signal update cycle runs. The callback
143 /// is registered in the signal update registry using the signal's
144 /// inner address as the key.
145 ///
146 /// # Arguments
147 ///
148 /// - `Signal<String>` - The attribute signal to subscribe.
149 /// - `Fn() -> String + 'static` - A closure that computes the current attribute value string.
150 fn subscribe_attr<F>(attr_signal: Signal<String>, compute: F)
151 where
152 F: Fn() -> String + 'static,
153 {
154 Registry::register_attr_listener(
155 attr_signal.get_inner(),
156 Box::new(move || {
157 attr_signal.set(compute());
158 }),
159 );
160 }
161
162 /// Converts a bool signal into a reactive `Signal<String>` attribute value.
163 ///
164 /// Creates a `Signal<String>` initialized with the bool's string
165 /// representation, then subscribes to the source signal so that
166 /// whenever the bool changes, the string signal is updated accordingly.
167 ///
168 /// # Arguments
169 ///
170 /// - `Signal<bool>` - The source boolean signal.
171 ///
172 /// # Returns
173 ///
174 /// - `AttributeValue` - An `AttributeValue::Signal` wrapping the derived string signal.
175 pub(crate) fn bool_to_attr(source: Signal<bool>) -> AttributeValue {
176 let string_signal: Signal<String> = Signal::create(source.get().to_string());
177 let string_signal_clone: Signal<String> = string_signal;
178 let source_for_sub: Signal<bool> = source;
179 source_for_sub.subscribe(move || {
180 string_signal_clone.set(source_for_sub.get().to_string());
181 });
182 AttributeValue::Signal(string_signal)
183 }
184}
185
186/// Visual equality comparison for attribute values.
187///
188/// Compares values by their visual output rather than identity. `Signal`
189/// values are compared by their current resolved string; when both signals
190/// share the same inner pointer, they are always considered **unequal**
191/// because the signal may have mutated between VDOM snapshots and `.get()`
192/// would return the same current value for both, masking the change.
193/// `Event` values are always considered equal (re-binding is handled by the
194/// handler registry), and `Css` values are compared by class name.
195impl PartialEq for AttributeValue {
196 /// Compares two attribute values for visual equality.
197 ///
198 /// # Arguments
199 ///
200 /// - `&Self` - The first attribute value.
201 /// - `&Self` - The second attribute value.
202 ///
203 /// # Returns
204 ///
205 /// - `bool` - `true` if the values are visually equal.
206 fn eq(&self, other: &Self) -> bool {
207 match (self, other) {
208 (Self::Text(old_value), Self::Text(new_value)) => old_value == new_value,
209 (Self::Signal(old_signal), Self::Signal(new_signal)) => {
210 if old_signal.get_inner() == new_signal.get_inner() {
211 return false;
212 }
213 old_signal.get() == new_signal.get()
214 }
215 (Self::Signal(old_signal), Self::Text(new_value)) => old_signal.get() == *new_value,
216 (Self::Text(old_value), Self::Signal(new_signal)) => *old_value == new_signal.get(),
217 (Self::Event(_), Self::Event(_)) => true,
218 (Self::Css(old_class), Self::Css(new_class)) => {
219 old_class.get_name() == new_class.get_name()
220 }
221 (Self::Dynamic(old_dynamic), Self::Dynamic(new_dynamic)) => old_dynamic == new_dynamic,
222 _ => false,
223 }
224 }
225}
226
227/// Visual equality comparison for attribute entries.
228///
229/// Two attribute entries are equal when their names match and their values
230/// are visually equal as defined by `AttributeValue::eq`.
231impl PartialEq for AttributeEntry {
232 /// Compares two attribute entries for visual equality.
233 ///
234 /// # Arguments
235 ///
236 /// - `&Self` - The first attribute entry.
237 /// - `&Self` - The second attribute entry.
238 ///
239 /// # Returns
240 ///
241 /// - `bool` - `true` if both names and values match.
242 fn eq(&self, other: &Self) -> bool {
243 self.get_name() == other.get_name() && self.get_value() == other.get_value()
244 }
245}
246
247/// Visual equality comparison for CSS classes.
248///
249/// Two CSS classes are considered equal when their class names match,
250/// since the name uniquely identifies the visual style rule.
251impl PartialEq for Css {
252 /// Compares two CSS classes by name.
253 ///
254 /// # Arguments
255 ///
256 /// - `&Self` - The first CSS class.
257 /// - `&Self` - The second CSS class.
258 ///
259 /// # Returns
260 ///
261 /// - `bool` - `true` if the class names match.
262 fn eq(&self, other: &Self) -> bool {
263 self.get_name() == other.get_name()
264 }
265}
266
267/// Implementation of Css construction and style injection.
268impl Css {
269 /// Parses pseudo-class/pseudo-element rules from a compact serialization string.
270 ///
271 /// The serialization format is: `:selector { key: value; key: value; }:another { ... }`
272 /// This is used by the `class!` macro for fully static class definitions
273 /// where pseudo rules can be computed at compile time.
274 ///
275 /// # Arguments
276 ///
277 /// - `I: AsRef<str>` - The serialized pseudo rules string.
278 ///
279 /// # Returns
280 ///
281 /// - `Vec<PseudoRule>` - The parsed pseudo rules.
282 pub fn parse_pseudo_rules<I>(input: I) -> Vec<PseudoRule>
283 where
284 I: AsRef<str>,
285 {
286 let mut remaining: &str = input.as_ref();
287 let mut rules: Vec<PseudoRule> = Vec::new();
288 while !remaining.is_empty() {
289 let selector_end: Option<usize> = remaining.find(CSS_RULE_OPEN);
290 let Some(selector_end_index) = selector_end else {
291 break;
292 };
293 let selector: &str = &remaining[..selector_end_index];
294 let after_selector: &str = remaining[selector_end_index..]
295 .strip_prefix(CSS_RULE_OPEN)
296 .unwrap_or_default();
297 let style_end: Option<usize> = after_selector.find(CHAR_CSS_RULE_CLOSE);
298 let Some(style_end_index) = style_end else {
299 break;
300 };
301 let style: &str = &after_selector[..style_end_index];
302 if !selector.is_empty() && !style.is_empty() {
303 rules.push(PseudoRule::new(selector.to_string(), style.to_string()));
304 }
305 remaining = after_selector[style_end_index..]
306 .strip_prefix(CHAR_CSS_RULE_CLOSE)
307 .unwrap_or_default();
308 }
309 rules
310 }
311
312 /// Parses media query rules from a compact serialization string.
313 ///
314 /// The serialization format is:
315 /// `@media query { key: value; ::selector { key: value; } }@media query2 { ... }`
316 /// This is used by the `class!` macro for fully static class definitions
317 /// where media rules can be computed at compile time.
318 /// Supports nested pseudo-element blocks inside media query blocks.
319 ///
320 /// # Arguments
321 ///
322 /// - `S: AsRef<str>` - The serialized media rules string.
323 ///
324 /// # Returns
325 ///
326 /// - `Vec<MediaRule>` - The parsed media rules.
327 pub fn parse_media_rules<S>(input: S) -> Vec<MediaRule>
328 where
329 S: AsRef<str>,
330 {
331 let input: &str = input.as_ref();
332 let mut rules: Vec<MediaRule> = Vec::new();
333 let mut remaining: &str = input;
334 while !remaining.is_empty() {
335 if !remaining.starts_with(CSS_MEDIA_PREFIX) {
336 break;
337 }
338 let after_prefix: &str = remaining.strip_prefix(CSS_MEDIA_PREFIX).unwrap_or_default();
339 let query_end: Option<usize> = after_prefix.find(CSS_RULE_OPEN);
340 let Some(query_end_index) = query_end else {
341 break;
342 };
343 let query: &str = &after_prefix[..query_end_index];
344 let after_query: &str = after_prefix[query_end_index..]
345 .strip_prefix(CSS_RULE_OPEN)
346 .unwrap_or_default();
347 let mut depth: usize = 1;
348 let mut close_pos: usize = 0;
349 for (index, char_value) in after_query.char_indices() {
350 if char_value == '{' {
351 depth += 1;
352 } else if char_value == '}' {
353 depth -= 1;
354 if depth == 0 {
355 close_pos = index;
356 break;
357 }
358 }
359 }
360 if close_pos == 0 {
361 break;
362 }
363 let body: &str = &after_query[..close_pos];
364 let (style, pseudo_rules): (String, Vec<PseudoRule>) = Self::parse_media_body(body);
365 if !query.is_empty() && (!style.is_empty() || !pseudo_rules.is_empty()) {
366 rules.push(MediaRule::new(query.to_string(), style, pseudo_rules));
367 }
368 remaining = after_query[close_pos..]
369 .strip_prefix(CHAR_CSS_RULE_CLOSE)
370 .unwrap_or_default();
371 }
372 rules
373 }
374
375 /// Parses the body of a media rule, separating top-level style declarations
376 /// from nested pseudo-element blocks.
377 ///
378 /// # Arguments
379 ///
380 /// - `&str` - The media rule body content (between the outer braces).
381 ///
382 /// # Returns
383 ///
384 /// - `(String, Vec<PseudoRule>)` - A tuple of the style string and pseudo rules.
385 fn parse_media_body(body: &str) -> (String, Vec<PseudoRule>) {
386 let mut style_parts: String = String::new();
387 let mut pseudo_rules: Vec<PseudoRule> = Vec::new();
388 let mut remaining: &str = body;
389 while !remaining.is_empty() {
390 let brace_pos: Option<usize> = remaining.find('{');
391 match brace_pos {
392 Some(pos) => {
393 let before_brace: &str = remaining[..pos].trim();
394 if before_brace.starts_with("::") || before_brace.starts_with(':') {
395 let selector: &str = before_brace;
396 let after_brace: &str = &remaining[pos + 1..];
397 let mut depth: usize = 1;
398 let mut close_pos: usize = 0;
399 for (index, char_value) in after_brace.char_indices() {
400 if char_value == '{' {
401 depth += 1;
402 } else if char_value == '}' {
403 depth -= 1;
404 if depth == 0 {
405 close_pos = index;
406 break;
407 }
408 }
409 }
410 if close_pos > 0 {
411 let inner_style: &str = after_brace[..close_pos].trim();
412 if !selector.is_empty() && !inner_style.is_empty() {
413 pseudo_rules.push(PseudoRule::new(
414 selector.to_string(),
415 inner_style.to_string(),
416 ));
417 }
418 remaining = after_brace[close_pos + 1..].trim_start();
419 continue;
420 }
421 break;
422 } else {
423 style_parts.push_str(before_brace);
424 style_parts.push(' ');
425 let after_brace: &str = &remaining[pos + 1..];
426 let mut depth: usize = 1;
427 let mut close_pos: usize = 0;
428 for (index, char_value) in after_brace.char_indices() {
429 if char_value == '{' {
430 depth += 1;
431 } else if char_value == '}' {
432 depth -= 1;
433 if depth == 0 {
434 close_pos = index;
435 break;
436 }
437 }
438 }
439 if close_pos > 0 {
440 style_parts.push_str(after_brace[..close_pos].trim());
441 style_parts.push(' ');
442 remaining = after_brace[close_pos + 1..].trim_start();
443 continue;
444 }
445 break;
446 }
447 }
448 None => {
449 style_parts.push_str(remaining.trim());
450 break;
451 }
452 }
453 }
454 (style_parts.trim().to_string(), pseudo_rules)
455 }
456
457 /// Injects this class's styles into the DOM if not already present.
458 ///
459 /// Uses a global `HashSet` to track injected class names, avoiding the
460 /// expensive `existing_css.contains(css)` full-text search on every call.
461 /// Builds the class rule, pseudo-class rules, and media rules as CSS text,
462 /// then appends them directly to the `<style>` element via
463 /// `append_child` with a new text node — no read-modify-write of the
464 /// entire stylesheet content.
465 ///
466 /// # Panics
467 ///
468 /// Panics if `window()` or `document()` is unavailable on the current platform.
469 pub fn inject_style(&self) {
470 if !Self::mark_injected(self.get_name().clone()) {
471 return;
472 }
473 let mut css_text: String = format!(
474 "{CHAR_CSS_CLASS_PREFIX}{}{CSS_RULE_OPEN_FORMAT}{}{CSS_RULE_CLOSE_FORMAT}",
475 self.get_name(),
476 self.get_style()
477 );
478 for pseudo_rule in self.get_pseudo_rules() {
479 if !pseudo_rule.get_style().is_empty() {
480 css_text = format!(
481 "{css_text}{CHAR_CSS_RULE_SEPARATOR}{CHAR_CSS_CLASS_PREFIX}{}{}{CSS_RULE_OPEN_FORMAT}{}{CSS_RULE_CLOSE_FORMAT}",
482 self.get_name(),
483 pseudo_rule.get_selector(),
484 pseudo_rule.get_style()
485 );
486 }
487 }
488 for media_rule in self.get_media_rules() {
489 if !media_rule.get_query().is_empty() {
490 let mut media_body: String = format!(
491 "{CHAR_CSS_CLASS_PREFIX}{}{CSS_RULE_OPEN_FORMAT}{}{CSS_RULE_CLOSE_FORMAT}",
492 self.get_name(),
493 media_rule.get_style()
494 );
495 for pseudo_rule in media_rule.get_pseudo_rules() {
496 if !pseudo_rule.get_style().is_empty() {
497 media_body = format!(
498 "{media_body} {CHAR_CSS_CLASS_PREFIX}{}{}{CSS_RULE_OPEN_FORMAT}{}{CSS_RULE_CLOSE_FORMAT}",
499 self.get_name(),
500 pseudo_rule.get_selector(),
501 pseudo_rule.get_style()
502 );
503 }
504 }
505 css_text = format!(
506 "{css_text}{CHAR_CSS_RULE_SEPARATOR}{CSS_MEDIA_PREFIX}{}{CSS_RULE_OPEN_FORMAT}{}{CSS_RULE_CLOSE_FORMAT}",
507 media_rule.get_query(),
508 media_body
509 );
510 }
511 }
512 Self::append_css(&css_text);
513 }
514
515 /// Marks a class name as injected in the global `HashSet`.
516 ///
517 /// Returns `false` if the class was already injected (no-op), `true`
518 /// if this is the first injection.
519 ///
520 /// # Arguments
521 ///
522 /// - `String` - The class name to mark as injected.
523 ///
524 /// # Returns
525 ///
526 /// - `bool` - `true` if newly injected, `false` if already present.
527 fn mark_injected(class_name: String) -> bool {
528 Self::get_injected_classes_mut().insert(class_name)
529 }
530
531 /// Returns a mutable reference to the global injected classes set.
532 ///
533 /// Lazily initializes the set on first access.
534 #[allow(static_mut_refs)]
535 fn get_injected_classes_mut() -> &'static mut HashSet<String> {
536 unsafe {
537 if (*INJECTED_CLASSES.get_0().get()).is_none() {
538 (*INJECTED_CLASSES.get_0().get()) = Some(HashSet::new());
539 }
540 (*INJECTED_CLASSES.get_0().get())
541 .as_mut()
542 .unwrap_unchecked()
543 }
544 }
545
546 /// Appends CSS text directly to the shared `<style>` element.
547 ///
548 /// Creates a new text node and appends it as a child of the `<style>`
549 /// element, avoiding the read-modify-write pattern of reading the entire
550 /// `innerText`, concatenating, and setting it back.
551 ///
552 /// # Arguments
553 ///
554 /// - `&str` - The CSS text to append.
555 ///
556 fn append_css(css_text: &str) {
557 let style_id: &str = EUV_CSS_INJECTED_ID;
558 let window_value: Window = match window() {
559 Some(window_instance) => window_instance,
560 None => return,
561 };
562 let document: Document = match window_value.document() {
563 Some(document_instance) => document_instance,
564 None => return,
565 };
566 let style_element: HtmlStyleElement = match document.get_element_by_id(style_id) {
567 Some(existing_element) => match existing_element.dyn_into::<HtmlStyleElement>() {
568 Ok(element) => element,
569 Err(_err) => return,
570 },
571 None => {
572 let created: Element = match document.create_element(STYLE_TAG) {
573 Ok(element) => element,
574 Err(_err) => return,
575 };
576 let style_element_from_id: HtmlStyleElement =
577 match created.dyn_into::<HtmlStyleElement>() {
578 Ok(element) => element,
579 Err(_err) => return,
580 };
581 style_element_from_id.set_id(style_id);
582 if let Some(head) = document.head() {
583 let _ = head.append_child(&style_element_from_id);
584 }
585 style_element_from_id
586 }
587 };
588 if !css_text.is_empty() {
589 let text_node: Text = document.create_text_node(css_text);
590 let _ = style_element.append_child(&text_node);
591 }
592 }
593
594 /// Builds a CSS style string from an array of key-value pairs.
595 ///
596 /// This function is used by the `html!` macro to convert static `style:`
597 /// attributes into a CSS string without allocating intermediate objects.
598 ///
599 /// # Arguments
600 ///
601 /// - `S: AsRef<str>` - An array of CSS property name-value pairs.
602 ///
603 /// # Returns
604 ///
605 /// - `String` - The CSS string (e.g., `"margin: 0 auto; max-width: 800px;"`).
606 pub fn style_string<K, V>(props: &[(K, V)]) -> String
607 where
608 K: AsRef<str>,
609 V: AsRef<str>,
610 {
611 props
612 .iter()
613 .map(|(key, value): &(K, V)| {
614 format!(
615 "{}{CSS_PROP_SEPARATOR}{}{CHAR_CSS_DECL_TERMINATOR}",
616 key.as_ref(),
617 value.as_ref()
618 )
619 })
620 .collect::<Vec<String>>()
621 .join(&CHAR_SPACE.to_string())
622 }
623
624 /// Builds a CSS style string from owned key-value pairs.
625 ///
626 /// Used by the `html!` macro for reactive style attributes (with `if`
627 /// conditions) where values are computed at runtime.
628 ///
629 /// # Arguments
630 ///
631 /// - `&[(String, String)]` - An array of owned CSS property name-value pairs.
632 ///
633 /// # Returns
634 ///
635 /// - `String` - The CSS string (e.g., `"margin: 0 auto; max-width: 800px;"`).
636 pub fn style_string_owned(props: &[(String, String)]) -> String {
637 props
638 .iter()
639 .map(|(key, value): &(String, String)| {
640 format!("{key}{CSS_PROP_SEPARATOR}{value}{CHAR_CSS_DECL_TERMINATOR}")
641 })
642 .collect::<Vec<String>>()
643 .join(&CHAR_SPACE.to_string())
644 }
645
646 /// Injects CSS text into the shared `<style>` element in the DOM.
647 ///
648 /// Delegates to [`Css::append_css`] for the actual DOM append.
649 /// Unlike the previous implementation, this does not read the existing
650 /// stylesheet content or perform a full-text `contains` search.
651 ///
652 /// # Arguments
653 ///
654 /// - `S: AsRef<str>` - The CSS text to inject (e.g., reset styles, keyframes, media queries).
655 ///
656 /// # Panics
657 ///
658 /// Panics if `window()` or `document()` is unavailable on the current platform.
659 pub fn inject_css<S>(css_text: S)
660 where
661 S: AsRef<str>,
662 {
663 let css_text: &str = css_text.as_ref();
664 Self::append_css(css_text);
665 }
666}
667
668/// Displays the CSS class name.
669///
670/// This enables `format!("{}", css)` to produce the class name string,
671/// which is required for reactive `if` conditions in `class:` attributes.
672impl Display for Css {
673 /// Formats the CSS class as its name string.
674 ///
675 /// # Arguments
676 ///
677 /// - `&mut Formatter` - The formatter.
678 ///
679 /// # Returns
680 ///
681 /// - `fmt::Result` - The formatting result.
682 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
683 write!(formatter, "{}", self.get_name())
684 }
685}