duat_term/
rules.rs

1use duat_core::{
2    context::{self, FixedFile},
3    form::{self, Form},
4    text::{Text, text},
5    ui::{Area as UiArea, PushSpecs},
6    widgets::{Widget, WidgetCfg},
7};
8
9use crate::{Area, Ui};
10
11/// A vertical line on screen, useful, for example, for the separation
12/// of a [`File`] and [`LineNumbers`].
13///
14/// [`File`]: duat_core::widgets::File
15/// [`LineNumbers`]: duat_core::widgets::LineNumbers
16pub struct VertRule {
17    ff: Option<FixedFile<Ui>>,
18    text: Text,
19    sep_char: SepChar,
20}
21
22impl Widget<Ui> for VertRule {
23    type Cfg = VertRuleCfg;
24
25    fn cfg() -> Self::Cfg {
26        VertRuleCfg::new()
27    }
28
29    fn update(&mut self, area: &Area) {
30        self.text = if let Some(ff) = self.ff.as_mut()
31            && let SepChar::ThreeWay(..) | SepChar::TwoWay(..) = self.sep_char
32        {
33            let (file, _) = ff.read();
34            let lines = file.printed_lines();
35            let (upper, middle, lower) = if let Some(main) = file.cursors().get_main() {
36                let main = main.line();
37                let upper = lines.iter().filter(|&(line, _)| *line < main).count();
38                let middle = lines.iter().filter(|&(line, _)| *line == main).count();
39                let lower = lines.iter().filter(|&(line, _)| *line > main).count();
40                (upper, middle, lower)
41            } else {
42                (0, lines.len(), 0)
43            };
44
45            let chars = self.sep_char.chars();
46
47            text!(
48                [VertRule.upper] { form_string(chars[0], upper) }
49                [VertRule] { form_string(chars[1], middle) }
50                [VertRule.lower] { form_string(chars[2], lower) }
51            )
52        } else {
53            let full_line =
54                format!("{}\n", self.sep_char.chars()[1]).repeat(area.height() as usize);
55
56            text!([VertRule] full_line)
57        }
58    }
59
60    fn text(&self) -> &Text {
61        &self.text
62    }
63
64    fn text_mut(&mut self) -> &mut Text {
65        &mut self.text
66    }
67
68    fn once() -> Result<(), Text> {
69        form::set_weak("VertRule", Form::dark_grey());
70        form::id_of!("VertRule.upper", "VertRule.lower");
71        Ok(())
72    }
73}
74
75/// The [`char`]s that should be printed above, equal to, and below
76/// the main line.
77#[derive(Clone)]
78enum SepChar {
79    Uniform(char),
80    /// Order: main line, other lines.
81    TwoWay(char, char),
82    /// Order: main line, above main line, below main line.
83    ThreeWay(char, char, char),
84}
85
86impl SepChar {
87    /// The [`char`]s above, equal to, and below the main line,
88    /// respectively.
89    fn chars(&self) -> [char; 3] {
90        match self {
91            SepChar::Uniform(uniform) => [*uniform, *uniform, *uniform],
92            SepChar::TwoWay(main, other) => [*other, *main, *other],
93            SepChar::ThreeWay(main, upper, lower) => [*upper, *main, *lower],
94        }
95    }
96}
97
98/// The configurations for the [`VertRule`] widget.
99#[derive(Clone)]
100pub struct VertRuleCfg {
101    sep_char: SepChar,
102    specs: PushSpecs,
103}
104
105impl VertRuleCfg {
106    /// Returns a new instance of [`VertRuleCfg`].
107    pub fn new() -> Self {
108        Self {
109            sep_char: SepChar::Uniform('│'),
110            specs: PushSpecs::left().with_hor_len(1.0),
111        }
112    }
113
114    pub fn on_the_right(self) -> Self {
115        Self {
116            specs: PushSpecs::right().with_hor_len(1.0),
117            ..self
118        }
119    }
120
121    pub fn with_char(self, char: char) -> Self {
122        Self { sep_char: SepChar::Uniform(char), ..self }
123    }
124
125    pub fn with_main_char(self, main: char) -> Self {
126        Self {
127            sep_char: match self.sep_char {
128                SepChar::Uniform(other) => SepChar::TwoWay(main, other),
129                SepChar::TwoWay(_, other) => SepChar::TwoWay(main, other),
130                SepChar::ThreeWay(_, above, below) => SepChar::ThreeWay(main, above, below),
131            },
132            ..self
133        }
134    }
135
136    pub fn with_char_above(self, above: char) -> Self {
137        Self {
138            sep_char: match self.sep_char {
139                SepChar::Uniform(other) => SepChar::ThreeWay(other, above, other),
140                SepChar::TwoWay(main, below) => SepChar::ThreeWay(main, above, below),
141                SepChar::ThreeWay(main, _, below) => SepChar::ThreeWay(main, above, below),
142            },
143            ..self
144        }
145    }
146
147    pub fn with_char_below(self, below: char) -> Self {
148        Self {
149            sep_char: match self.sep_char {
150                SepChar::Uniform(other) => SepChar::ThreeWay(other, other, below),
151                SepChar::TwoWay(main, above) => SepChar::ThreeWay(main, above, below),
152                SepChar::ThreeWay(main, above, _) => SepChar::ThreeWay(main, above, below),
153            },
154            ..self
155        }
156    }
157}
158
159impl Default for VertRuleCfg {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165impl WidgetCfg<Ui> for VertRuleCfg {
166    type Widget = VertRule;
167
168    fn build(self, on_file: bool) -> (Self::Widget, impl Fn() -> bool, PushSpecs) {
169        let ff = on_file.then_some(context::fixed_file().unwrap());
170
171        let checker = if let Some(ff) = ff.as_ref()
172            && let SepChar::TwoWay(..) | SepChar::ThreeWay(..) = self.sep_char
173        {
174            let checker = ff.checker();
175            Box::new(checker) as Box<dyn Fn() -> bool + Send + Sync>
176        } else {
177            Box::new(move || false)
178        };
179
180        let widget = VertRule {
181            ff,
182            text: Text::default(),
183            sep_char: self.sep_char,
184        };
185
186        (widget, checker, self.specs)
187    }
188}
189
190fn form_string(char: char, count: usize) -> String {
191    [char, '\n'].repeat(count).iter().collect()
192}