Skip to main content

pathland_core/
window.rs

1//! Window/surface presentation metadata (`META::SET_WINDOW_METADATA`,
2//! spec/OPCODE.md §Window metadata).
3//!
4//! These are the *protocol* fields — presentation metadata for the host
5//! window/surface that owns an opcode stream. Each renderer maps them onto its
6//! own chrome (browser `<title>`/meta description/`<html lang>`, GTK window
7//! title, Qt window title, …). The web mapping is never the definition.
8
9use alloc::string::String;
10
11use crate::constants::window_field;
12
13/// Decoded window/surface metadata.
14///
15/// A field is `None` when unset (the renderer applies its default). An empty
16/// wire string clears the field, so [`Self::set`] stores `None` for `""`.
17#[derive(Debug, Clone, Default, PartialEq, Eq)]
18pub struct WindowMeta {
19    /// Window/surface title.
20    pub title: Option<String>,
21    /// Human-readable description of the current content.
22    pub description: Option<String>,
23    /// Content locale (BCP 47).
24    pub locale: Option<String>,
25}
26
27impl WindowMeta {
28    /// Create empty metadata (every field unset).
29    #[must_use]
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Set one field from a decoded string. An empty value clears the field.
35    pub fn set(&mut self, field: u16, value: &str) {
36        let slot = self.slot_mut(field);
37        if let Some(slot) = slot {
38            *slot = if value.is_empty() {
39                None
40            } else {
41                Some(String::from(value))
42            };
43        }
44    }
45
46    /// Clear one field.
47    pub fn clear(&mut self, field: u16) {
48        if let Some(slot) = self.slot_mut(field) {
49            *slot = None;
50        }
51    }
52
53    /// The value of `field`, if set.
54    #[must_use]
55    pub fn get(&self, field: u16) -> Option<&str> {
56        self.slot(field).and_then(Option::as_deref)
57    }
58
59    /// Whether every field is unset.
60    #[must_use]
61    pub fn is_empty(&self) -> bool {
62        self.title.is_none() && self.description.is_none() && self.locale.is_none()
63    }
64
65    fn slot(&self, field: u16) -> Option<&Option<String>> {
66        match field {
67            window_field::TITLE => Some(&self.title),
68            window_field::DESCRIPTION => Some(&self.description),
69            window_field::LOCALE => Some(&self.locale),
70            _ => None,
71        }
72    }
73
74    fn slot_mut(&mut self, field: u16) -> Option<&mut Option<String>> {
75        match field {
76            window_field::TITLE => Some(&mut self.title),
77            window_field::DESCRIPTION => Some(&mut self.description),
78            window_field::LOCALE => Some(&mut self.locale),
79            _ => None,
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn set_get_clear_and_empty_clears() {
90        let mut meta = WindowMeta::new();
91        assert!(meta.is_empty());
92        meta.set(window_field::TITLE, "Playlist");
93        meta.set(window_field::DESCRIPTION, "8 songs");
94        meta.set(window_field::LOCALE, "en");
95        assert_eq!(meta.title.as_deref(), Some("Playlist"));
96        assert_eq!(meta.get(window_field::DESCRIPTION), Some("8 songs"));
97        assert_eq!(meta.locale.as_deref(), Some("en"));
98
99        meta.set(window_field::TITLE, "");
100        assert_eq!(meta.title, None);
101        meta.clear(window_field::LOCALE);
102        assert_eq!(meta.locale, None);
103        assert!(!meta.is_empty());
104    }
105
106    #[test]
107    fn unknown_field_is_ignored() {
108        let mut meta = WindowMeta::new();
109        meta.set(0x00FF, "ignored");
110        assert!(meta.is_empty());
111        assert_eq!(meta.get(0x00FF), None);
112    }
113}