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