tui-framework-experiment 0.4.0

An set of harmonious Ratatui widgets with a goal of building a proper widget framework.
Documentation
use rand::Rng;
use ratatui::prelude::*;
use ratatui::widgets::*;
use tui_framework_experiment::events::{EventHandler, Key, KeyPressedEvent, MouseEvent};
use tui_framework_experiment::StackContainer;

#[derive(Debug)]
pub struct StackTab {
    counter: usize,
    stack: StackContainer,
}

impl Default for StackTab {
    fn default() -> Self {
        use Constraint::*;
        let stack = StackContainer::vertical().with_widgets(vec![
            // a couple of widgets to start with
            (
                Box::new(
                    Span::raw("First Span")
                        .bg(Color::LightBlue)
                        .fg(Color::Black),
                ),
                Length(1),
            ),
            (
                Box::new(
                    Paragraph::new("Second\n(paragraph)")
                        .bg(Color::LightYellow)
                        .fg(Color::Black),
                ),
                Length(2),
            ),
        ]);
        StackTab { counter: 2, stack }
    }
}

impl Widget for &StackTab {
    fn render(self, area: Rect, buf: &mut Buffer) {
        use Constraint::*;
        let [instruction, items] = Layout::vertical([Length(1), Fill(1)]).areas(area);
        Line::from("Press space to add a new item, backspace to remove the last one.")
            .centered()
            .render(instruction, buf);
        self.stack.render(items, buf);
    }
}

impl EventHandler for StackTab {
    fn handle_key(&mut self, event: KeyPressedEvent) {
        match event.key {
            Key::Char(' ') => self.add_widget(),
            Key::Backspace => self.remove_widget(),
            _ => {}
        }
    }

    fn handle_mouse(&mut self, _event: MouseEvent) {}
}

impl StackTab {
    fn add_widget(&mut self) {
        self.counter += 1;
        let (widget, constraint) = self.random_widget();
        self.stack.push(widget, constraint);
    }

    fn remove_widget(&mut self) {
        if self.counter == 0 {
            return;
        }
        self.counter -= 1;
        self.stack.remove(self.counter);
    }

    fn random_widget(&mut self) -> (Box<dyn WidgetRef>, Constraint) {
        let text = format!("Item {}", self.counter);

        let mut rng = rand::rng();

        let color_index = rng.random_range(0..=255);
        let style = Style::new()
            .bg(Color::Indexed(color_index))
            .fg(Color::Black);

        let choice = rng.random_range(0..3);

        let widget: Box<dyn WidgetRef> = match choice {
            0 => Box::new(Paragraph::new(format!("{}\n(paragraph)", text)).style(style)),
            1 => Box::new(Line::styled(format!("{} (line)", text), style)),
            2 => Box::new(Span::styled(format!("{} (span)", text), style)),
            _ => unreachable!(),
        };
        let constraint = match choice {
            0 => Constraint::Length(2),
            1 => Constraint::Length(1),
            2 => Constraint::Length(1),
            _ => unreachable!(),
        };

        (widget, constraint)
    }
}