1use crate::console::RenderContext;
6use crate::renderable::{BoxedRenderable, Renderable, Segment};
7use crate::rule::Rule;
8use crate::text::Span;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Fit {
13 Fill,
15 Shrink,
17}
18
19pub struct RenderGroup {
21 renderables: Vec<BoxedRenderable>,
22 spacing: usize,
23 fit: Fit,
24 divider: Option<String>,
25}
26
27impl RenderGroup {
28 pub fn new() -> Self {
30 RenderGroup {
31 renderables: Vec::new(),
32 spacing: 0,
33 fit: Fit::Fill,
34 divider: None,
35 }
36 }
37
38 pub fn add(&mut self, renderable: impl Renderable + Send + Sync + 'static) -> &mut Self {
40 self.renderables.push(Box::new(renderable));
41 self
42 }
43
44 pub fn spacing(mut self, spacing: usize) -> Self {
46 self.spacing = spacing;
47 self
48 }
49
50 pub fn fit(mut self, fit: Fit) -> Self {
52 self.fit = fit;
53 self
54 }
55
56 pub fn divider(mut self, divider: impl Into<String>) -> Self {
58 self.divider = Some(divider.into());
59 self
60 }
61
62 pub fn from_renderables(renderables: Vec<BoxedRenderable>) -> Self {
64 RenderGroup {
65 renderables,
66 spacing: 0,
67 fit: Fit::Fill,
68 divider: None,
69 }
70 }
71}
72
73impl Default for RenderGroup {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl Renderable for RenderGroup {
80 fn render(&self, context: &RenderContext) -> Vec<Segment> {
81 let mut result = Vec::new();
82
83 for (i, renderable) in self.renderables.iter().enumerate() {
84 let item_context = match self.fit {
86 Fit::Fill => context.clone(),
87 Fit::Shrink => RenderContext {
88 width: renderable.min_width().min(context.width),
89 height: None,
90 },
91 };
92
93 let segments = renderable.render(&item_context);
94 result.extend(segments);
95
96 if i < self.renderables.len() - 1 {
98 if let Some(ref divider_text) = self.divider {
100 let rule = Rule::new(divider_text);
101 let divider_segments = rule.render(context);
102 result.extend(divider_segments);
103 }
104
105 for _ in 0..self.spacing {
107 result.push(Segment::line(vec![Span::raw(" ".repeat(context.width))]));
108 }
109 }
110 }
111
112 result
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::text::Text;
120
121 #[test]
122 fn test_render_group_creation() {
123 let group = RenderGroup::new();
124 assert_eq!(group.renderables.len(), 0);
125 }
126
127 #[test]
128 fn test_add_renderables() {
129 let mut group = RenderGroup::new();
130 group.add(Text::plain("First"));
131 group.add(Text::plain("Second"));
132
133 assert_eq!(group.renderables.len(), 2);
134 }
135
136 #[test]
137 fn test_spacing() {
138 let group = RenderGroup::new().spacing(2);
139 assert_eq!(group.spacing, 2);
140 }
141
142 #[test]
143 fn test_fit() {
144 let group = RenderGroup::new().fit(Fit::Shrink);
145 assert_eq!(group.fit, Fit::Shrink);
146 }
147
148 #[test]
149 fn test_divider() {
150 let group = RenderGroup::new().divider("---");
151 assert!(group.divider.is_some());
152 }
153}