photon_ui/components/
container.rs1use crate::{
2 Component,
3 InputResult,
4 RenderError,
5 Rendered,
6 events::Event,
7 layout::{
8 Rect,
9 layout::Layout,
10 },
11};
12
13pub struct Container {
18 layout: Layout,
19 children: Vec<Box<dyn Component>>,
20}
21
22impl Container {
23 pub fn new(layout: Layout) -> Self {
25 Self {
26 layout,
27 children: Vec::new(),
28 }
29 }
30
31 pub fn push(&mut self, child: Box<dyn Component>) {
33 self.children.push(child);
34 }
35
36 pub fn with_child(mut self, child: Box<dyn Component>) -> Self {
38 self.children.push(child);
39 self
40 }
41}
42
43impl Component for Container {
44 fn render(&self, width: u16) -> Result<Rendered, RenderError> {
45 let rect = Rect::new(0, 0, width, self.children.len() as u16 * 3);
46 self.render_rect(rect)
47 }
48
49 fn render_rect(&self, rect: Rect) -> Result<Rendered, RenderError> {
50 let mut screen = Rendered::empty();
51 let areas = self.layout.split(rect);
52
53 for (child, area) in self.children.iter().zip(areas.iter()) {
54 if let Ok(rendered) = child.render_rect(*area) {
55 let rel_area = Rect::new(
60 area.x.saturating_sub(rect.x),
61 area.y.saturating_sub(rect.y),
62 area.width,
63 area.height,
64 );
65 rendered.blit_into_rect(&mut screen, rel_area);
66 }
67 }
68
69 Ok(screen)
70 }
71
72 fn handle_input(&mut self, event: &Event) -> InputResult {
73 for child in &mut self.children {
74 let result = child.handle_input(event);
75 if result != InputResult::Ignored {
76 return result;
77 }
78 }
79 InputResult::Ignored
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::{
87 components::Text,
88 layout::Constraint,
89 };
90
91 #[test]
92 fn container_renders_children() {
93 let mut container = Container::new(Layout::horizontal([
94 Constraint::Length(10),
95 Constraint::Length(10),
96 ]));
97 container.push(Box::new(Text::new("left", 0, 0)));
98 container.push(Box::new(Text::new("right", 0, 0)));
99
100 let rendered = container.render_rect(Rect::new(0, 0, 20, 1)).unwrap();
101 assert_eq!(rendered.lines.len(), 1);
102 assert!(rendered.lines[0].contains("left"));
103 assert!(rendered.lines[0].contains("right"));
104 }
105
106 #[test]
107 fn container_vertical_split() {
108 let mut container = Container::new(Layout::vertical([
109 Constraint::Length(1),
110 Constraint::Length(1),
111 ]));
112 container.push(Box::new(Text::new("top", 0, 0)));
113 container.push(Box::new(Text::new("bottom", 0, 0)));
114
115 let rendered = container.render_rect(Rect::new(0, 0, 10, 2)).unwrap();
116 assert_eq!(rendered.lines.len(), 2);
117 assert!(rendered.lines[0].contains("top"));
118 assert!(rendered.lines[1].contains("bottom"));
119 }
120
121 #[test]
124 fn container_nonzero_rect_no_double_offset() {
125 let mut outer = Container::new(Layout::horizontal([
126 Constraint::Length(10),
127 Constraint::Length(10),
128 ]));
129
130 let mut inner = Container::new(Layout::vertical([
131 Constraint::Length(1),
132 Constraint::Length(1),
133 ]));
134 inner.push(Box::new(Text::new("a", 0, 0)));
135 inner.push(Box::new(Text::new("b", 0, 0)));
136
137 outer.push(Box::new(Text::new("left", 0, 0)));
138 outer.push(Box::new(inner));
139
140 let rendered = outer.render_rect(Rect::new(0, 2, 20, 2)).unwrap();
142 assert_eq!(
145 rendered.lines.len(),
146 2,
147 "expected 2 lines, got {}",
148 rendered.lines.len()
149 );
150 assert!(rendered.lines[0].contains("left"));
151 assert!(rendered.lines[0].contains("a"));
152 assert!(rendered.lines[1].contains("b"));
153 }
154}