bevy_ratatui

repository·main·Indexed 41 days ago

https://github.com/ratatui/bevy_ratatui

A Bevy plugin for building terminal user interfaces (TUI) with Ratatui. It bridges the Bevy game engine's ECS-driven architecture with the Ratatui library, allowing developers to handle terminal input and rendering through Bevy schedules. Version 0.12.0 supports features like windowed rendering, the Kitty keyboard protocol, and mouse capture.

Tokens
7.1K
Snippets
28
Records
34
Agent score
1%

What's inside bevy_ratatui

  1. Understand the `bevy_ratatui` application pattern

    main

    The bevy_ratatui integration follows a specific flow through the Bevy schedules to handle terminal input and rendering. This pattern is applicable to games, dashboards, and editors:

    1. Initialization: RatatuiPlugins initializes the terminal context and emits terminal events (e.g., KeyMessage).
    2. Input Translation: A PreUpdate system translates terminal-specific messages (like KeyMessage) into your application's domain-specific messages (e.g., GameCommand). This decouples terminal key codes from your core logic.
    3. State Update: Chained Update systems synchronize terminal-dependent resources, read your application commands, and advance the application state in a deliberate order.
    4. Rendering: A PostUpdate system borrows RatatuiContext and calls draw. This ensures rendering sees the state produced by the current update cycle without the rendering system owning or mutating the application rules.
  2. Quickstart: Set up a basic Ratatui application in Bevy

    main

    To use bevy_ratatui, add RatatuiPlugins to your Bevy App. You can then access the RatatuiContext resource in a system to perform drawing operations using the standard Ratatui API.

    Note: In the example below, MinimalPlugins is configured with a ScheduleRunnerPlugin to define the frame rate.

    use bevy::prelude::*;
    use bevy::app::ScheduleRunnerPlugin;
    use bevy_ratatui::{RatatuiContext, RatatuiPlugins};
    
    fn main() {
        let frame_time = std::time::Duration::from_secs_f32(1. / 60.);
    
        App::new()
            .add_plugins((
                MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(frame_time)),
                RatatuiPlugins::default(),
            ))
            .add_systems(Update, draw_system)
            .run();
    }
    
    fn draw_system(mut context: ResMut<RatatuiContext>) -> Result<(), Box<dyn std::error::Error>> {
        context.draw(|frame| {
            let text = ratatui::text::Text::raw("hello world");
            frame.render_widget(text, frame.area());
        })?;
    
        Ok(())
    }
  3. Handle user input in bevy_ratatui

    main

    There are two primary ways to handle user input:

    1. Listen for Crossterm messages: Use MessageReader<KeyMessage> to read forwarded crossterm input events directly.
    2. Map to Bevy Input: Enable the enable_input_forwarding option in RatatuiPlugins to map crossterm events to standard Bevy input messages.
    use bevy::app::AppExit;
    use bevy_ratatui::event::KeyMessage;
    use crossterm::event::KeyCode;
    
    fn input_system(mut messages: MessageReader<KeyMessage>, mut exit: MessageWriter<AppExit>) {
        for message in messages.read() {
            if let KeyCode::Char('q') = message.code {
                exit.write_default();
            }
        }
    }
  4. Configure keyboard emulation with EmulationPolicy

    main

    Some terminals do not emit key release events or independent modifier events. You can control how bevy_ratatui emulates these missing capabilities using the EmulationPolicy resource.

    • EmulationPolicy::Automatic: The default policy. It emulates any capability (key release or modifiers) that has not been explicitly detected from the terminal.
    • EmulationPolicy::Manual(Capability): Allows you to specify exactly which capabilities to emulate using the Capability bitflags.

    To use emulation, you must also insert the Emulate marker resource into your Bevy app.

    use bevy::prelude::*;
    use bevy_ratatui::crossterm_context::translation::keyboard_translation::{EmulationPolicy, Emulate, Capability};
    
    // Example: Manually emulating only key releases
    fn setup_emulation(mut commands: Commands) {
        commands.insert_resource(Emulate);
        commands.insert_resource(EmulationPolicy::Manual(Capability::KEY_RELEASE));
    }
  5. Use the windowed rendering feature

    main

    The windowed feature allows you to render your Ratatui application in a window instead of the terminal buffer.

    When using this feature, bevy_ratatui only enables the Bevy features required for the windowed backend: windowing, rendering, and bevy_ui. If your application requires other Bevy features (like audio, 3D, scenes, or gamepads), you must enable them explicitly in your bevy dependency, as Cargo features are additive.

  6. Order input processing using InputSet

    main

    The EventPlugin uses a specific set of system sets to order input processing within the PreUpdate schedule. If you need to ensure your input handling systems run at a specific stage relative to the terminal event emission, use the InputSet enum with .in_set().

    To ensure the ordering applies correctly, systems using these extension points must be added to PreUpdate.

    The execution order is:

    1. InputSet::Pre: Before any input messages are emitted.
    2. InputSet::EmitCrossterm: When crossterm messages are emitted.
    3. InputSet::CheckEmulation: For checking emulation.
    4. InputSet::EmitBevy: When Bevy-native messages are emitted (requires TranslationPlugin).
    5. InputSet::Post: After all input messages are emitted.
    # use bevy::prelude::*;
    # use bevy_ratatui::event::{InputSet, KeyMessage};
    # fn handle_keys(_: MessageReader<KeyMessage>) {}
    
    fn handle_keys(_: MessageReader<KeyMessage>) {}
    
    // In your app setup:
    // app.add_systems(PreUpdate, handle_keys.in_set(InputSet::Post));
  7. Quickstart: Building a terminal application with Bevy and Ratatui

    main

    To use bevy_ratatui, add RatatuiPlugins to your Bevy App. You can then access the RatatuiContext resource in your systems to draw Ratatui widgets. For handling input, you can use the MessageReader to listen for events like KeyMessage (when the crossterm feature is enabled).

    use std::time::Duration;
    
    use bevy::{
        app::{AppExit, ScheduleRunnerPlugin},
        prelude::*,
    };
    use bevy_ratatui::{event::KeyMessage, RatatuiContext, RatatuiPlugins};
    use ratatui::crossterm::event::KeyCode;
    use ratatui::text::Text;
    
    fn main() {
        let frame_time = Duration::from_secs_f32(1. / 60.);
    
        App::new()
            .add_plugins((
                MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(frame_time)),
                RatatuiPlugins::default(),
            ))
            .add_systems(PreUpdate, input_system)
            .add_systems(Update, draw_system)
            .run();
    }
    
    fn draw_system(mut context: ResMut<RatatuiContext>) -> Result<(), Box<dyn std::error::Error>> {
        context.draw(|frame| {
            let text = Text::raw("hello world\npress 'q' to quit");
            frame.render_widget(text, frame.area());
        })?;
    
        Ok(())
    }
    
    fn input_system(mut messages: MessageReader<KeyMessage>, mut exit: MessageWriter<AppExit>) {
        for message in messages.read() {
            if let KeyCode::Char('q') = message.code {
                exit.write_default();
            }
        }
    }
  8. Enable mouse support with MousePlugin

    main

    To enable mouse capture in your Bevy application using crossterm, add the MousePlugin to your App. This plugin executes a startup system that enables mouse capture in the terminal and inserts a MouseEnabled resource.

    When the application shuts down, the MouseEnabled resource is dropped, which automatically executes DisableMouseCapture to restore the terminal to its original state.

    use bevy::prelude::*;
    use bevy_ratatui::MousePlugin;
    
    fn main() {
        App::new()
            .add_plugins(DefaultPlugins)
            .add_plugins(MousePlugin)
            .run();
    }
  9. Setup Ratatui in a Bevy application using RatatuiPlugins

    main

    To integrate Ratatui into your Bevy application, use the RatatuiPlugins plugin group. This group includes the ContextPlugin and configures the default terminal context. You can customize the plugin behavior by configuring the fields in the RatatuiPlugins struct.

    Available configuration options:

    • enable_kitty_protocol: (bool) Enables the kitty graphics protocol if available. Defaults to true.
    • enable_mouse_capture: (bool) Enables mouse capture. Defaults to false.
    • enable_input_forwarding: (bool) Forwards terminal input events to the Bevy input system. Defaults to false.
    use bevy::prelude::*;
    use bevy_ratatui::RatatuiPlugins;
    
    fn main() {
        App::new()
            .add_plugins(RatatuiPlugins::default())
            .run();
    }
  10. Setup the EventPlugin for terminal input

    main

    To handle terminal events (keyboard, mouse, resize, etc.) in Bevy, add the EventPlugin to your app. By default, it includes a control_c_interrupt feature that automatically sends a Bevy AppExit message when Ctrl+C is pressed.

    use bevy::prelude::*;
    use bevy_ratatui::event::EventPlugin;
    
    fn main() {
        App::new()
            .add_plugins(DefaultPlugins)
            .add_plugins(EventPlugin::default())
            .run();
    }