tui_react/
lib.rs

1#![forbid(unsafe_code)]
2
3mod list;
4mod terminal;
5
6pub use list::*;
7pub use terminal::*;
8
9use tui::{self, buffer::Buffer, layout::Rect, style::Color, style::Style};
10use unicode_segmentation::UnicodeSegmentation;
11use unicode_width::UnicodeWidthStr;
12
13pub fn fill_background_to_right(mut s: String, entire_width: u16) -> String {
14    match (s.len(), entire_width as usize) {
15        (x, y) if x >= y => s,
16        (x, y) => {
17            s.extend(std::iter::repeat_n(' ', y - x));
18            s
19        }
20    }
21}
22
23/// Helper method to quickly set the background of all cells inside the specified area.
24pub fn fill_background(area: Rect, buf: &mut Buffer, color: Color) {
25    for y in area.top()..area.bottom() {
26        for x in area.left()..area.right() {
27            buf.get_mut(x, y).set_bg(color);
28        }
29    }
30}
31
32pub fn draw_text_with_ellipsis_nowrap(
33    bound: Rect,
34    buf: &mut Buffer,
35    text: impl AsRef<str>,
36    style: impl Into<Option<Style>>,
37) -> u16 {
38    let s = style.into();
39    let t = text.as_ref();
40    let mut graphemes = t.graphemes(true);
41    let mut total_width = 0;
42    {
43        let mut ellipsis_candidate_x = None;
44        let mut x_offset = 0;
45        for (g, mut x) in graphemes.by_ref().zip(bound.left()..bound.right()) {
46            let width = g.width();
47            total_width += width;
48
49            x += x_offset;
50            let cell = buf.get_mut(x, bound.y);
51            if x + 1 == bound.right() {
52                ellipsis_candidate_x = Some(x);
53            }
54            cell.set_symbol(g);
55            if let Some(s) = s {
56                cell.set_style(s);
57            }
58
59            x_offset += width.saturating_sub(1) as u16;
60            if x + x_offset >= bound.right() {
61                break;
62            }
63            let x = x as usize;
64            for x in x + 1..x + width {
65                let i = buf.index_of(x as u16, bound.y);
66                buf.content[i].reset();
67            }
68        }
69        if let (Some(_), Some(x)) = (graphemes.next(), ellipsis_candidate_x) {
70            buf.get_mut(x, bound.y).set_symbol("…");
71        }
72    }
73    total_width as u16
74}
75
76pub fn draw_text_nowrap_fn(
77    bound: Rect,
78    buf: &mut Buffer,
79    t: impl AsRef<str>,
80    mut s: impl FnMut(&str, u16, u16) -> Style,
81) {
82    if bound.width == 0 {
83        return;
84    }
85    for (g, x) in t.as_ref().graphemes(true).zip(bound.left()..bound.right()) {
86        let cell = buf.get_mut(x, bound.y);
87        cell.set_symbol(g);
88        cell.set_style(s(cell.symbol(), x, bound.y));
89    }
90}
91
92pub mod util {
93    use unicode_segmentation::UnicodeSegmentation;
94    use unicode_width::UnicodeWidthStr;
95
96    pub fn sanitize_offset(offset: u16, num_items: usize, num_displayable_lines: u16) -> u16 {
97        offset.min((num_items.saturating_sub(num_displayable_lines as usize)) as u16)
98    }
99
100    #[derive(Default)]
101    pub struct GraphemeCountWriter(pub usize);
102
103    impl std::io::Write for GraphemeCountWriter {
104        fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
105            self.0 += String::from_utf8_lossy(buf).graphemes(true).count();
106            Ok(buf.len())
107        }
108
109        fn flush(&mut self) -> Result<(), std::io::Error> {
110            Ok(())
111        }
112    }
113
114    pub fn block_width(s: &str) -> u16 {
115        s.width() as u16
116    }
117
118    pub mod rect {
119        use tui::layout::Rect;
120
121        /// A safe version of Rect::intersection that doesn't suffer from underflows
122        pub fn intersect(lhs: Rect, rhs: Rect) -> Rect {
123            let x1 = lhs.x.max(rhs.x);
124            let y1 = lhs.y.max(rhs.y);
125            let x2 = lhs.right().min(rhs.right());
126            let y2 = lhs.bottom().min(rhs.bottom());
127            Rect {
128                x: x1,
129                y: y1,
130                width: x2.saturating_sub(x1),
131                height: y2.saturating_sub(y1),
132            }
133        }
134
135        pub fn offset_x(r: Rect, offset: u16) -> Rect {
136            Rect {
137                x: r.x + offset,
138                width: r.width.saturating_sub(offset),
139                ..r
140            }
141        }
142
143        pub fn snap_to_right(bound: Rect, new_width: u16) -> Rect {
144            offset_x(bound, bound.width.saturating_sub(new_width))
145        }
146
147        pub fn line_bound(bound: Rect, line: usize) -> Rect {
148            Rect {
149                y: bound.y + line as u16,
150                height: 1,
151                ..bound
152            }
153        }
154    }
155}