use crate::vnode::Element;
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
widgets::{Block, List, Paragraph, Widget},
};
#[derive(Clone)]
pub enum AnyWidget {
Layout(LayoutWrapper),
Block(BlockWrapper),
Paragraph(Paragraph<'static>),
List(List<'static>),
VNode(Element),
}
impl Widget for AnyWidget {
fn render(self, area: Rect, buffer: &mut Buffer) {
match self {
AnyWidget::Layout(layout) => layout.render(area, buffer),
AnyWidget::Block(block) => block.render(area, buffer),
AnyWidget::Paragraph(paragraph) => paragraph.render(area, buffer),
AnyWidget::List(list) => list.render(area, buffer),
AnyWidget::VNode(vnode) => vnode.render(area, buffer),
}
}
}
impl From<LayoutWrapper> for AnyWidget {
fn from(layout: LayoutWrapper) -> Self {
AnyWidget::Layout(layout)
}
}
impl From<BlockWrapper> for AnyWidget {
fn from(block: BlockWrapper) -> Self {
AnyWidget::Block(block)
}
}
impl From<Paragraph<'static>> for AnyWidget {
fn from(paragraph: Paragraph<'static>) -> Self {
AnyWidget::Paragraph(paragraph)
}
}
impl From<List<'static>> for AnyWidget {
fn from(list: List<'static>) -> Self {
AnyWidget::List(list)
}
}
impl From<Element> for AnyWidget {
fn from(vnode: Element) -> Self {
AnyWidget::VNode(vnode)
}
}
impl From<Block<'static>> for AnyWidget {
fn from(block: Block<'static>) -> Self {
AnyWidget::Block(BlockWrapper::new(block, vec![]))
}
}
impl From<String> for AnyWidget {
fn from(text: String) -> Self {
AnyWidget::VNode(Element::text(text))
}
}
impl From<&str> for AnyWidget {
fn from(text: &str) -> Self {
AnyWidget::VNode(Element::text(text.to_string()))
}
}
impl From<&String> for AnyWidget {
fn from(text: &String) -> Self {
AnyWidget::VNode(Element::text(text.clone()))
}
}
impl From<ratatui::text::Span<'static>> for AnyWidget {
fn from(span: ratatui::text::Span<'static>) -> Self {
use ratatui::text::Line;
let line = Line::from(vec![span]);
let paragraph = Paragraph::new(vec![line]);
AnyWidget::Paragraph(paragraph)
}
}
impl From<ratatui::text::Line<'static>> for AnyWidget {
fn from(line: ratatui::text::Line<'static>) -> Self {
let paragraph = Paragraph::new(vec![line]);
AnyWidget::Paragraph(paragraph)
}
}
#[derive(Clone)]
pub struct LayoutWrapper {
layout: Layout,
children: Vec<AnyWidget>,
constraints: Option<Vec<Constraint>>,
}
impl LayoutWrapper {
pub fn new(layout: Layout, children: Vec<AnyWidget>) -> Self {
Self {
layout,
children,
constraints: None,
}
}
pub fn with_constraints(
layout: Layout,
children: Vec<AnyWidget>,
constraints: Vec<Constraint>,
) -> Self {
Self {
layout,
children,
constraints: Some(constraints),
}
}
pub fn from_elements(layout: Layout, children: Vec<Element>) -> Self {
Self {
layout,
children: children.into_iter().map(AnyWidget::from).collect(),
constraints: None,
}
}
pub fn from_elements_with_constraints(
layout: Layout,
children: Vec<Element>,
constraints: Vec<Constraint>,
) -> Self {
Self {
layout,
children: children.into_iter().map(AnyWidget::from).collect(),
constraints: Some(constraints),
}
}
}
impl Widget for LayoutWrapper {
fn render(self, area: Rect, buffer: &mut Buffer) {
let constraints = if let Some(custom_constraints) = self.constraints {
custom_constraints
} else {
match self.children.len() {
0 => vec![],
1 => vec![Constraint::Percentage(100)],
2 => vec![Constraint::Percentage(50), Constraint::Percentage(50)],
3 => vec![
Constraint::Percentage(33),
Constraint::Percentage(33),
Constraint::Percentage(34),
],
4 => vec![Constraint::Percentage(25); 4],
5 => vec![Constraint::Percentage(20); 5],
_ => {
let percentage = 100 / self.children.len() as u16;
let remainder = 100 % self.children.len() as u16;
let mut constraints =
vec![Constraint::Percentage(percentage); self.children.len()];
if remainder > 0 && !constraints.is_empty() {
if let Some(last) = constraints.last_mut() {
*last = Constraint::Percentage(percentage + remainder);
}
}
constraints
}
}
};
let layout = self.layout.constraints(constraints);
let chunks = layout.split(area);
for (i, child) in self.children.into_iter().enumerate() {
if i < chunks.len() {
child.render(chunks[i], buffer);
}
}
}
}
#[derive(Clone)]
pub struct BlockWrapper {
block: Block<'static>,
children: Vec<AnyWidget>,
}
impl BlockWrapper {
pub fn new(block: Block<'static>, children: Vec<AnyWidget>) -> Self {
Self { block, children }
}
}
impl Widget for BlockWrapper {
fn render(self, area: Rect, buffer: &mut Buffer) {
let inner_area = self.block.inner(area);
self.block.render(area, buffer);
if !self.children.is_empty() {
if self.children.len() == 1 {
self.children
.into_iter()
.next()
.unwrap()
.render(inner_area, buffer);
} else {
let constraints: Vec<Constraint> = (0..self.children.len())
.map(|_| Constraint::Min(0))
.collect();
let layout = Layout::default()
.direction(ratatui::layout::Direction::Vertical)
.constraints(constraints);
let chunks = layout.split(inner_area);
for (i, child) in self.children.into_iter().enumerate() {
if i < chunks.len() {
child.render(chunks[i], buffer);
}
}
}
}
}
}