1use crate::{
2 Component,
3 layout::{
4 Layout,
5 Rect,
6 },
7 renderer::Rendered,
8};
9
10#[derive(Debug, Clone, PartialEq, Default)]
12pub enum Shadow {
13 #[default]
15 None,
16 Dim {
20 style: String,
23 },
24 Drop {
26 style: String,
28 offset_x: i16,
30 offset_y: i16,
32 },
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Default)]
37pub enum LayerKind {
38 #[default]
40 Base,
41 Overlay,
43 Modal,
45}
46
47#[derive(Default)]
53pub struct Layer {
54 pub components: Vec<Box<dyn Component>>,
56 pub layout: Option<Layout>,
58 pub shadow: Shadow,
60 pub visible: bool,
62 pub kind: LayerKind,
64}
65
66impl Layer {
67 pub fn new() -> Self {
69 Self {
70 components: Vec::new(),
71 layout: None,
72 shadow: Shadow::None,
73 visible: true,
74 kind: LayerKind::Base,
75 }
76 }
77
78 pub fn with_component(component: Box<dyn Component>) -> Self {
80 Self {
81 components: vec![component],
82 layout: None,
83 shadow: Shadow::None,
84 visible: true,
85 kind: LayerKind::Base,
86 }
87 }
88
89 pub fn set_layout(&mut self, layout: Layout) {
91 self.layout = Some(layout);
92 }
93
94 pub fn mount(&mut self, component: Box<dyn Component>) {
96 self.components.push(component);
97 }
98
99 pub fn with_kind(mut self, kind: LayerKind) -> Self {
101 self.kind = kind;
102 self
103 }
104
105 pub fn render(&self, width: u16, height: u16, focused_index: Option<usize>) -> Rendered {
111 if !self.visible {
112 let mut rendered = Rendered::empty();
113 while rendered.lines.len() < height as usize {
114 rendered.lines.push(String::new());
115 }
116 return rendered;
117 }
118
119 let mut rendered = Rendered::empty();
120 let term_rect = Rect::new(0, 0, width, height);
121
122 if let Some(ref layout) = self.layout {
123 let areas = layout.split(term_rect);
124 for (i, (child, area)) in self.components.iter().zip(areas.iter()).enumerate() {
125 let child_rendered = match child.render_rect(*area) {
126 | Ok(r) => r,
127 | Err(_) => continue,
128 };
129 child_rendered.blit_into_rect(&mut rendered, *area);
130 if Some(i) == focused_index &&
131 let Some((r_local, c_local)) = child_rendered.cursor
132 {
133 rendered.cursor = Some((area.y as usize + r_local, area.x as usize + c_local));
134 }
135 }
136 } else {
137 let mut row = 0usize;
138 for (i, child) in self.components.iter().enumerate() {
139 if row >= height as usize {
140 break;
141 }
142 let child_rendered = match child.render(width) {
143 | Ok(r) => r,
144 | Err(_) => continue,
145 };
146 let start_row = row;
147 for line in &child_rendered.lines {
148 if row < height as usize {
149 rendered.lines.push(line.clone());
150 row += 1;
151 }
152 }
153 if Some(i) == focused_index &&
154 let Some((r_local, c_local)) = child_rendered.cursor
155 {
156 rendered.cursor = Some((start_row + r_local, c_local));
157 }
158 for image in &child_rendered.images {
159 rendered.images.push(crate::renderer::ImageCommand {
160 id: image.id,
161 data: image.data.clone(),
162 row: start_row as u16 + image.row,
163 col: image.col,
164 });
165 }
166 }
167 }
168
169 while rendered.lines.len() < height as usize {
170 rendered.lines.push(String::new());
171 }
172
173 rendered
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::{
181 RenderError,
182 components::Text,
183 layout::{
184 Constraint,
185 Layout,
186 },
187 renderer::ImageCommand,
188 };
189
190 struct CursorComponent;
191
192 impl Component for CursorComponent {
193 fn render(&self, _width: u16) -> Result<Rendered, RenderError> {
194 Ok(Rendered {
195 lines: vec!["cursor".into()],
196 cursor: Some((0, 3)),
197 images: Vec::new(),
198 })
199 }
200 }
201
202 #[test]
203 fn layer_new_is_empty() {
204 let layer = Layer::new();
205 assert!(layer.components.is_empty());
206 assert!(layer.layout.is_none());
207 assert_eq!(layer.shadow, Shadow::None);
208 assert!(layer.visible);
209 }
210
211 #[test]
212 fn layer_render_empty_pads_to_terminal_size() {
213 let layer = Layer::new();
214 let rendered = layer.render(80, 24, None);
215 assert_eq!(rendered.lines.len(), 24);
216 assert!(rendered.cursor.is_none());
217 assert!(rendered.images.is_empty());
218 }
219
220 #[test]
221 fn layer_render_single_component_vertical_stack() {
222 let mut layer = Layer::new();
223 layer.mount(Box::new(Text::new("hello", 0, 0)));
224 let rendered = layer.render(80, 24, None);
225 assert!(rendered.lines[0].starts_with("hello"));
226 assert_eq!(crate::utils::visible_width(&rendered.lines[0]), 80);
227 assert_eq!(rendered.lines.len(), 24);
228 }
229
230 #[test]
231 fn layer_render_multiple_components_vertical_stack() {
232 let mut layer = Layer::new();
233 layer.mount(Box::new(Text::new("first", 0, 0)));
234 layer.mount(Box::new(Text::new("second", 0, 0)));
235 let rendered = layer.render(80, 24, None);
236 assert!(rendered.lines[0].starts_with("first"));
237 assert!(rendered.lines[1].starts_with("second"));
238 assert_eq!(rendered.lines.len(), 24);
239 }
240
241 #[test]
242 fn layer_render_clips_component_to_size() {
243 let mut layer = Layer::new();
244 layer.mount(Box::new(Text::new("hello", 0, 0)));
245 let rendered = layer.render(4, 2, None);
246 assert!(rendered.lines[0].starts_with("hel"));
247 assert_eq!(rendered.lines.len(), 2);
248 }
249
250 #[test]
251 fn layer_render_focused_component_cursor() {
252 let mut layer = Layer::new();
253 layer.mount(Box::new(Text::new("a", 0, 0)));
254 layer.mount(Box::new(CursorComponent));
255 let rendered = layer.render(80, 24, Some(1));
256 assert_eq!(rendered.cursor, Some((1, 3)));
257 }
258
259 #[test]
260 fn layer_render_preserves_images() {
261 struct ImageComponent;
262
263 impl Component for ImageComponent {
264 fn render(&self, _width: u16) -> Result<Rendered, RenderError> {
265 Ok(Rendered {
266 lines: vec!["img".into()],
267 cursor: None,
268 images: vec![ImageCommand {
269 id: 7,
270 data: "data".into(),
271 row: 0,
272 col: 0,
273 }],
274 })
275 }
276 }
277
278 let mut layer = Layer::new();
279 layer.mount(Box::new(ImageComponent));
280 let rendered = layer.render(80, 24, None);
281 assert_eq!(rendered.images.len(), 1);
282 assert_eq!(rendered.images[0].id, 7);
283 }
284
285 #[test]
286 fn layer_shadow_variants_clone_and_equal() {
287 let dim = Shadow::Dim {
288 style: "\x1b[2m".into(),
289 };
290 let cloned = dim.clone();
291 assert_eq!(dim, cloned);
292
293 let drop_shadow = Shadow::Drop {
294 style: "\x1b[2m".into(),
295 offset_x: 1,
296 offset_y: 1,
297 };
298 assert_ne!(dim, drop_shadow);
299 }
300
301 #[test]
302 fn layer_visible_false_skips_render() {
303 let mut layer = Layer::new();
304 layer.mount(Box::new(Text::new("hello", 0, 0)));
305 layer.visible = false;
306 let rendered = layer.render(80, 24, None);
307 assert_eq!(rendered.lines[0], "");
308 assert_eq!(rendered.lines.len(), 24);
309 }
310
311 #[test]
312 fn shadow_default_is_none() {
313 assert_eq!(Shadow::default(), Shadow::None);
314 }
315
316 #[test]
317 fn layer_kind_default_is_base() {
318 assert_eq!(LayerKind::default(), LayerKind::Base);
319 }
320
321 #[test]
322 fn layer_with_component() {
323 let layer = Layer::with_component(Box::new(Text::new("hi", 0, 0)));
324 assert_eq!(layer.components.len(), 1);
325 assert!(layer.layout.is_none());
326 assert_eq!(layer.shadow, Shadow::None);
327 assert_eq!(layer.kind, LayerKind::Base);
328 assert!(layer.visible);
329 }
330
331 #[test]
332 fn layer_with_kind() {
333 let layer = Layer::new().with_kind(LayerKind::Overlay);
334 assert_eq!(layer.kind, LayerKind::Overlay);
335 }
336
337 #[test]
338 fn layer_render_with_layout_focuses_cursor() {
339 let mut layer = Layer::new();
340 layer.set_layout(Layout::horizontal([
341 Constraint::Percentage(50),
342 Constraint::Percentage(50),
343 ]));
344 layer.mount(Box::new(Text::new("a", 0, 0)));
345 layer.mount(Box::new(CursorComponent));
346 let rendered = layer.render(80, 24, Some(1));
347 assert!(rendered.cursor.is_some());
348 let (_, col) = rendered.cursor.unwrap();
349 assert!(col >= 40, "expected cursor in second half, got col {}", col);
350 }
351
352 #[test]
353 fn layer_render_clips_to_height() {
354 let mut layer = Layer::new();
355 layer.mount(Box::new(Text::new("a", 0, 0)));
356 layer.mount(Box::new(Text::new("b", 0, 0)));
357 layer.mount(Box::new(Text::new("c", 0, 0)));
358 layer.mount(Box::new(Text::new("d", 0, 0)));
359 let rendered = layer.render(80, 2, None);
360 assert_eq!(rendered.lines.len(), 2);
361 assert!(rendered.lines[0].starts_with("a"));
362 assert!(rendered.lines[1].starts_with("b"));
363 }
364
365 #[test]
366 fn layer_render_ignores_failing_component() {
367 struct Fail;
368
369 impl Component for Fail {
370 fn render(&self, _width: u16) -> Result<Rendered, RenderError> {
371 Err(RenderError::WidthOverflow {
372 line: String::new(),
373 width: 0,
374 actual: 0,
375 })
376 }
377 }
378
379 let mut layer = Layer::new();
380 layer.mount(Box::new(Text::new("ok", 0, 0)));
381 layer.mount(Box::new(Fail));
382 let rendered = layer.render(80, 24, None);
383 assert!(rendered.lines[0].starts_with("ok"));
384 }
385}