Skip to content

Latest commit

 

History

History
96 lines (77 loc) · 2.93 KB

File metadata and controls

96 lines (77 loc) · 2.93 KB

Ratatecs

Experiment with Bevy ECS and Ratatui to build TUI with completely isolated panels (group of widgets).

  • Panel inner state is stored in the ECS
  • Panel selection through State
  • Each panel pushes a z-ordered list of widgets that are rendered at the end of the frame

Bevy handles the run loop, storing state in the world, and passing the needed arguments to each function through dependency injection.

Note

This is experimental, and the API will evolve as I collect feedback.

Example Usage

example with 5 panels

use ratatecs::prelude::*;

fn main() {
    App::new().add_plugins((RatatEcsPlugins, app::panel)).run();
}

mod app {
    use ratatecs::prelude::*;
    use ratatui::widgets::{Block, Paragraph};
    use symbols::border;

    #[derive(Resource)]
    struct Counter(u32);

    pub fn panel(app: &mut App) {
        // Store the state of this panel in the world
        app.insert_resource(Counter(0));

        // Systems that update the state or react to user inputs
        app.add_systems(Update, (exit_on_esc, change_counter));

        // System to render thos panel
        app.add_systems(PostUpdate, render);
    }

    fn exit_on_esc(event: Res<BackendEvent>, mut exit: EventWriter<AppExit>) {
        if let Some(event) = &event.0 {
            if let event::Event::Key(key_event) = event {
                if key_event.code == event::KeyCode::Esc {
                    exit.send(AppExit::Success);
                }
            }
        }
    }

    fn change_counter(mut counter: ResMut<Counter>, event: Res<BackendEvent>) {
        if let Some(event) = &event.0 {
            if let event::Event::Key(key_event) = event {
                match key_event.code {
                    event::KeyCode::Left => counter.0 = counter.0.saturating_sub(1),
                    event::KeyCode::Right => counter.0 += 1,
                    _ => (),
                }
            }
        }
    }

    fn render(counter: Res<Counter>, mut drawer: WidgetDrawer) {
        let frame = drawer.get_frame();
        let area = frame.area();

        let title = Line::from(" My Great TUI ".bold());
        let instructions = Line::from(vec![
            " Decrement ".into(),
            "<Left>".blue().bold(),
            " Increment ".into(),
            "<Right>".blue().bold(),
            " Quit ".into(),
            "<Esc> ".blue().bold(),
        ]);
        let block = Block::bordered()
            .title(title.centered())
            .title_bottom(instructions.centered())
            .border_set(border::THICK);

        let counter_text = Text::from(vec![Line::from(vec![
            "Value: ".into(),
            counter.0.to_string().yellow(),
        ])]);

        drawer.push_widget(
            Box::new(Paragraph::new(counter_text).centered().block(block)),
            area,
            0,
        );
    }
}