1use std::{
2 collections::HashMap,
3 hash::BuildHasher,
4 rc::Rc,
5 sync::{Arc, Mutex},
6};
7
8use anyhow::Result;
9use config::Value;
10use derive_builder::Builder;
11use derive_debug::Dbg;
12use futures::task::AtomicWaker;
13use pangocairo::functions::show_layout;
14
15#[cfg(feature = "cursor")]
16use crate::bar::CursorInfo;
17use crate::{
18 actions::Actions,
19 attrs::Attrs,
20 bar::{Dependence, PanelDrawInfo},
21 image::Image,
22 remove_array_from_config, remove_bool_from_config,
23 remove_string_from_config, Highlight, PanelHideFn, PanelShowFn, Ramp,
24};
25
26#[derive(Dbg)]
29pub enum ShowHide {
30 Default(Arc<Mutex<bool>>, Arc<AtomicWaker>),
36 #[dbg(skip)]
38 Custom(Option<PanelShowFn>, Option<PanelHideFn>),
39 None,
41}
42
43#[derive(Debug, Clone, Builder)]
46#[builder_struct_attr(allow(missing_docs))]
47#[builder_impl_attr(allow(missing_docs))]
48pub struct PanelCommon {
49 pub dependence: Dependence,
51 pub actions: Actions,
53 pub images: Vec<Image>,
55 pub visible: bool,
57}
58
59impl PanelCommon {
60 pub fn draw(
70 &self,
71 cr: &Rc<cairo::Context>,
72 text: &str,
73 attrs: &Attrs,
74 dependence: Dependence,
75 highlight: Option<Highlight>,
76 images: Vec<Image>,
77 height: i32,
78 show_hide: ShowHide,
79 dump: String,
80 ) -> Result<PanelDrawInfo> {
81 let layout = pangocairo::functions::create_layout(cr);
82 layout.set_markup(text);
83 attrs.apply_font(&layout);
84 let dims = layout.pixel_size();
85
86 let attrs = attrs.clone();
87 let bg = attrs.bg.clone().unwrap_or_default();
88
89 let (show, hide): (Option<PanelShowFn>, Option<PanelHideFn>) =
90 match show_hide {
91 ShowHide::Default(paused, waker) => {
92 let paused_ = paused.clone();
93 (
94 Some(Box::new(move || {
95 *paused.lock().unwrap() = false;
96 waker.wake();
97 Ok(())
98 })),
99 Some(Box::new(move || {
100 *paused_.lock().unwrap() = true;
101 Ok(())
102 })),
103 )
104 }
105 ShowHide::Custom(show, hide) => (show, hide),
106 ShowHide::None => (None, None),
107 };
108
109 Ok(PanelDrawInfo::new(
110 bg.adjust_dims(dims, height),
111 dependence,
112 Box::new(move |cr, _| {
113 let offset =
114 bg.draw(cr, dims.0 as f64, dims.1 as f64, height as f64)?;
115
116 for image in &images {
117 image.draw(cr)?;
118 }
119
120 cr.save()?;
121
122 cr.translate(offset, 0.0);
123 if let Some(ref highlight) = highlight {
124 highlight.draw(cr, height as f64, dims.0 as f64)?;
125 }
126
127 cr.translate(0.0, (height - dims.1) as f64 / 2.0);
128
129 attrs.apply_fg(cr);
130 show_layout(cr, &layout);
131 cr.restore()?;
132 Ok(())
133 }),
134 show,
135 hide,
136 None,
137 #[cfg(feature = "cursor")]
138 CursorInfo::Static(self.actions.get_cursor()),
139 dump,
140 ))
141 }
142
143 pub fn parse_format<S: BuildHasher>(
145 table: &mut HashMap<String, Value, S>,
146 suffix: &'static str,
147 default: &'static str,
148 ) -> String {
149 let format = remove_string_from_config(
150 format!("format{suffix}").as_str(),
151 table,
152 )
153 .unwrap_or_else(|| (*default).to_string());
154 log::debug!("got format: {:?}", format);
155 format
156 }
157
158 pub fn parse_formats<S: BuildHasher, const N: usize>(
160 table: &mut HashMap<String, Value, S>,
161 suffixes: &[&'static str; N],
162 defaults: &[&'static str; N],
163 ) -> [String; N] {
164 let mut formats = [const { String::new() }; N];
165 let mut config = suffixes.iter().zip(defaults);
166 for format in &mut formats {
167 let (suffix, default) = config.next().unwrap();
168 *format = remove_string_from_config(
169 format!("format{suffix}").as_str(),
170 table,
171 )
172 .unwrap_or_else(|| (*default).to_string());
173 }
174 log::debug!("got formats: {:?}", formats);
175 formats
176 }
177
178 pub fn parse_formats_variadic<S: BuildHasher>(
184 table: &mut HashMap<String, Value, S>,
185 default: &[&'static str],
186 ) -> Vec<String> {
187 let formats = remove_array_from_config("formats", table).map_or_else(
188 || default.iter().map(ToString::to_string).collect::<Vec<_>>(),
189 |arr| {
190 arr.into_iter()
191 .filter_map(|v| v.into_string().ok())
192 .collect::<Vec<_>>()
193 },
194 );
195 log::debug!("got formats: {:?}", formats);
196 formats
197 }
198
199 pub fn parse_attr<S: BuildHasher>(
201 table: &mut HashMap<String, Value, S>,
202 suffix: &'static str,
203 ) -> Attrs {
204 let attr =
205 remove_string_from_config(format!("attrs{suffix}").as_str(), table)
206 .map_or_else(Attrs::default, |name| {
207 Attrs::parse(name).unwrap_or_default()
208 });
209 log::debug!("got attrs: {:?}", attr);
210 attr
211 }
212
213 pub fn parse_attrs<S: BuildHasher, const N: usize>(
216 table: &mut HashMap<String, Value, S>,
217 suffixes: &[&'static str; N],
218 ) -> [Attrs; N] {
219 let mut attrs = [const { Attrs::empty() }; N];
220 let mut config = suffixes.iter();
221 for attr in &mut attrs {
222 let suffix = config.next().unwrap();
223 if let Some(name) = remove_string_from_config(
224 format!("attrs{suffix}").as_str(),
225 table,
226 ) {
227 if let Ok(res) = Attrs::parse(name) {
228 *attr = res;
229 }
230 }
231 }
232 log::debug!("got attrs: {:?}", attrs);
233 attrs
234 }
235
236 pub fn parse_ramp<S: BuildHasher>(
238 table: &mut HashMap<String, Value, S>,
239 suffix: &'static str,
240 ) -> Ramp {
241 let ramp =
242 remove_string_from_config(format!("ramp{suffix}").as_str(), table)
243 .and_then(Ramp::parse)
244 .unwrap_or_default();
245 log::debug!("got ramps: {:?}", ramp);
246 ramp
247 }
248
249 pub fn parse_ramps<S: BuildHasher, const N: usize>(
252 table: &mut HashMap<String, Value, S>,
253 suffixes: &[&'static str; N],
254 ) -> [Ramp; N] {
255 let mut ramps = [const { Ramp::empty() }; N];
256 let mut config = suffixes.iter();
257 for ramp in &mut ramps {
258 let suffix = config.next().unwrap();
259 if let Some(name) = remove_string_from_config(
260 format!("ramp{suffix}").as_str(),
261 table,
262 ) {
263 if let Some(res) = Ramp::parse(name) {
264 *ramp = res;
265 }
266 }
267 }
268 log::debug!("got ramps: {:?}", ramps);
269 ramps
270 }
271
272 pub fn parse_highlight<S: BuildHasher>(
274 table: &mut HashMap<String, Value, S>,
275 suffix: &'static str,
276 ) -> Highlight {
277 let highlight = remove_string_from_config(
278 format!("highlight{suffix}").as_str(),
279 table,
280 )
281 .and_then(Highlight::parse)
282 .unwrap_or_default();
283 log::debug!("got highlights: {:?}", highlight);
284 highlight
285 }
286
287 pub fn parse_highlights<S: BuildHasher, const N: usize>(
289 table: &mut HashMap<String, Value, S>,
290 suffixes: &[&'static str; N],
291 ) -> [Highlight; N] {
292 let mut highlights = [const { Highlight::empty() }; N];
293 let mut config = suffixes.iter();
294 for highlight in &mut highlights {
295 let suffix = config.next().unwrap();
296 if let Some(name) = remove_string_from_config(
297 format!("highlight{suffix}").as_str(),
298 table,
299 ) {
300 if let Some(res) = Highlight::parse(name) {
301 *highlight = res;
302 }
303 }
304 }
305 log::debug!("got highlights: {:?}", highlights);
306 highlights
307 }
308
309 pub fn parse_common<S: BuildHasher>(
320 table: &mut HashMap<String, Value, S>,
321 ) -> Result<Self> {
322 let mut builder = PanelCommonBuilder::default();
323
324 builder.dependence(
325 match remove_string_from_config("dependence", table)
326 .map(|s| s.to_lowercase())
327 .as_deref()
328 {
329 Some("left") => Dependence::Left,
330 Some("right") => Dependence::Right,
331 Some("both") => Dependence::Both,
332 _ => Dependence::None,
333 },
334 );
335 log::debug!("got dependence: {:?}", builder.dependence);
336
337 builder.actions(Actions::parse(table)?);
338 log::debug!("got actions: {:?}", builder.actions);
339
340 builder.images(remove_array_from_config("images", table).map_or_else(
341 Vec::new,
342 |a| {
343 a.into_iter()
344 .filter_map(|i| {
345 Image::parse(
346 i.into_string()
347 .map_err(|e| {
348 log::warn!("Failed to parse string: {e}");
349 })
350 .ok()?
351 .as_str(),
352 )
353 .map_err(|e| log::warn!("Failed to parse image: {e}"))
354 .ok()
355 })
356 .collect()
357 },
358 ));
359 log::debug!("got images: {:?}", builder.images);
360
361 builder
362 .visible(remove_bool_from_config("visible", table).unwrap_or(true));
363
364 Ok(builder.build()?)
365 }
366}