1#![allow(unused)]
2use cli_boilerplate_automation::bait::{BoolExt, TransformExt};
3use log::debug;
4use ratatui::{
5 layout::{Constraint, Rect},
6 style::{Style, Stylize},
7 text::Text,
8 widgets::{Cell, Paragraph, Row, Table, Wrap},
9};
10
11use crate::{
12 config::{DisplayConfig, RowConnectionStyle},
13 utils::{
14 serde::StringOrVec,
15 text::{left_pad, prefix_text, wrap_text, wrapped_height},
16 },
17};
18
19#[derive(Debug)]
20pub struct DisplayUI {
21 width: u16,
22 height: u16,
23 text: Vec<Text<'static>>,
24 text_split_index: usize, pub show: bool,
26 pub config: DisplayConfig,
27}
28
29impl DisplayUI {
30 pub fn new(config: DisplayConfig) -> Self {
31 let (text, height) = match &config.content {
32 Some(StringOrVec::String(s)) => {
33 let text = Text::from(s.clone());
34 let height = text.height() as u16;
35 (vec![text], height)
36 }
37 Some(StringOrVec::Vec(s)) => {
38 let text: Vec<_> = s.iter().map(|s| Text::from(s.clone())).collect();
39 let height = text.iter().map(|t| t.height()).max().unwrap_or_default() as u16;
40 (text, height)
41 }
42 _ => (vec![], 0),
43 };
44
45 Self {
46 height,
47 width: 0,
48 show: config.content.is_some() || config.header_lines > 0,
49 text_split_index: text.len(),
50 text,
51 config,
52 }
53 }
54
55 pub fn update_width(&mut self, width: u16) {
56 let border_w = self.config.border.width();
57 let new_w = width.saturating_sub(border_w);
58 if new_w != self.width {
59 self.width = new_w;
60 if self.config.wrap && self.text_split_index == 1 {
62 let text = wrap_text(self.text[0].clone(), self.width).0;
63 self.text[0] = text;
64 }
65 }
66 }
67
68 pub fn height(&self) -> u16 {
69 if !self.show {
70 return 0;
71 }
72 let mut height = self.height;
73 height += self.config.border.height();
74
75 height
76 }
77
78 pub fn set(&mut self, text: impl Into<Text<'static>>, keep_header_columns: bool) {
80 let (text, _) = wrap_text(text.into(), self.config.wrap as u16 * self.width);
81
82 if keep_header_columns {
83 let header = self.text.split_off(self.text_split_index);
85
86 self.text = vec![text];
87 self.text.extend(header);
88 } else {
89 self.text = vec![text];
90 }
91
92 self.text_split_index = 1;
93 self.show = true;
94 }
95
96 pub fn clear(&mut self, keep_header_columns: bool) {
97 self.show = false;
98
99 if keep_header_columns {
100 let header = self.text.split_off(self.text_split_index);
101 self.text = header;
102 } else {
103 self.text.clear();
104 }
105
106 self.text_split_index = 0;
107 }
108
109 pub fn single(&self) -> bool {
111 self.text_split_index == 1
112 }
113
114 pub fn header_columns(&mut self, columns: Vec<Text<'static>>) {
115 self.text.truncate(self.text_split_index);
116 self.text.extend(columns);
117 }
118
119 pub fn make_display(
121 &mut self,
122 result_indentation: u16,
123 mut widths: Vec<u16>,
124 col_spacing: u16,
125 ) -> Table<'_> {
126 if self.text.is_empty() || widths.is_empty() {
127 return Table::default();
128 }
129
130 let block = {
131 let b = self.config.border.as_block();
132 if self.config.match_indent {
133 let mut padding = self.config.border.padding;
134
135 padding.left = result_indentation.saturating_sub(self.config.border.left());
136 widths[0] -= result_indentation;
137 b.padding(padding)
138 } else {
139 b
140 }
141 };
142
143 let style = Style::default()
144 .fg(self.config.fg)
145 .add_modifier(self.config.modifier);
146
147 let (cells, height) = if self.text_split_index == 1 {
148 let cells = if self.text.len() > 1 {
151 vec![]
152 } else {
153 vec![Cell::from(self.text[0].clone())]
154 };
155 let height = self.text[0].height() as u16;
156
157 (cells, height)
158 } else {
159 let mut height = 0;
160 let cells = self.text[..self.text_split_index]
162 .iter()
163 .cloned()
164 .zip(widths.iter().copied())
165 .map(|(text, width)| {
166 let mut ret = wrap_text(text, width).0;
167 height = height.max(ret.height() as u16);
168
169 Cell::from(ret.transform_if(
170 matches!(
171 self.config.row_connection_style,
172 RowConnectionStyle::Disjoint
173 ),
174 |r| r.style(style),
175 ))
176 })
177 .collect();
178
179 (cells, height)
180 };
181
182 let row = Row::new(cells).style(style).height(height);
183 let mut rows = vec![row];
184 self.height = height;
185
186 if self.text_split_index < self.text.len() {
188 let mut height = 0;
189
190 let cells = self.text[self.text_split_index..].iter().map(|x| {
191 height = height.max(x.height() as u16);
192 Cell::from(x.clone())
193 });
194
195 rows.push(Row::new(cells).style(style).height(height));
196
197 self.height += height;
198 }
199
200 let widths = if self.single() && self.text.len() == 1 {
201 vec![Constraint::Percentage(100)]
202 } else {
203 widths.into_iter().map(Constraint::Length).collect()
204 };
205
206 Table::new(rows, widths)
207 .block(block)
208 .column_spacing(col_spacing)
209 .transform_if(
210 !matches!(
211 self.config.row_connection_style,
212 RowConnectionStyle::Disjoint
213 ),
214 |t| t.style(style),
215 )
216 }
217
218 pub fn make_full_width_row(&self, result_indentation: u16) -> Paragraph<'_> {
220 let style = Style::default()
221 .fg(self.config.fg)
222 .add_modifier(self.config.modifier);
223
224 let left = if self.config.match_indent {
226 result_indentation.saturating_sub(self.config.border.left())
227 } else {
228 self.config.border.left()
229 };
230 let top = self.config.border.top();
231 let right = self.config.border.width().saturating_sub(left);
232 let bottom = self.config.border.height() - top;
233
234 let block = ratatui::widgets::Block::default().padding(ratatui::widgets::Padding {
235 left,
236 top,
237 right,
238 bottom,
239 });
240
241 Paragraph::new(self.text[0].clone())
242 .block(block)
243 .style(style)
244 }
245}