duat_term/
rules.rs

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