duat_term/
rules.rs

1use duat_core::{
2    context::Handle,
3    data::Pass,
4    text::{Text, txt},
5    ui::{PushSpecs, PushTarget, Side, Widget},
6};
7
8/// A vertical line on screen, useful, for example, for the separation
9/// of a [`Buffer`] and [`LineNumbers`].
10///
11/// By default, this [`VertRule`] will show the `'│'` character on the
12/// whole line, using the `"default.VertRule"` form. However, you can change that with the [`VertRule::sep_char`] field.
13/// 
14/// This field, of type [`SepChar`], can have up to 3 distinct characters, in order to differentiate between the main line's separator and even the separators above and below the main line.
15///
16/// If the main character is not the same as the other two characters,
17/// then the line will be printed with the `"rule.upper"` and
18/// `"rule.lower"` forms for the characters above and below.
19///
20/// [`Buffer`]: duat_core::buffer::Buffer
21/// [`LineNumbers`]: https://docs.rs/duat-utils/latest/duat_utils/widgets/struct.LineNumbers.html
22pub struct VertRule {
23    handle: Option<Handle>,
24    text: Text,
25    pub sep_char: SepChar,
26}
27
28impl VertRule {
29    /// Returns a [`VertRuleBuilder`], letting you push your own
30    /// [`VertRule`]s around
31    pub fn builder() -> VertRuleBuilder {
32        VertRuleBuilder::default()
33    }
34}
35
36impl Widget for VertRule {
37    fn update(pa: &mut Pass, handle: &Handle<Self>) {
38        let vr = handle.read(pa);
39        let text = if let Some(handle) = vr.handle.as_ref()
40            && let SepChar::ThreeWay(..) | SepChar::TwoWay(..) = vr.sep_char
41        {
42            let (upper, middle, lower) = {
43                let file = handle.read(pa);
44
45                let lines = file.printed_lines();
46                if let Some(main) = file.selections().get_main() {
47                    let main = main.line();
48                    let upper = lines.iter().filter(|&(line, _)| *line < main).count();
49                    let middle = lines.iter().filter(|&(line, _)| *line == main).count();
50                    let lower = lines.iter().filter(|&(line, _)| *line > main).count();
51                    (upper, middle, lower)
52                } else {
53                    (0, lines.len(), 0)
54                }
55            };
56
57            let chars = vr.sep_char.chars();
58            txt!(
59                "[rule.upper]{}[]{}[rule.lower]{}",
60                form_string(chars[0], upper),
61                form_string(chars[1], middle),
62                form_string(chars[2], lower)
63            )
64        } else {
65            let full_line =
66                format!("{}\n", vr.sep_char.chars()[1]).repeat(handle.area().height(pa) as usize);
67
68            txt!("{full_line}")
69        };
70
71        handle.write(pa).text = text;
72    }
73
74    fn needs_update(&self, pa: &Pass) -> bool {
75        matches!(self.sep_char, SepChar::ThreeWay(..) | SepChar::TwoWay(..))
76            && self.handle.as_ref().is_some_and(|fh| fh.has_changed(pa))
77    }
78
79    fn text(&self) -> &Text {
80        &self.text
81    }
82
83    fn text_mut(&mut self) -> &mut Text {
84        &mut self.text
85    }
86}
87
88/// The [`char`]s that should be printed above, equal to, and below
89/// the main line.
90#[derive(Clone)]
91pub enum SepChar {
92    Uniform(char),
93    /// Order: main line, other lines.
94    TwoWay(char, char),
95    /// Order: main line, above main line, below main line.
96    ThreeWay(char, char, char),
97}
98
99impl SepChar {
100    /// The [`char`]s above, equal to, and below the main line,
101    /// respectively.
102    fn chars(&self) -> [char; 3] {
103        match self {
104            SepChar::Uniform(uniform) => [*uniform, *uniform, *uniform],
105            SepChar::TwoWay(main, other) => [*other, *main, *other],
106            SepChar::ThreeWay(main, upper, lower) => [*upper, *main, *lower],
107        }
108    }
109}
110
111/// The configurations for the [`VertRule`] widget.
112#[derive(Default, Clone)]
113pub struct VertRuleBuilder {
114    pub sep_char: SepChar = SepChar::Uniform('│'),
115    pub on_the_right: bool = false
116}
117
118impl VertRuleBuilder {
119    /// Pushes a [`VertRule`] to a [`PushTarget`]
120    pub fn push_on(self, pa: &mut Pass, push_target: &impl PushTarget) -> Handle<VertRule> {
121        let vert_rule = VertRule {
122            handle: push_target.try_downcast(),
123            text: Text::default(),
124            sep_char: self.sep_char,
125        };
126
127        let specs = PushSpecs {
128            side: if self.on_the_right {
129                Side::Right
130            } else {
131                Side::Left
132            },
133            width: Some(1.0),
134            ..
135        };
136
137        push_target.push_outer(pa, vert_rule, specs)
138    }
139}
140
141fn form_string(char: char, count: usize) -> String {
142    [char, '\n'].repeat(count).iter().collect()
143}