1use crate::vnode::Element;
7use ratatui::{
8 buffer::Buffer,
9 layout::{Constraint, Layout, Rect},
10 widgets::{Block, List, Paragraph, Widget},
11};
12
13#[derive(Clone)]
15pub enum AnyWidget {
16 Layout(LayoutWrapper),
18 Block(BlockWrapper),
20 Paragraph(Paragraph<'static>),
22 List(List<'static>),
24 VNode(Element),
26}
27
28impl Widget for AnyWidget {
29 fn render(self, area: Rect, buffer: &mut Buffer) {
30 match self {
31 AnyWidget::Layout(layout) => layout.render(area, buffer),
32 AnyWidget::Block(block) => block.render(area, buffer),
33 AnyWidget::Paragraph(paragraph) => paragraph.render(area, buffer),
34 AnyWidget::List(list) => list.render(area, buffer),
35 AnyWidget::VNode(vnode) => vnode.render(area, buffer),
36 }
37 }
38}
39
40impl From<LayoutWrapper> for AnyWidget {
41 fn from(layout: LayoutWrapper) -> Self {
42 AnyWidget::Layout(layout)
43 }
44}
45
46impl From<BlockWrapper> for AnyWidget {
47 fn from(block: BlockWrapper) -> Self {
48 AnyWidget::Block(block)
49 }
50}
51
52impl From<Paragraph<'static>> for AnyWidget {
53 fn from(paragraph: Paragraph<'static>) -> Self {
54 AnyWidget::Paragraph(paragraph)
55 }
56}
57
58impl From<List<'static>> for AnyWidget {
59 fn from(list: List<'static>) -> Self {
60 AnyWidget::List(list)
61 }
62}
63
64impl From<Element> for AnyWidget {
65 fn from(vnode: Element) -> Self {
66 AnyWidget::VNode(vnode)
67 }
68}
69
70impl From<Block<'static>> for AnyWidget {
71 fn from(block: Block<'static>) -> Self {
72 AnyWidget::Block(BlockWrapper::new(block, vec![]))
73 }
74}
75
76impl From<String> for AnyWidget {
77 fn from(text: String) -> Self {
78 AnyWidget::VNode(Element::text(text))
79 }
80}
81
82impl From<&str> for AnyWidget {
83 fn from(text: &str) -> Self {
84 AnyWidget::VNode(Element::text(text.to_string()))
85 }
86}
87
88impl From<&String> for AnyWidget {
89 fn from(text: &String) -> Self {
90 AnyWidget::VNode(Element::text(text.clone()))
91 }
92}
93
94impl From<ratatui::text::Span<'static>> for AnyWidget {
95 fn from(span: ratatui::text::Span<'static>) -> Self {
96 use ratatui::text::Line;
98 let line = Line::from(vec![span]);
99 let paragraph = Paragraph::new(vec![line]);
100 AnyWidget::Paragraph(paragraph)
101 }
102}
103
104impl From<ratatui::text::Line<'static>> for AnyWidget {
105 fn from(line: ratatui::text::Line<'static>) -> Self {
106 let paragraph = Paragraph::new(vec![line]);
107 AnyWidget::Paragraph(paragraph)
108 }
109}
110
111#[derive(Clone)]
113pub struct LayoutWrapper {
114 layout: Layout,
115 children: Vec<AnyWidget>,
116 constraints: Option<Vec<Constraint>>,
117}
118
119impl LayoutWrapper {
120 pub fn new(layout: Layout, children: Vec<AnyWidget>) -> Self {
122 Self {
123 layout,
124 children,
125 constraints: None,
126 }
127 }
128
129 pub fn with_constraints(
131 layout: Layout,
132 children: Vec<AnyWidget>,
133 constraints: Vec<Constraint>,
134 ) -> Self {
135 Self {
136 layout,
137 children,
138 constraints: Some(constraints),
139 }
140 }
141
142 pub fn from_elements(layout: Layout, children: Vec<Element>) -> Self {
144 Self {
145 layout,
146 children: children.into_iter().map(AnyWidget::from).collect(),
147 constraints: None,
148 }
149 }
150
151 pub fn from_elements_with_constraints(
153 layout: Layout,
154 children: Vec<Element>,
155 constraints: Vec<Constraint>,
156 ) -> Self {
157 Self {
158 layout,
159 children: children.into_iter().map(AnyWidget::from).collect(),
160 constraints: Some(constraints),
161 }
162 }
163}
164
165impl Widget for LayoutWrapper {
166 fn render(self, area: Rect, buffer: &mut Buffer) {
167 let constraints = if let Some(custom_constraints) = self.constraints {
169 custom_constraints
170 } else {
171 match self.children.len() {
173 0 => vec![],
174 1 => vec![Constraint::Percentage(100)],
175 2 => vec![Constraint::Percentage(50), Constraint::Percentage(50)],
176 3 => vec![
177 Constraint::Percentage(33),
178 Constraint::Percentage(33),
179 Constraint::Percentage(34),
180 ],
181 4 => vec![Constraint::Percentage(25); 4],
182 5 => vec![Constraint::Percentage(20); 5],
183 _ => {
184 let percentage = 100 / self.children.len() as u16;
186 let remainder = 100 % self.children.len() as u16;
187 let mut constraints =
188 vec![Constraint::Percentage(percentage); self.children.len()];
189 if remainder > 0 && !constraints.is_empty() {
190 if let Some(last) = constraints.last_mut() {
192 *last = Constraint::Percentage(percentage + remainder);
193 }
194 }
195 constraints
196 }
197 }
198 };
199
200 let layout = self.layout.constraints(constraints);
202 let chunks = layout.split(area);
203
204 for (i, child) in self.children.into_iter().enumerate() {
206 if i < chunks.len() {
207 child.render(chunks[i], buffer);
208 }
209 }
210 }
211}
212
213#[derive(Clone)]
215pub struct BlockWrapper {
216 block: Block<'static>,
217 children: Vec<AnyWidget>,
218}
219
220impl BlockWrapper {
221 pub fn new(block: Block<'static>, children: Vec<AnyWidget>) -> Self {
223 Self { block, children }
224 }
225}
226
227impl Widget for BlockWrapper {
228 fn render(self, area: Rect, buffer: &mut Buffer) {
229 let inner_area = self.block.inner(area);
231
232 self.block.render(area, buffer);
234
235 if !self.children.is_empty() {
237 if self.children.len() == 1 {
238 self.children
240 .into_iter()
241 .next()
242 .unwrap()
243 .render(inner_area, buffer);
244 } else {
245 let constraints: Vec<Constraint> = (0..self.children.len())
247 .map(|_| Constraint::Min(0))
248 .collect();
249
250 let layout = Layout::default()
251 .direction(ratatui::layout::Direction::Vertical)
252 .constraints(constraints);
253
254 let chunks = layout.split(inner_area);
255
256 for (i, child) in self.children.into_iter().enumerate() {
257 if i < chunks.len() {
258 child.render(chunks[i], buffer);
259 }
260 }
261 }
262 }
263 }
264}