duat_term/
rules.rs

1use duat_core::prelude::*;
2
3use crate::Ui;
4
5/// A vertical line on screen, useful, for example, for the separation
6/// of a [`File`] and [`LineNumbers`].
7///
8/// By default, this [`VertRule`] will show the `'│'` character on the
9/// whole line, using the `"default"` form. However, with the
10/// following options:
11///
12/// - [`VertRuleCfg::with_main_char`]
13/// - [`VertRuleCfg::with_char_above`]
14/// - [`VertRuleCfg::with_char_below`]
15/// - [`VertRuleCfg::with_char`]
16///
17/// If the main character is not the same as the other two characters,
18/// then the line will be printed with the `"rule.upper"` and
19/// `"rule.lower"` forms for the characters above and below.
20///
21/// If you want them to have the same characer, but printing with
22/// these different forms, you can just call [`with_main_char`] and
23/// set it to the same character.
24///
25/// [`File`]: duat_core::file::File
26/// [`LineNumbers`]: https://docs.rs/duat-utils/latest/duat_utils/widgets/struct.LineNumbers.html
27/// [`with_main_char`]: VertRuleCfg::with_main_char
28pub struct VertRule {
29    handle: Option<FileHandle<Ui>>,
30    text: Text,
31    sep_char: SepChar,
32}
33
34impl Widget<Ui> for VertRule {
35    type Cfg = VertRuleCfg;
36
37    fn update(pa: &mut Pass, handle: Handle<Self, Ui>) {
38        let text = handle.read(pa, |wid, area| {
39            if let Some(handle) = wid.handle.as_ref()
40                && let SepChar::ThreeWay(..) | SepChar::TwoWay(..) = wid.sep_char
41            {
42                let (upper, middle, lower) = handle.read(pa, |file, _| {
43                    let lines = file.printed_lines();
44                    if let Some(main) = file.selections().get_main() {
45                        let main = main.line();
46                        let upper = lines.iter().filter(|&(line, _)| *line < main).count();
47                        let middle = lines.iter().filter(|&(line, _)| *line == main).count();
48                        let lower = lines.iter().filter(|&(line, _)| *line > main).count();
49                        (upper, middle, lower)
50                    } else {
51                        (0, lines.len(), 0)
52                    }
53                });
54
55                let chars = wid.sep_char.chars();
56                txt!(
57                    "[rule.upper]{}[]{}[rule.lower]{}",
58                    form_string(chars[0], upper),
59                    form_string(chars[1], middle),
60                    form_string(chars[2], lower)
61                )
62                .build()
63            } else {
64                let full_line =
65                    format!("{}\n", wid.sep_char.chars()[1]).repeat(area.height() as usize);
66
67                txt!("{full_line}").build()
68            }
69        });
70
71        handle.widget().replace_text(pa, text);
72    }
73
74    fn needs_update(&self) -> bool {
75        matches!(self.sep_char, SepChar::ThreeWay(..) | SepChar::TwoWay(..))
76            && self.handle.as_ref().is_some_and(FileHandle::has_changed)
77    }
78
79    fn cfg() -> Self::Cfg {
80        VertRuleCfg::new()
81    }
82
83    fn text(&self) -> &Text {
84        &self.text
85    }
86
87    fn text_mut(&mut self) -> &mut Text {
88        &mut self.text
89    }
90
91    fn once() -> Result<(), Text> {
92        form::set_weak("rule.upper", "default.VertRule");
93        form::set_weak("rule.lower", "default.VertRule");
94        Ok(())
95    }
96}
97
98/// The [`char`]s that should be printed above, equal to, and below
99/// the main line.
100#[derive(Clone)]
101enum SepChar {
102    Uniform(char),
103    /// Order: main line, other lines.
104    TwoWay(char, char),
105    /// Order: main line, above main line, below main line.
106    ThreeWay(char, char, char),
107}
108
109impl SepChar {
110    /// The [`char`]s above, equal to, and below the main line,
111    /// respectively.
112    fn chars(&self) -> [char; 3] {
113        match self {
114            SepChar::Uniform(uniform) => [*uniform, *uniform, *uniform],
115            SepChar::TwoWay(main, other) => [*other, *main, *other],
116            SepChar::ThreeWay(main, upper, lower) => [*upper, *main, *lower],
117        }
118    }
119}
120
121/// The configurations for the [`VertRule`] widget.
122#[derive(Clone)]
123pub struct VertRuleCfg {
124    sep_char: SepChar,
125    specs: PushSpecs,
126}
127
128impl VertRuleCfg {
129    /// Returns a new instance of [`VertRuleCfg`].
130    pub fn new() -> Self {
131        Self {
132            sep_char: SepChar::Uniform('│'),
133            specs: PushSpecs::left().with_hor_len(1.0),
134        }
135    }
136
137    /// Puts this [`VertRule`] on the right
138    pub fn on_the_right(self) -> Self {
139        Self {
140            specs: PushSpecs::right().with_hor_len(1.0),
141            ..self
142        }
143    }
144
145    /// Sets a [`char`] to be displayed, is `'│'` by default
146    pub fn with_char(self, char: char) -> Self {
147        Self { sep_char: SepChar::Uniform(char), ..self }
148    }
149
150    /// Sets a [`char`] to be displayed on the main line only, is
151    /// `'│'` by default
152    ///
153    /// The lower and upper ranges are unaffected by this option.
154    pub fn with_main_char(self, main: char) -> Self {
155        Self {
156            sep_char: match self.sep_char {
157                SepChar::Uniform(other) => SepChar::TwoWay(main, other),
158                SepChar::TwoWay(_, other) => SepChar::TwoWay(main, other),
159                SepChar::ThreeWay(_, above, below) => SepChar::ThreeWay(main, above, below),
160            },
161            ..self
162        }
163    }
164
165    /// Sets a [`char`] to be displayed on the main line only, is
166    /// `'│'` by default
167    ///
168    /// The lower and upper ranges are unaffected by this option.
169    pub fn with_char_above(self, above: char) -> Self {
170        Self {
171            sep_char: match self.sep_char {
172                SepChar::Uniform(other) => SepChar::ThreeWay(other, above, other),
173                SepChar::TwoWay(main, below) => SepChar::ThreeWay(main, above, below),
174                SepChar::ThreeWay(main, _, below) => SepChar::ThreeWay(main, above, below),
175            },
176            ..self
177        }
178    }
179
180    pub fn with_char_below(self, below: char) -> Self {
181        Self {
182            sep_char: match self.sep_char {
183                SepChar::Uniform(other) => SepChar::ThreeWay(other, other, below),
184                SepChar::TwoWay(main, above) => SepChar::ThreeWay(main, above, below),
185                SepChar::ThreeWay(main, above, _) => SepChar::ThreeWay(main, above, below),
186            },
187            ..self
188        }
189    }
190}
191
192impl Default for VertRuleCfg {
193    fn default() -> Self {
194        Self::new()
195    }
196}
197
198impl WidgetCfg<Ui> for VertRuleCfg {
199    type Widget = VertRule;
200
201    fn build(self, _: &mut Pass, handle: Option<FileHandle<Ui>>) -> (Self::Widget, PushSpecs) {
202        let widget = VertRule {
203            handle,
204            text: Text::default(),
205            sep_char: self.sep_char,
206        };
207
208        (widget, self.specs)
209    }
210}
211
212fn form_string(char: char, count: usize) -> String {
213    [char, '\n'].repeat(count).iter().collect()
214}