1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
use std::{
    collections::HashMap,
    hash::BuildHasher,
    rc::Rc,
    sync::{Arc, Mutex},
};

use anyhow::Result;
use config::Value;
use derive_builder::Builder;
use derive_debug::Dbg;
use futures::task::AtomicWaker;
use pangocairo::functions::show_layout;

use crate::{
    actions::Actions,
    attrs::Attrs,
    bar::{CursorInfo, Dependence, PanelDrawInfo},
    image::Image,
    remove_array_from_config, remove_bool_from_config,
    remove_string_from_config, Highlight, PanelHideFn, PanelShowFn, Ramp,
};

/// A [`PanelShowFn`] and a [`PanelHideFn`] bundled together. Only for use with
/// [`draw_common`].
#[derive(Dbg)]
pub enum ShowHide {
    /// This is designed for use with a [`ManagedIntervalStream`], but other
    /// streams can also work.
    ///
    /// The show function sets the bool to false and wakes the Waker. The hide
    /// function sets the bool to true (indicating paused).
    Default(Arc<Mutex<bool>>, Arc<AtomicWaker>),
    /// The functions are provided.
    #[dbg(skip)]
    Custom(Option<PanelShowFn>, Option<PanelHideFn>),
    /// The functions are no-ops.
    None,
}

/// The common part of most [`PanelConfigs`][crate::PanelConfig]. Stores format
/// strings, [`Attrs`], and [`Dependence`]
#[derive(Debug, Clone, Builder)]
#[builder_struct_attr(allow(missing_docs))]
#[builder_impl_attr(allow(missing_docs))]
pub struct PanelCommon {
    /// Whether the panel depends on its neighbors
    pub dependence: Dependence,
    /// The events that should be run on mouse events
    pub actions: Actions,
    /// The images that will be displayed on the bar
    pub images: Vec<Image>,
    /// Whether the panel should be visible on startup
    pub visible: bool,
}

impl PanelCommon {
    /// The end of a typical draw function.
    ///
    /// Takes a cairo context, a string to
    /// display, and attributes to use, and returns a closure that will do the
    /// drawing and a tuple representing the final width and height.
    ///
    /// The text will be interpreted as markup. If this is not your intended
    /// behavior, use [`markup_escape_text`][crate::markup_escape_text] to
    /// display what you want or implement this functionality manually.
    pub fn draw(
        &self,
        cr: &Rc<cairo::Context>,
        text: &str,
        attrs: &Attrs,
        dependence: Dependence,
        highlight: Option<Highlight>,
        images: Vec<Image>,
        height: i32,
        show_hide: ShowHide,
    ) -> Result<PanelDrawInfo> {
        let layout = pangocairo::functions::create_layout(cr);
        layout.set_markup(text);
        attrs.apply_font(&layout);
        let dims = layout.pixel_size();

        let attrs = attrs.clone();
        let bg = attrs.bg.clone().unwrap_or_default();

        let (show, hide): (Option<PanelShowFn>, Option<PanelHideFn>) =
            match show_hide {
                ShowHide::Default(paused, waker) => {
                    let paused_ = paused.clone();
                    (
                        Some(Box::new(move || {
                            *paused.lock().unwrap() = false;
                            waker.wake();
                            Ok(())
                        })),
                        Some(Box::new(move || {
                            *paused_.lock().unwrap() = true;
                            Ok(())
                        })),
                    )
                }
                ShowHide::Custom(show, hide) => (show, hide),
                ShowHide::None => (None, None),
            };

        Ok(PanelDrawInfo::new(
            bg.adjust_dims(dims, height),
            dependence,
            Box::new(move |cr, _| {
                let offset =
                    bg.draw(cr, dims.0 as f64, dims.1 as f64, height as f64)?;

                for image in &images {
                    image.draw(cr)?;
                }

                cr.save()?;

                cr.translate(offset, 0.0);
                if let Some(ref highlight) = highlight {
                    highlight.draw(cr, height as f64, dims.0 as f64)?;
                }

                cr.translate(0.0, (height - dims.1) as f64 / 2.0);

                attrs.apply_fg(cr);
                show_layout(cr, &layout);
                cr.restore()?;
                Ok(())
            }),
            show,
            hide,
            None,
            CursorInfo::Static(self.actions.get_cursor()),
        ))
    }

