i3status_rs/
widget.rs

1use crate::config::SharedConfig;
2use crate::errors::*;
3use crate::formatting::{Format, Fragment, Values};
4use crate::protocol::i3bar_block::I3BarBlock;
5use serde::Deserialize;
6use smart_default::SmartDefault;
7
8#[derive(Debug, Clone, Default)]
9pub struct Widget {
10    pub state: State,
11    source: Source,
12    values: Values,
13}
14
15impl Widget {
16    pub fn new() -> Self {
17        Self::default()
18    }
19
20    /*
21     * Builders
22     */
23
24    pub fn with_text(mut self, text: String) -> Self {
25        self.set_text(text);
26        self
27    }
28
29    pub fn with_state(mut self, state: State) -> Self {
30        self.state = state;
31        self
32    }
33
34    pub fn with_format(mut self, format: Format) -> Self {
35        self.set_format(format);
36        self
37    }
38
39    /*
40     * Setters
41     */
42
43    pub fn set_text(&mut self, text: String) {
44        if text.is_empty() {
45            self.source = Source::None;
46        } else {
47            self.source = Source::Text(text);
48        }
49    }
50
51    pub fn set_format(&mut self, format: Format) {
52        self.source = Source::Format(format);
53    }
54
55    pub fn set_values(&mut self, new_values: Values) {
56        self.values = new_values;
57    }
58
59    pub fn intervals(&self) -> Vec<u64> {
60        match &self.source {
61            Source::Format(f) => f.intervals(),
62            _ => Vec::new(),
63        }
64    }
65
66    /// Construct `I3BarBlock` from this widget
67    pub fn get_data(&self, shared_config: &SharedConfig, id: usize) -> Result<Vec<I3BarBlock>> {
68        // Create a "template" block
69        let (key_bg, key_fg) = shared_config.theme.get_colors(self.state);
70        let (full, short) = self.source.render(shared_config, &self.values)?;
71        let mut template = I3BarBlock {
72            instance: format!("{id}:"),
73            background: key_bg,
74            color: key_fg,
75            ..I3BarBlock::default()
76        };
77
78        // Collect all the pieces into "parts"
79        let mut parts = Vec::new();
80
81        if full.is_empty() {
82            return Ok(parts);
83        }
84
85        // If short text is available, it's necessary to hide all full blocks. `swaybar`/`i3bar`
86        // will switch a block to "short mode" only if it's "short_text" is set to a non-empty
87        // string "<span/>" is a non-empty string and it doesn't display anything. It's kinda hacky,
88        // but it works.
89        if !short.is_empty() {
90            template.short_text = "<span/>".into();
91        }
92
93        parts.extend(full.into_iter().map(|w| {
94            let mut data = template.clone();
95            data.full_text = w.formatted_text();
96            if let Some(i) = &w.metadata.instance {
97                data.instance.push_str(i);
98            }
99            data
100        }));
101
102        template.full_text = "<span/>".into();
103        parts.extend(short.into_iter().map(|w| {
104            let mut data = template.clone();
105            data.short_text = w.formatted_text();
106            if let Some(i) = &w.metadata.instance {
107                data.instance.push_str(i);
108            }
109            data
110        }));
111
112        Ok(parts)
113    }
114}
115
116/// State of the widget. Affects the theming.
117#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, SmartDefault)]
118pub enum State {
119    #[default]
120    #[serde(alias = "idle")]
121    Idle,
122    #[serde(alias = "info")]
123    Info,
124    #[serde(alias = "good")]
125    Good,
126    #[serde(alias = "warning")]
127    Warning,
128    #[serde(alias = "critical")]
129    Critical,
130}
131
132/// The source of text for widget
133#[derive(Debug, Clone, SmartDefault)]
134enum Source {
135    /// Collapsed widget (only icon will be displayed)
136    #[default]
137    None,
138    /// Simple text
139    Text(String),
140    /// A format template
141    Format(Format),
142}
143
144impl Source {
145    fn render(
146        &self,
147        config: &SharedConfig,
148        values: &Values,
149    ) -> Result<(Vec<Fragment>, Vec<Fragment>)> {
150        match self {
151            Self::Text(text) => Ok((vec![text.clone().into()], vec![])),
152            Self::Format(format) => format.render(values, config),
153            Self::None => Ok((vec![], vec![])),
154        }
155    }
156}