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,
25 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 {
61 let text = wrap_text(self.text.remove(0), self.width).0;
62 self.text[0] = text;
63 }
64 }
65 }
66
67 pub fn height(&self) -> u16 {
68 if !self.show {
69 return 0;
70 }
71 let mut height = self.height;
72 height += self.config.border.height();
73
74 height
75 }
76
77 pub fn set(&mut self, text: impl Into<Text<'static>>) {
79 let (text, _) = wrap_text(text.into(), self.config.wrap as u16 * self.width);
80 self.text = vec![text];
81 self.text_split_index = 1;
82 self.show = true;
83 }
84
85 pub fn clear(&mut self) {
86 self.show = false;
87 self.text.clear();
88 self.text_split_index = 0;
89 }
90
91 pub fn single(&self) -> bool {
92 self.text_split_index == 1
93 }
94
95 pub fn header_columns(&mut self, columns: Vec<Text<'static>>) {
96 self.text.truncate(self.text_split_index);
97 self.text.extend(columns);
98 }
99
100 pub fn make_display(
102 &mut self,
103 result_indentation: u16,
104 mut widths: Vec<u16>,
105 col_spacing: u16,
106 ) -> Table<'_> {
107 if self.text.is_empty() || widths.is_empty() {
108 return Table::default();
109 }
110
111 let block = {
112 let b = self.config.border.as_block();
113 if self.config.match_indent {
114 let mut padding = self.config.border.padding;
115
116 padding.left = result_indentation.saturating_sub(self.config.border.left());
117 widths[0] -= result_indentation;
118 b.padding(padding)
119 } else {
120 b
121 }
122 };
123
124 let style = Style::default()
125 .fg(self.config.fg)
126 .add_modifier(self.config.modifier);
127
128 let (cells, height) = if self.text_split_index == 1 {
129 let cells = if self.text_split_index < self.text.len() {
132 vec![]
133 } else {
134 vec![Cell::from(self.text[0].clone())]
135 };
136 let height = self.text[0].height() as u16;
137
138 (cells, height)
139 } else {
140 let mut height = 0;
141 let cells = self.text[..self.text_split_index]
143 .iter()
144 .cloned()
145 .enumerate()
146 .map(|(i, text)| {
147 let mut ret = wrap_text(text, widths[i]).0;
148 height = height.max(ret.height() as u16);
149
150 Cell::from(ret.transform_if(
151 matches!(
152 self.config.row_connection_style,
153 RowConnectionStyle::Disjoint
154 ),
155 |r| r.style(style),
156 ))
157 })
158 .collect();
159
160 (cells, height)
161 };
162
163 let row = Row::new(cells).style(style).height(height);
164 let mut rows = vec![row];
165
166 if self.text_split_index < self.text.len() {
167 self.height = height;
168 let mut height = 0;
169
170 let cells = self.text[self.text_split_index..].iter().map(|x| {
171 height = height.max(x.height() as u16);
172 Cell::from(x.clone())
173 });
174
175 rows.push(Row::new(cells).style(style).height(height));
176
177 self.height += height;
178 }
179
180 Table::new(rows, widths.to_vec())
181 .block(block)
182 .column_spacing(col_spacing)
183 .transform_if(
184 !matches!(
185 self.config.row_connection_style,
186 RowConnectionStyle::Disjoint
187 ),
188 |t| t.style(style),
189 )
190 }
191
192 pub fn make_full_width_row(&self, result_indentation: u16) -> Paragraph<'_> {
194 let style = Style::default()
195 .fg(self.config.fg)
196 .add_modifier(self.config.modifier);
197
198 let left = if self.config.match_indent {
200 result_indentation.saturating_sub(self.config.border.left())
201 } else {
202 self.config.border.left()
203 };
204 let top = self.config.border.top();
205 let right = self.config.border.width().saturating_sub(left);
206 let bottom = self.config.border.height() - top;
207
208 let block = ratatui::widgets::Block::default().padding(ratatui::widgets::Padding {
209 left,
210 top,
211 right,
212 bottom,
213 });
214
215 Paragraph::new(self.text[0].clone())
217 .block(block)
218 .style(style)
219 }
220}