A (forked) rust crate for using Ratatui in a Bevy application.
1use bevy::{
2 asset::RenderAssetUsages,
3 prelude::*,
4 render::render_resource::{Extent3d, TextureDimension, TextureFormat},
5 window::WindowResized,
6};
7
8use crate::RatatuiContext;
9
10/// A plugin that, rather than drawing to a terminal buffer, uses software rendering to build a 2D
11/// texture from the ratatui buffer, and displays the result in a window.
12pub struct WindowedPlugin;
13
14impl Plugin for WindowedPlugin {
15 fn build(&self, app: &mut App) {
16 app.add_systems(PostStartup, terminal_render_setup)
17 .add_systems(PreUpdate, handle_resize_messages)
18 .add_systems(Update, render_terminal_to_handle);
19 }
20}
21
22#[derive(Resource)]
23struct TerminalRender(Handle<Image>);
24
25/// A startup system that sets up the terminal
26pub fn terminal_render_setup(
27 mut commands: Commands,
28 softatui: ResMut<RatatuiContext>,
29 mut images: ResMut<Assets<Image>>,
30) -> Result {
31 commands.spawn(Camera2d);
32 // Create an image that we are going to draw into
33 let width = softatui.backend().get_pixmap_width() as u32;
34 let height = softatui.backend().get_pixmap_height() as u32;
35 let data = softatui.backend().get_pixmap_data_as_rgba();
36
37 let image = Image::new(
38 Extent3d {
39 width,
40 height,
41 depth_or_array_layers: 1,
42 },
43 TextureDimension::D2,
44 data,
45 TextureFormat::Rgba8UnormSrgb,
46 RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD,
47 );
48 let handle = images.add(image);
49 commands.spawn((
50 ImageNode::new(handle.clone()),
51 Node {
52 justify_self: JustifySelf::Center,
53 align_self: AlignSelf::Center,
54 ..default()
55 },
56 ));
57
58 commands.insert_resource(TerminalRender(handle));
59
60 Ok(())
61}
62
63/// System that updates the terminal texture each frame
64fn render_terminal_to_handle(
65 softatui: ResMut<RatatuiContext>,
66 mut images: ResMut<Assets<Image>>,
67 my_handle: Res<TerminalRender>,
68) {
69 let width = softatui.backend().get_pixmap_width() as u32;
70 let height = softatui.backend().get_pixmap_height() as u32;
71 let data = softatui.backend().get_pixmap_data_as_rgba();
72
73 let image = images.get_mut(&my_handle.0).expect("Image not found");
74 *image = Image::new(
75 Extent3d {
76 width,
77 height,
78 depth_or_array_layers: 1,
79 },
80 TextureDimension::D2,
81 data,
82 TextureFormat::Rgba8UnormSrgb,
83 RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD,
84 );
85}
86
87/// System that reacts to window resize
88fn handle_resize_messages(
89 mut resize_reader: MessageReader<WindowResized>,
90 mut softatui: ResMut<RatatuiContext>,
91) {
92 for message in resize_reader.read() {
93 let cur_pix_width = softatui.backend().char_width;
94 let cur_pix_height = softatui.backend().char_height;
95 let av_wid = (message.width / cur_pix_width as f32) as u16;
96 let av_hei = (message.height / cur_pix_height as f32) as u16;
97 softatui.backend_mut().resize(av_wid, av_hei);
98 }
99}