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
143impl Widget for LayoutWrapper {
144 fn render(self, area: Rect, buffer: &mut Buffer) {
145 let constraints = if let Some(custom_constraints) = self.constraints {
147 custom_constraints
148 } else {
149 match self.children.len() {
151 0 => vec![],
152 1 => vec![Constraint::Percentage(100)],
153 2 => vec![Constraint::Percentage(50), Constraint::Percentage(50)],
154 3 => vec![
155 Constraint::Percentage(33),
156 Constraint::Percentage(33),
157 Constraint::Percentage(34),
158 ],
159 4 => vec![Constraint::Percentage(25); 4],
160 5 => vec![Constraint::Percentage(20); 5],
161 _ => {
162 let percentage = 100 / self.children.len() as u16;
164 let remainder = 100 % self.children.len() as u16;
165 let mut constraints =
166 vec![Constraint::Percentage(percentage); self.children.len()];
167 if remainder > 0 && !constraints.is_empty() {
168 if let Some(last) = constraints.last_mut() {
170 *last = Constraint::Percentage(percentage + remainder);
171 }
172 }
173 constraints
174 }
175 }
176 };
177
178 let layout = self.layout.constraints(constraints);
180 let chunks = layout.split(area);
181
182 for (i, child) in self.children.into_iter().enumerate() {
184 if i < chunks.len() {
185 child.render(chunks[i], buffer);
186 }
187 }
188 }
189}
190
191#[derive(Clone)]
193pub struct BlockWrapper {
194 block: Block<'static>,
195 children: Vec<AnyWidget>,
196}
197
198impl BlockWrapper {
199 pub fn new(block: Block<'static>, children: Vec<AnyWidget>) -> Self {
201 Self { block, children }
202 }
203}
204
205impl Widget for BlockWrapper {
206 fn render(self, area: Rect, buffer: &mut Buffer) {
207 let inner_area = self.block.inner(area);
209
210 self.block.render(area, buffer);
212
213 if !self.children.is_empty() {
215 if self.children.len() == 1 {
216 self.children
218 .into_iter()
219 .next()
220 .unwrap()
221 .render(inner_area, buffer);
222 } else {
223 let constraints: Vec<Constraint> = (0..self.children.len())
225 .map(|_| Constraint::Min(0))
226 .collect();
227
228 let layout = Layout::default()
229 .direction(ratatui::layout::Direction::Vertical)
230 .constraints(constraints);
231
232 let chunks = layout.split(inner_area);
233
234 for (i, child) in self.children.into_iter().enumerate() {
235 if i < chunks.len() {
236 child.render(chunks[i], buffer);
237 }
238 }
239 }
240 }
241 }
242}