duat_term/
rules.rs

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