    /// Parses a single format from a subset of the global config.
    pub fn parse_format<S: BuildHasher>(
        table: &mut HashMap<String, Value, S>,
        suffix: &'static str,
        default: &'static str,
    ) -> String {
        let format = remove_string_from_config(
            format!("format{suffix}").as_str(),
            table,
        )
        .unwrap_or_else(|| (*default).to_string());
        log::debug!("got format: {:?}", format);
        format
    }

    /// Parses a fixed-size group of formats from a subset of the global config.
    pub fn parse_formats<S: BuildHasher, const N: usize>(
        table: &mut HashMap<String, Value, S>,
        suffixes: &[&'static str; N],
        defaults: &[&'static str; N],
    ) -> [String; N] {
        let mut formats = [const { String::new() }; N];
        let mut config = suffixes.iter().zip(defaults);
        for format in &mut formats {
            let (suffix, default) = config.next().unwrap();
            *format = remove_string_from_config(
                format!("format{suffix}").as_str(),
                table,
            )
            .unwrap_or_else(|| (*default).to_string());
        }
        log::debug!("got formats: {:?}", formats);
        formats
    }

    /// Parses a variable-size group of formats from a subset of the global
    /// config.
    ///
    /// The formats should be specified with `formats = ["format1", "format2",
    /// ...]`
    pub fn parse_formats_variadic<S: BuildHasher>(
        table: &mut HashMap<String, Value, S>,
        default: &[&'static str],
    ) -> Vec<String> {
        let formats = remove_array_from_config("formats", table).map_or_else(
            || default.iter().map(ToString::to_string).collect::<Vec<_>>(),
            |arr| {
                arr.into_iter()
                    .filter_map(|v| v.into_string().ok())
                    .collect::<Vec<_>>()
            },
        );
        log::debug!("got formats: {:?}", formats);
        formats
    }

    /// Parses a single [`Attrs`] from a subset of the global config.
    pub fn parse_attr<S: BuildHasher>(
        table: &mut HashMap<String, Value, S>,
        suffix: &'static str,
    ) -> Attrs {
        let attr =
            remove_string_from_config(format!("attrs{suffix}").as_str(), table)
                .map_or_else(Attrs::default, |name| {
                    Attrs::parse(name).unwrap_or_default()
                });
        log::debug!("got attrs: {:?}", attr);
        attr
    }

    /// Parses a fixed-size group of [`Attrs`] from a subset of the global
    /// config.
    pub fn parse_attrs<S: BuildHasher, const N: usize>(
        table: &mut HashMap<String, Value, S>,
        suffixes: &[&'static str; N],
    ) -> [Attrs; N] {
        let mut attrs = [const { Attrs::empty() }; N];
        let mut config = suffixes.iter();
        for attr in &mut attrs {
            let suffix = config.next().unwrap();
            if let Some(name) = remove_string_from_config(
                format!("attrs{suffix}").as_str(),
                table,
            ) {
                if let Ok(res) = Attrs::parse(name) {
                    *attr = res;
                }
            }
        }
        log::debug!("got attrs: {:?}", attrs);
        attrs
    }

    /// Parses a single [`Ramp`] from a subset of the global config.
    pub fn parse_ramp<S: BuildHasher>(
        table: &mut HashMap<String, Value, S>,
        suffix: &'static str,
    ) -> Ramp {
        let ramp =
            remove_string_from_config(format!("ramp{suffix}").as_str(), table)
                .and_then(Ramp::parse)
                .unwrap_or_default();
        log::debug!("got ramps: {:?}", ramp);
        ramp
    }

    /// Parses a fixed-size group of [`Ramp`]s from a subset of the global
    /// config.
    pub fn parse_ramps<S: BuildHasher, const N: usize>(
        table: &mut HashMap<String, Value, S>,
        suffixes: &[&'static str; N],
    ) -> [Ramp; N] {
        let mut ramps = [const { Ramp::empty() }; N];
        let mut config = suffixes.iter();
        for ramp in &mut ramps {
            let suffix = config.next().unwrap();
            if let Some(name) = remove_string_from_config(
                format!("ramp{suffix}").as_str(),
                table,
            ) {
                if let Some(res) = Ramp::parse(name) {
                    *ramp = res;
                }
            }
        }
        log::debug!("got ramps: {:?}", ramps);
        ramps
    }

    /// Parses a single [`Highlight`] from a subset of the global config.
    pub fn parse_highlight<S: BuildHasher>(
        table: &mut HashMap<String, Value, S>,
        suffix: &'static str,
    ) -> Highlight {
        let highlight = remove_string_from_config(
            format!("highlight{suffix}").as_str(),
            table,
        )
        .and_then(Highlight::parse)
        .unwrap_or_default();
        log::debug!("got highlights: {:?}", highlight);
        highlight
    }

    /// Parses a fixed-size group of [`Highlight`]s from the global config.
    pub fn parse_highlights<S: BuildHasher, const N: usize>(
        table: &mut HashMap<String, Value, S>,
        suffixes: &[&'static str; N],
    ) -> [Highlight; N] {
        let mut highlights = [const { Highlight::empty() }; N];
        let mut config = suffixes.iter();
        for highlight in &mut highlights {
            let suffix = config.next().unwrap();
            if let Some(name) = remove_string_from_config(
                format!("highlight{suffix}").as_str(),
                table,
            ) {
                if let Some(res) = Highlight::parse(name) {
                    *highlight = res;
                }
            }
        }
        log::debug!("got highlights: {:?}", highlights);
        highlights
    }

    /// Attempts to parse common panel configuration options from a subset of
    /// the global [`Config`][config::Config]. The format suffixes and defaults
    /// and attrs prefixes are documented by each panel.
    ///
    /// Format strings should be specified as `format{suffix} = "value"`. Where
    /// not noted, panels accept one format string with no suffix.
    ///
    /// Dependence should be specified as `dependence = "value"`, where value is
    /// a valid variant of [`Dependence`].
    ///
    /// See [`Actions::parse`] and [`Image::parse`] for more parsing details.
    pub fn parse_common<S: BuildHasher>(
        table: &mut HashMap<String, Value, S>,
    ) -> Result<Self> {
        let mut builder = PanelCommonBuilder::default();

        builder.dependence(
            match remove_string_from_config("dependence", table)
                .map(|s| s.to_lowercase())
                .as_deref()
            {
                Some("left") => Dependence::Left,
                Some("right") => Dependence::Right,
                Some("both") => Dependence::Both,
                _ => Dependence::None,
            },
        );
        log::debug!("got dependence: {:?}", builder.dependence);

        builder.actions(Actions::parse(table)?);
        log::debug!("got actions: {:?}", builder.actions);

        builder.images(remove_array_from_config("images", table).map_or_else(
            Vec::new,
            |a| {
                a.into_iter()
                    .filter_map(|i| {
                        Image::parse(
                            i.into_string()
                                .map_err(|e| {
                                    log::warn!("Failed to parse string: {e}");
                                })
                                .ok()?
                                .as_str(),
                        )
                        .map_err(|e| log::warn!("Failed to parse image: {e}"))
                        .ok()
                    })
                    .collect()
            },
        ));
        log::debug!("got images: {:?}", builder.images);

        builder
            .visible(remove_bool_from_config("visible", table).unwrap_or(true));

        Ok(builder.build()?)
    }
}