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
23pub 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 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}