1use crate::block_plan::{CONTRACT_BUG, OutputHandle, OutputKind};
2use crate::config::SharedConfig;
3use crate::errors::*;
4use crate::formatting::{Format, Fragment, Values};
5use crate::protocol::i3bar_block::I3BarBlock;
6use serde::Deserialize;
7use smart_default::SmartDefault;
8
9#[derive(Debug, Clone, Default)]
10pub struct Widget {
11 pub state: State,
12 source: Source,
13 values: Values,
14 contract: Option<OutputHandle>,
17}
18
19impl Widget {
20 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn with_text(mut self, text: String) -> Self {
29 self.set_text(text);
30 self
31 }
32
33 pub fn with_state(mut self, state: State) -> Self {
34 self.state = state;
35 self
36 }
37
38 pub fn with_format(mut self, format: Format) -> Self {
39 self.set_format(format);
40 self
41 }
42
43 pub fn set_text(&mut self, text: String) {
48 if text.is_empty() {
49 self.source = Source::None;
50 } else {
51 self.source = Source::Text(text);
52 }
53 self.contract = None;
57 }
58
59 pub fn set_format(&mut self, format: Format) {
60 self.source = Source::Format(format);
61 self.contract = None;
66 }
67
68 pub fn set_values(&mut self, new_values: Values) {
69 self.values = new_values;
70 }
71
72 pub(crate) fn from_output(output: &OutputHandle) -> Self {
77 let mut widget = Self::new();
78 widget.set_output(output);
79 widget
80 }
81
82 pub(crate) fn text_from_output(output: &OutputHandle, text: String) -> Self {
85 let mut widget = Self::new();
86 widget.set_text(text);
87 widget.contract = Some(output.clone());
88 widget
89 }
90
91 pub(crate) fn set_output(&mut self, output: &OutputHandle) {
94 self.set_format(output.format().clone());
95 self.contract = Some(output.clone());
96 }
97
98 #[cfg(test)]
99 pub(crate) fn contract(&self) -> Option<&OutputHandle> {
100 self.contract.as_ref()
101 }
102
103 pub(crate) fn check_contract(&self) -> Result<()> {
109 if matches!(self.source, Source::None) {
112 return Ok(());
113 }
114 let Some(handle) = &self.contract else {
115 let kind = match self.source {
116 Source::Format(_) => "format",
117 _ => "text",
118 };
119 return Err(Error::new(format!(
120 "{CONTRACT_BUG}: a {kind} widget was published without a \
121 prepared output handle"
122 )));
123 };
124 let declared = handle.output().kind();
125 let matches_source = match self.source {
126 Source::Format(_) => declared == OutputKind::Format,
127 Source::Text(_) => declared == OutputKind::Text,
128 Source::None => true,
129 };
130 if !matches_source {
131 return Err(Error::new(format!(
132 "{CONTRACT_BUG}: output '{}' is declared as {declared:?} but \
133 the published widget renders {}",
134 handle.id(),
135 match self.source {
136 Source::Format(_) => "a format",
137 _ => "text",
138 }
139 )));
140 }
141 if let Some(violation) = self.contract_violations().into_iter().next() {
142 return Err(Error::new(violation));
143 }
144 Ok(())
145 }
146
147 pub(crate) fn contract_violations(&self) -> Vec<String> {
149 match &self.contract {
150 Some(handle) => handle.icon_violations(&self.values),
151 None => Vec::new(),
152 }
153 }
154
155 pub fn intervals(&self) -> Vec<u64> {
156 match &self.source {
157 Source::Format(f) => f.intervals(),
158 _ => Vec::new(),
159 }
160 }
161
162 pub fn get_data(&self, shared_config: &SharedConfig, id: usize) -> Result<Vec<I3BarBlock>> {
164 let (key_bg, key_fg) = shared_config.theme.get_colors(self.state);
166 let (full, short) = self.source.render(shared_config, &self.values)?;
167 let mut template = I3BarBlock {
168 instance: format!("{id}:"),
169 background: key_bg,
170 color: key_fg,
171 ..I3BarBlock::default()
172 };
173
174 let mut parts = Vec::new();
176
177 if full.is_empty() {
178 return Ok(parts);
179 }
180
181 if !short.is_empty() {
186 template.short_text = "<span/>".into();
187 }
188
189 parts.extend(full.into_iter().map(|w| {
190 let mut data = template.clone();
191 data.full_text = w.formatted_text();
192 if let Some(i) = &w.metadata.instance {
193 data.instance.push_str(i);
194 }
195 data
196 }));
197
198 template.full_text = "<span/>".into();
199 parts.extend(short.into_iter().map(|w| {
200 let mut data = template.clone();
201 data.short_text = w.formatted_text();
202 if let Some(i) = &w.metadata.instance {
203 data.instance.push_str(i);
204 }
205 data
206 }));
207
208 Ok(parts)
209 }
210}
211
212#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, SmartDefault)]
214pub enum State {
215 #[default]
216 #[serde(alias = "idle")]
217 Idle,
218 #[serde(alias = "info")]
219 Info,
220 #[serde(alias = "good")]
221 Good,
222 #[serde(alias = "warning")]
223 Warning,
224 #[serde(alias = "critical")]
225 Critical,
226}
227
228#[derive(Debug, Clone, SmartDefault)]
230enum Source {
231 #[default]
233 None,
234 Text(String),
236 Format(Format),
238}
239
240impl Source {
241 fn render(
242 &self,
243 config: &SharedConfig,
244 values: &Values,
245 ) -> Result<(Vec<Fragment>, Vec<Fragment>)> {
246 match self {
247 Self::Text(text) => Ok((vec![text.clone().into()], vec![])),
248 Self::Format(format) => format.render(values, config),
249 Self::None => Ok((vec![], vec![])),
250 }
251 }
252}