tui_react/
lib.rs

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