1use std::any::Any;
6use std::sync::{Arc, Mutex};
7
8use super::component::Component;
9use crate::utils::{apply_background_to_line, visible_width};
10
11#[derive(Debug, Clone)]
13struct RenderCache {
14 child_lines: Vec<String>,
15 width: usize,
16 bg_sample: Option<String>,
17 lines: Vec<String>,
18}
19
20pub struct Box {
22 children: Mutex<Vec<Arc<dyn Component>>>,
23 padding_x: usize,
24 padding_y: usize,
25 bg_fn: Mutex<Option<Arc<dyn Fn(&str) -> String + Send + Sync>>>,
26 cache: Mutex<Option<RenderCache>>,
27}
28
29impl Box {
30 pub fn new(padding_x: usize, padding_y: usize) -> Self {
32 Self {
33 children: Mutex::new(Vec::new()),
34 padding_x,
35 padding_y,
36 bg_fn: Mutex::new(None),
37 cache: Mutex::new(None),
38 }
39 }
40
41 pub fn simple() -> Self {
43 Self::new(1, 1)
44 }
45
46 pub fn no_padding() -> Self {
48 Self::new(0, 0)
49 }
50
51 pub fn add_child(&self, component: Arc<dyn Component>) {
53 if let Ok(mut children) = self.children.lock() {
54 children.push(component);
55 }
56 self.invalidate_cache();
57 }
58
59 pub fn remove_child(&self, component: &Arc<dyn Component>) {
61 if let Ok(mut children) = self.children.lock() {
62 children.retain(|c| !Arc::ptr_eq(c, component));
63 }
64 self.invalidate_cache();
65 }
66
67 pub fn clear(&self) {
69 if let Ok(mut children) = self.children.lock() {
70 children.clear();
71 }
72 self.invalidate_cache();
73 }
74
75 pub fn set_bg_fn(&self, f: Option<Arc<dyn Fn(&str) -> String + Send + Sync>>) {
77 if let Ok(mut bg_fn) = self.bg_fn.lock() {
78 *bg_fn = f;
79 }
80 }
82
83 fn invalidate_cache(&self) {
85 if let Ok(mut cache) = self.cache.lock() {
86 *cache = None;
87 }
88 }
89
90 fn is_cache_valid(
92 &self,
93 width: usize,
94 child_lines: &[String],
95 bg_sample: Option<&str>,
96 ) -> bool {
97 if let Ok(cache) = self.cache.lock() {
98 if let Some(ref cache) = *cache {
99 return cache.width == width
100 && cache.bg_sample.as_deref() == bg_sample
101 && cache.child_lines.len() == child_lines.len()
102 && cache
103 .child_lines
104 .iter()
105 .zip(child_lines.iter())
106 .all(|(a, b)| a == b);
107 }
108 }
109 false
110 }
111
112 fn apply_bg(&self, line: &str, width: usize) -> String {
114 if let Ok(bg_fn) = self.bg_fn.lock() {
115 if let Some(ref f) = *bg_fn {
116 return apply_background_to_line(line, width, |s| f(s));
117 }
118 }
119 let vis_len = visible_width(line);
121 let pad = " ".repeat(width.saturating_sub(vis_len));
122 format!("{}{}", line, pad)
123 }
124}
125
126impl Component for Box {
127 fn render(&self, width: usize) -> Vec<String> {
128 let children = if let Ok(children) = self.children.lock() {
129 children.clone()
130 } else {
131 return Vec::new();
132 };
133
134 if children.is_empty() {
135 return Vec::new();
136 }
137
138 let content_width = width.saturating_sub(self.padding_x * 2);
139 let left_pad = " ".repeat(self.padding_x);
140
141 let mut child_lines: Vec<String> = Vec::new();
143 for child in &children {
144 let lines = child.render(content_width);
145 for line in lines {
146 child_lines.push(format!("{}{}", left_pad, line));
147 }
148 }
149
150 if child_lines.is_empty() {
151 return Vec::new();
152 }
153
154 let bg_sample = if let Ok(bg_fn) = self.bg_fn.lock() {
156 bg_fn.as_ref().map(|f| f("test"))
157 } else {
158 None
159 };
160
161 if self.is_cache_valid(width, &child_lines, bg_sample.as_deref()) {
163 if let Ok(cache) = self.cache.lock() {
164 if let Some(ref cache) = *cache {
165 return cache.lines.clone();
166 }
167 }
168 }
169
170 let mut result: Vec<String> = Vec::new();
172
173 for _ in 0..self.padding_y {
175 result.push(self.apply_bg("", width));
176 }
177
178 for line in &child_lines {
180 result.push(self.apply_bg(line, width));
181 }
182
183 for _ in 0..self.padding_y {
185 result.push(self.apply_bg("", width));
186 }
187
188 if let Ok(mut cache) = self.cache.lock() {
190 *cache = Some(RenderCache {
191 child_lines,
192 width,
193 bg_sample,
194 lines: result.clone(),
195 });
196 }
197
198 result
199 }
200
201 fn invalidate(&self) {
202 self.invalidate_cache();
203 if let Ok(children) = self.children.lock() {
204 for child in &*children {
205 child.invalidate();
206 }
207 }
208 }
209
210 fn as_any(&self) -> &dyn Any {
211 self
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::text::Text;
219
220 #[test]
221 fn test_box_padding() {
222 let box_component = Box::new(2, 1);
223 let text = Arc::new(Text::new("Hello", 1, 0));
224 box_component.add_child(text);
225
226 let lines = box_component.render(10);
227 assert!(!lines.is_empty());
228
229 assert!(lines.len() >= 3);
231 }
232
233 #[test]
234 fn test_box_empty() {
235 let box_component = Box::new(1, 1);
236 let lines = box_component.render(10);
237 assert!(lines.is_empty());
238 }
239
240 #[test]
241 fn test_box_clear() {
242 let box_component = Box::new(1, 1);
243 let text = Arc::new(Text::new("Hello", 1, 0));
244 box_component.add_child(text);
245 box_component.clear();
246
247 let lines = box_component.render(10);
248 assert!(lines.is_empty());
249 }
250}