├── new.avif ├── .DS_Store ├── assets ├── fm.ttf ├── iosevka.ttf ├── iosevka_bold.ttf └── iosevka_italic.ttf ├── more_examples_click_me ├── egui_colors_example │ ├── my_image.png │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── egui_modifier_example │ ├── my_image.png │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── egui_modifier_example_cosmic │ ├── my_image.png │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── bevy_example │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── bevy_cube_example │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── bevy_demo_example │ ├── Cargo.toml │ └── src │ │ └── main.rs └── bevy_cube_colors │ └── Cargo.toml ├── todo.txt ├── examples ├── cosmic.rs ├── bdf.rs ├── ttf.rs └── embedded.rs ├── .gitignore ├── LICENSE-MIT.md ├── Cargo.toml ├── scratch ├── random.rs └── ab_glyph.rs ├── src ├── colors.rs ├── pixmap.rs ├── lib.rs ├── soft_backend.rs ├── embedded_backend.rs ├── embedded_ttf_backend.rs ├── bdf_backend.rs └── cosmic_backend.rs ├── README.md ├── LICENSE-APACHE.txt └── Cargo.lock /new.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/new.avif -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/.DS_Store -------------------------------------------------------------------------------- /assets/fm.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/assets/fm.ttf -------------------------------------------------------------------------------- /assets/iosevka.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/assets/iosevka.ttf -------------------------------------------------------------------------------- /assets/iosevka_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/assets/iosevka_bold.ttf -------------------------------------------------------------------------------- /assets/iosevka_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/assets/iosevka_italic.ttf -------------------------------------------------------------------------------- /more_examples_click_me/egui_colors_example/my_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/more_examples_click_me/egui_colors_example/my_image.png -------------------------------------------------------------------------------- /more_examples_click_me/egui_modifier_example/my_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/more_examples_click_me/egui_modifier_example/my_image.png -------------------------------------------------------------------------------- /more_examples_click_me/egui_modifier_example_cosmic/my_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gold-silver-copper/soft_ratatui/HEAD/more_examples_click_me/egui_modifier_example_cosmic/my_image.png -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | 2 | Showcase projects using it 3 | 4 | update bevy_ratatui 5 | update egui_ratatui 6 | 7 | 8 | cargo run -- cozette.bdf --suffix "_all" --output ../../src --range 0x0000..0x10FFFF 9 | ffmpeg -i input.mov -c:v libaom-av1 -crf 23 -cpu-used 6 -an output.avif 10 | -------------------------------------------------------------------------------- /more_examples_click_me/bevy_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_example" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | bevy = "0.17.0" 8 | ratatui = { version = "0.29.0", default-features = false } 9 | soft_ratatui = { path = "../.."} 10 | [target.wasm32-unknown-unknown] 11 | runner = "wasm-server-runner" 12 | -------------------------------------------------------------------------------- /more_examples_click_me/bevy_cube_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_cube_example" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | bevy = "0.17.0" 8 | ratatui = { version = "0.29.0", default-features = false } 9 | soft_ratatui = { path = "../.." } 10 | embedded-graphics-unicodefonts = "0.2.0" 11 | [target.wasm32-unknown-unknown] 12 | runner = "wasm-server-runner" 13 | -------------------------------------------------------------------------------- /more_examples_click_me/bevy_demo_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_demo_example" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | bevy = "0.17.0" 8 | rand = "0.9.2" 9 | ratatui = { version = "0.29.0", default-features = false } 10 | soft_ratatui = { path = "../.." , features=["bdf-parser"]} 11 | [target.wasm32-unknown-unknown] 12 | runner = "wasm-server-runner" 13 | -------------------------------------------------------------------------------- /more_examples_click_me/egui_modifier_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | color-eyre = "0.6.4" 8 | eframe = "0.31.1" 9 | itertools = "0.14.0" 10 | palette = "0.7.6" 11 | ratatui = "0.29.0" 12 | soft_ratatui = { path = "../..", default-features= false, features = ["embedded-ttf"]} 13 | embedded-graphics-unicodefonts = "0.2.0" 14 | -------------------------------------------------------------------------------- /more_examples_click_me/egui_modifier_example_cosmic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | color-eyre = "0.6.4" 8 | eframe = "0.31.1" 9 | itertools = "0.14.0" 10 | palette = "0.7.6" 11 | ratatui = "0.29.0" 12 | embedded-graphics-unicodefonts = "0.2.0" 13 | soft_ratatui = { path = "../.." , default-features= false, features = ["cosmic-text"]} 14 | -------------------------------------------------------------------------------- /more_examples_click_me/bevy_cube_colors/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_cube_example" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | bevy = "0.17.0" 8 | ratatui = { version = "0.29.0", default-features = false } 9 | itertools = "0.14.0" 10 | palette = "0.7.6" 11 | color-eyre = "0.6.4" 12 | soft_ratatui = { path = "../.." ,features=["bdf-parser"]} 13 | embedded-graphics-unicodefonts = "0.2.0" 14 | [target.wasm32-unknown-unknown] 15 | runner = "wasm-server-runner" 16 | -------------------------------------------------------------------------------- /more_examples_click_me/egui_colors_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egui_example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | color-eyre = "0.6.4" 8 | eframe = "0.31.1" 9 | itertools = "0.14.0" 10 | palette = "0.7.6" 11 | ratatui = "0.29.0" 12 | soft_ratatui = { path = "../.." , default-features= false, features = ["bdf-parser"]} 13 | embedded-graphics = "0.8.1" 14 | embedded-graphics-unicodefonts = "0.2.0" 15 | [profile.release] 16 | opt-level = 3 17 | #lto = true # Enable link-time optimization 18 | #odegen-units = 1 # Better optimizations, slightly slower compile 19 | #panic = "abort" # Smaller binary if you don't need backtraces 20 | #strip = "symbols" # Remove debug symbols (unless you need them) 21 | 22 | # Uncomment this for even smaller output (disable debug info) 23 | #debug = true 24 | -------------------------------------------------------------------------------- /examples/cosmic.rs: -------------------------------------------------------------------------------- 1 | use ratatui::Terminal; 2 | /// A minimal example of a Ratatui application. 3 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 4 | use soft_ratatui::{CosmicText, SoftBackend}; 5 | static FONT: &[u8] = include_bytes!("../assets/fm.ttf"); 6 | fn main() { 7 | let backend = SoftBackend::::new(100, 50, 16, FONT); 8 | let mut terminal = Terminal::new(backend).unwrap(); 9 | terminal.clear(); 10 | 11 | terminal.draw(|frame| { 12 | let area = frame.area(); 13 | let textik = format!("Hello soft! The window area is {}", area); 14 | frame.render_widget( 15 | Paragraph::new(textik) 16 | .block(Block::new().title("Ratatui").borders(Borders::ALL)) 17 | .wrap(Wrap { trim: false }), 18 | area, 19 | ); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /examples/bdf.rs: -------------------------------------------------------------------------------- 1 | use ratatui::Terminal; 2 | /// A minimal example of a Ratatui application. 3 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 4 | use soft_ratatui::{Bdf, SoftBackend}; 5 | static FONT: &str = include_str!("../assets/cozette.bdf"); 6 | fn main() { 7 | let backend = SoftBackend::::new(100, 50, (6, 13), FONT, None, None); 8 | let mut terminal = Terminal::new(backend).unwrap(); 9 | terminal.clear(); 10 | 11 | terminal.draw(|frame| { 12 | let area = frame.area(); 13 | let textik = format!("Hello soft! The window area is {}", area); 14 | frame.render_widget( 15 | Paragraph::new(textik) 16 | .block(Block::new().title("Ratatui").borders(Borders::ALL)) 17 | .wrap(Wrap { trim: false }), 18 | area, 19 | ); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /egui_example/target 3 | /egui_colors_example/target 4 | /egui_modifier_example/target 5 | /egui_colors_example/target 6 | /egui_colors_example/target 7 | /egui_colors_example/target 8 | /egui_colors_example/target/release 9 | /egui_colors_example/target 10 | /bevy_example/target 11 | /bevy_cube_example/target 12 | /egui_modifier_example_cosmic/target 13 | /bevy_cube_colors/target 14 | /more_examples_click_me/bevy_cube_colors/target 15 | /more_examples_click_me/bevy_cube_example/target 16 | /more_examples_click_me/bevy_demo_example/target 17 | /more_examples_click_me/bevy_example/target 18 | /more_examples_click_me/egui_colors_example/target 19 | /more_examples_click_me/egui_modifier_example/target 20 | /more_examples_click_me/egui_modifier_example_cosmic/target 21 | target/ 22 | more_examples_click_me/bevy_cube_colors/Cargo.lock 23 | more_examples_click_me/bevy_example/Cargo.lock 24 | -------------------------------------------------------------------------------- /examples/ttf.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics_unicodefonts::{ 2 | mono_8x13_atlas, mono_8x13_bold_atlas, mono_8x13_italic_atlas, 3 | }; 4 | use ratatui::Terminal; 5 | /// A minimal example of a Ratatui application. 6 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 7 | use rusttype::Font; 8 | use soft_ratatui::{EmbeddedGraphics, EmbeddedTTF, SoftBackend}; 9 | 10 | fn main() { 11 | let font_regular = Font::try_from_bytes(include_bytes!("../assets/iosevka.ttf")).unwrap(); 12 | let backend = SoftBackend::::new(100, 50, 16, font_regular, None, None); 13 | let mut terminal = Terminal::new(backend).unwrap(); 14 | terminal.clear(); 15 | 16 | terminal.draw(|frame| { 17 | let area = frame.area(); 18 | let textik = format!("Hello soft! The window area is {}", area); 19 | frame.render_widget( 20 | Paragraph::new(textik) 21 | .block(Block::new().title("Ratatui").borders(Borders::ALL)) 22 | .wrap(Wrap { trim: false }), 23 | area, 24 | ); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2025 gold silver copper 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /examples/embedded.rs: -------------------------------------------------------------------------------- 1 | use embedded_graphics_unicodefonts::{ 2 | mono_8x13_atlas, mono_8x13_bold_atlas, mono_8x13_italic_atlas, 3 | }; 4 | use ratatui::Terminal; 5 | /// A minimal example of a Ratatui application. 6 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 7 | use soft_ratatui::{EmbeddedGraphics, SoftBackend}; 8 | 9 | fn main() { 10 | let font_regular = mono_8x13_atlas(); 11 | let font_italic = mono_8x13_italic_atlas(); 12 | let font_bold = mono_8x13_bold_atlas(); 13 | let backend = SoftBackend::::new( 14 | 100, 15 | 50, 16 | font_regular, 17 | Some(font_bold), 18 | Some(font_italic), 19 | ); 20 | let mut terminal = Terminal::new(backend).unwrap(); 21 | terminal.clear(); 22 | 23 | terminal.draw(|frame| { 24 | let area = frame.area(); 25 | let textik = format!("Hello soft! The window area is {}", area); 26 | frame.render_widget( 27 | Paragraph::new(textik) 28 | .block(Block::new().title("Ratatui").borders(Borders::ALL)) 29 | .wrap(Wrap { trim: false }), 30 | area, 31 | ); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "soft_ratatui" 3 | version = "0.1.2" 4 | edition = "2024" 5 | authors = ["gold-silver-copper"] 6 | 7 | include = ["LICENSE-APACHE", "LICENSE-MIT", "**/*.rs", "Cargo.toml"] 8 | 9 | description = "Software rendering for ratatui. TUI everywhere." 10 | license = "MIT OR Apache-2.0" 11 | repository = "https://github.com/gold-silver-copper/soft_ratatui" 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [dependencies] 17 | rustc-hash = "2.1" 18 | ratatui = { version = "0.29.0", default-features = false } 19 | embedded-graphics = {version = "0.8.1",optional = true} 20 | 21 | embedded-graphics-unicodefonts = {version = "0.2.0",optional = true} 22 | cosmic-text = { version = "0.14.2", optional = true } 23 | copper-bdf-parser = { version = "0.1.1", optional = true } 24 | embedded-ttf = { version = "0.2.2", optional = true } 25 | rusttype = { version = "0.9.3", optional = true } 26 | [features] 27 | default = ["unicodefonts",] 28 | unicodefonts = ["embedded-graphics","dep:embedded-graphics-unicodefonts"] 29 | embedded-graphics = ["dep:embedded-graphics"] 30 | cosmic-text = ["dep:cosmic-text"] 31 | bdf-parser = ["dep:copper-bdf-parser"] 32 | embedded-ttf = ["dep:embedded-graphics","dep:embedded-ttf","dep:rusttype"] 33 | 34 | #cosmic-text = ["dep:bdf-parser"] 35 | 36 | [[example]] 37 | name = "bdf" 38 | path = "examples/bdf.rs" 39 | required-features = ["bdf-parser"] 40 | [[example]] 41 | name = "cosmic" 42 | path = "examples/cosmic.rs" 43 | required-features = ["cosmic-text"] 44 | [[example]] 45 | name = "ttf" 46 | path = "examples/ttf.rs" 47 | required-features = ["embedded-ttf"] 48 | 49 | 50 | [package.metadata.docs.rs] 51 | # Enable all features for documentation 52 | all-features = true 53 | 54 | [profile.release] 55 | #opt-level = 1 56 | #lto = true # Enable link-time optimization 57 | #codegen-units = 1 # Better optimizations, slightly slower compile 58 | #panic = "abort" # Smaller binary if you don't need backtraces 59 | #strip = "symbols" # Remove debug symbols (unless you need them) 60 | 61 | # Uncomment this for even smaller output (disable debug info) 62 | #debug = true 63 | -------------------------------------------------------------------------------- /scratch/random.rs: -------------------------------------------------------------------------------- 1 | /* 2 | pub fn rat_to_cosmic_color(rat_col: &RatColor, is_a_fg: bool) -> CosmicColor { 3 | match rat_col { 4 | RatColor::Reset => { 5 | if is_a_fg { 6 | CosmicColor::rgba(204, 204, 255, 255) 7 | } else { 8 | CosmicColor::rgba(15, 15, 112, 255) 9 | } 10 | } 11 | RatColor::Black => CosmicColor::rgba(0, 0, 0, 255), 12 | RatColor::Red => CosmicColor::rgba(139, 0, 0, 255), 13 | RatColor::Green => CosmicColor::rgba(0, 100, 0, 255), 14 | RatColor::Yellow => CosmicColor::rgba(255, 215, 0, 255), 15 | RatColor::Blue => CosmicColor::rgba(0, 0, 139, 255), 16 | RatColor::Magenta => CosmicColor::rgba(99, 9, 99, 255), 17 | RatColor::Cyan => CosmicColor::rgba(0, 0, 255, 255), 18 | RatColor::Gray => CosmicColor::rgba(128, 128, 128, 255), 19 | RatColor::DarkGray => CosmicColor::rgba(64, 64, 64, 255), 20 | RatColor::LightRed => CosmicColor::rgba(255, 0, 0, 255), 21 | RatColor::LightGreen => CosmicColor::rgba(0, 255, 0, 255), 22 | RatColor::LightBlue => CosmicColor::rgba(173, 216, 230, 255), 23 | RatColor::LightYellow => CosmicColor::rgba(255, 255, 224, 255), 24 | RatColor::LightMagenta => CosmicColor::rgba(139, 0, 139, 255), 25 | RatColor::LightCyan => CosmicColor::rgba(224, 255, 255, 255), 26 | RatColor::White => CosmicColor::rgba(255, 255, 255, 255), 27 | RatColor::Indexed(i) => { 28 | let i = *i as u8; 29 | CosmicColor::rgba(i.wrapping_mul(i), i.wrapping_add(i), i, 255) 30 | } 31 | RatColor::Rgb(r, g, b) => CosmicColor::rgba(*r, *g, *b, 255), 32 | } 33 | } 34 | */ 35 | 36 | /// Blend two RGBA colors using alpha compositing. 37 | /// 38 | /// (fg over bg) -> resulting RGB 39 | /// 40 | /// * `fg` - [R, G, B, A] foreground color 41 | /// * `bg` - [R, G, B, A] background color 42 | /// 43 | /// Returns: blended color as [u8; 4] 44 | pub fn blend_rgba(fg: [u8; 4], bg: [u8; 4]) -> [u8; 3] { 45 | let fg_a = fg[3] as f32 / 255.0; 46 | let bg_a = bg[3] as f32 / 255.0; 47 | let out_a = fg_a + bg_a * (1.0 - fg_a); 48 | 49 | let blend_channel = 50 | |f: u8, b: u8| ((f as f32 * fg_a + b as f32 * bg_a * (1.0 - fg_a)) / out_a).round() as u8; 51 | 52 | if out_a == 0.0 { 53 | [0, 0, 0] 54 | } else { 55 | [ 56 | blend_channel(fg[0], bg[0]), 57 | blend_channel(fg[1], bg[1]), 58 | blend_channel(fg[2], bg[2]), 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /more_examples_click_me/bevy_example/src/main.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | asset::RenderAssetUsages, 3 | prelude::*, 4 | render::render_resource::{Extent3d, TextureDimension, TextureFormat}, 5 | }; 6 | use ratatui::{ 7 | prelude::{Stylize, Terminal}, 8 | widgets::{Block, Borders, Paragraph, Wrap}, 9 | }; 10 | use soft_ratatui::embedded_graphics_unicodefonts::{ 11 | mono_8x13_atlas, mono_8x13_bold_atlas, mono_8x13_italic_atlas, 12 | }; 13 | use soft_ratatui::{EmbeddedGraphics, SoftBackend}; 14 | 15 | fn main() { 16 | App::new() 17 | .add_plugins(DefaultPlugins) 18 | .init_resource::() 19 | .add_systems(Startup, setup) 20 | .add_systems(Update, ui_example_system) 21 | .run(); 22 | } 23 | fn setup( 24 | mut commands: Commands, 25 | softatui: ResMut, 26 | mut images: ResMut>, 27 | ) { 28 | commands.spawn(Camera2d); 29 | 30 | let width = softatui.backend().get_pixmap_width() as u32; 31 | let height = softatui.backend().get_pixmap_height() as u32; 32 | let data = softatui.backend().get_pixmap_data_as_rgba(); 33 | 34 | let image = Image::new( 35 | Extent3d { 36 | width, 37 | height, 38 | depth_or_array_layers: 1, 39 | }, 40 | TextureDimension::D2, 41 | data, 42 | TextureFormat::Rgba8Unorm, 43 | RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD, 44 | ); 45 | let handle = images.add(image); 46 | commands.spawn(Sprite::from_image(handle.clone())); 47 | commands.insert_resource(MyProcGenImage(handle)); 48 | } 49 | 50 | // Render to the terminal and to egui , both are immediate mode 51 | fn ui_example_system( 52 | mut softatui: ResMut, 53 | mut images: ResMut>, 54 | my_handle: Res, 55 | ) { 56 | softatui 57 | .draw(|frame| { 58 | let area = frame.area(); 59 | let textik = format!("Hello bevy! The window area is {}", area); 60 | frame.render_widget( 61 | Paragraph::new(textik) 62 | .block(Block::new().title("Ratatui").borders(Borders::ALL)) 63 | .white() 64 | .on_blue() 65 | .wrap(Wrap { trim: false }), 66 | area, 67 | ); 68 | }) 69 | .expect("epic fail"); 70 | 71 | let width = softatui.backend().get_pixmap_width() as u32; 72 | let height = softatui.backend().get_pixmap_height() as u32; 73 | let data = softatui.backend().get_pixmap_data_as_rgba(); 74 | 75 | let image = images.get_mut(&my_handle.0).expect("Image not found"); 76 | *image = Image::new( 77 | Extent3d { 78 | width, 79 | height, 80 | depth_or_array_layers: 1, 81 | }, 82 | TextureDimension::D2, 83 | data, 84 | TextureFormat::Rgba8Unorm, 85 | RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD, 86 | ); 87 | } 88 | 89 | // Create resource to hold the ratatui terminal 90 | #[derive(Resource, Deref, DerefMut)] 91 | struct SoftTerminal(Terminal>); 92 | impl Default for SoftTerminal { 93 | fn default() -> Self { 94 | let font_regular = mono_8x13_atlas(); 95 | let font_italic = mono_8x13_italic_atlas(); 96 | let font_bold = mono_8x13_bold_atlas(); 97 | let backend = SoftBackend::::new( 98 | 100, 99 | 50, 100 | font_regular, 101 | Some(font_bold), 102 | Some(font_italic), 103 | ); 104 | //backend.set_font_size(12); 105 | Self(Terminal::new(backend).unwrap()) 106 | } 107 | } 108 | 109 | #[derive(Resource)] 110 | struct MyProcGenImage(Handle); 111 | -------------------------------------------------------------------------------- /src/colors.rs: -------------------------------------------------------------------------------- 1 | use ratatui::style::Color as RatColor; 2 | 3 | ///Converts a Ratatui Color into a rgb [u8;3] 4 | pub fn rat_to_rgb(rat_col: &RatColor, is_a_fg: bool) -> [u8; 3] { 5 | match rat_col { 6 | RatColor::Reset => { 7 | if is_a_fg { 8 | [204, 204, 255] // foreground reset color 9 | } else { 10 | [5, 1, 121] // background reset color 11 | } 12 | } 13 | RatColor::Black => [0, 0, 0], 14 | RatColor::Red => [139, 0, 0], 15 | RatColor::Green => [0, 100, 0], 16 | RatColor::Yellow => [255, 215, 0], 17 | RatColor::Blue => [0, 0, 139], 18 | RatColor::Magenta => [255, 0, 255], 19 | RatColor::Cyan => [0, 0, 255], 20 | RatColor::Gray => [128, 128, 128], 21 | RatColor::DarkGray => [64, 64, 64], 22 | RatColor::LightRed => [255, 0, 0], 23 | RatColor::LightGreen => [0, 255, 0], 24 | RatColor::LightBlue => [173, 216, 230], 25 | RatColor::LightYellow => [255, 255, 224], 26 | RatColor::LightMagenta => [139, 0, 139], 27 | RatColor::LightCyan => [224, 255, 255], 28 | RatColor::White => [255, 255, 255], 29 | RatColor::Indexed(i) => { 30 | let i = *i as u8; 31 | [i.wrapping_mul(i), i.wrapping_add(i), i] 32 | } 33 | RatColor::Rgb(r, g, b) => [*r, *g, *b], 34 | } 35 | } 36 | 37 | pub fn dim_rgb(color: [u8; 3]) -> [u8; 3] { 38 | let factor = 77; // 77 ≈ 255 * 0.3 39 | [ 40 | ((color[0] as u32 * factor + 127) / 255) as u8, 41 | ((color[1] as u32 * factor + 127) / 255) as u8, 42 | ((color[2] as u32 * factor + 127) / 255) as u8, 43 | ] 44 | } 45 | 46 | /// Blend two RGBA colors using alpha compositing. 47 | /// 48 | /// (fg over bg) -> resulting RGB 49 | /// 50 | /// * `fg` - [R, G, B, A] foreground color 51 | /// * `bg` - [R, G, B, A] background color 52 | /// 53 | /// Returns: blended color as [u8; 4] 54 | pub fn blend_rgba(fg: [u8; 4], bg: [u8; 4]) -> [u8; 3] { 55 | let fg_a = fg[3] as f32 / 255.0; 56 | let bg_a = bg[3] as f32 / 255.0; 57 | let out_a = fg_a + bg_a * (1.0 - fg_a); 58 | 59 | let blend_channel = 60 | |f: u8, b: u8| ((f as f32 * fg_a + b as f32 * bg_a * (1.0 - fg_a)) / out_a).round() as u8; 61 | 62 | if out_a == 0.0 { 63 | [0, 0, 0] 64 | } else { 65 | [ 66 | blend_channel(fg[0], bg[0]), 67 | blend_channel(fg[1], bg[1]), 68 | blend_channel(fg[2], bg[2]), 69 | ] 70 | } 71 | } 72 | 73 | pub fn blend_ignore_bg_alpha(fg: [u8; 4], bg: [u8; 3]) -> [u8; 3] { 74 | let alpha = fg[3] as f32 / 255.0; 75 | let inv_alpha = 1.0 - alpha; 76 | 77 | [ 78 | (fg[0] as f32 * alpha + bg[0] as f32 * inv_alpha).round() as u8, 79 | (fg[1] as f32 * alpha + bg[1] as f32 * inv_alpha).round() as u8, 80 | (fg[2] as f32 * alpha + bg[2] as f32 * inv_alpha).round() as u8, 81 | ] 82 | } 83 | pub fn blend_gamma_corrected(fg: [u8; 4], bg: [u8; 4]) -> [u8; 3] { 84 | let fg_a = fg[3] as f32 / 255.0; 85 | let bg_a = bg[3] as f32 / 255.0; 86 | let out_a = fg_a + bg_a * (1.0 - fg_a); 87 | 88 | if out_a == 0.0 { 89 | return [0, 0, 0]; 90 | } 91 | 92 | // Convert to linear space (approx gamma 2.2) 93 | let to_linear = |c: u8| (c as f32 / 255.0).powf(2.2); 94 | let from_linear = |c: f32| (c.powf(1.0 / 2.2).clamp(0.0, 1.0) * 255.0).round() as u8; 95 | 96 | let blend_channel = |f: u8, b: u8| { 97 | let f_lin = to_linear(f); 98 | let b_lin = to_linear(b); 99 | let result_lin = (f_lin * fg_a + b_lin * bg_a * (1.0 - fg_a)) / out_a; 100 | from_linear(result_lin) 101 | }; 102 | 103 | [ 104 | blend_channel(fg[0], bg[0]), 105 | blend_channel(fg[1], bg[1]), 106 | blend_channel(fg[2], bg[2]), 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /src/pixmap.rs: -------------------------------------------------------------------------------- 1 | use core::convert::Infallible; 2 | #[cfg(any(feature = "embedded-graphics", feature = "embedded-ttf"))] 3 | use embedded_graphics::{ 4 | Pixel, 5 | draw_target::DrawTarget, 6 | geometry::{OriginDimensions, Size}, 7 | pixelcolor::Rgb888, 8 | prelude::*, 9 | }; 10 | 11 | /// A pixmap with RGB pixels stored in a flat vector. 12 | #[derive(Debug, Clone)] 13 | pub struct RgbPixmap { 14 | pub width: usize, 15 | pub height: usize, 16 | pub data: Vec, 17 | } 18 | 19 | impl RgbPixmap { 20 | /// Creates a new pixmap. 21 | pub fn new(width: usize, height: usize) -> Self { 22 | let data = [0, 0, 0].repeat(width * height); 23 | Self { 24 | width, 25 | height, 26 | data, 27 | } 28 | } 29 | /// Outputs the RGBpixmap as a RGBA flat vector, useful when target renderer does not take pure RGB data 30 | pub fn to_rgba(&self) -> Vec { 31 | let mut rgba_data = Vec::with_capacity(self.width * self.height * 4); 32 | for chunk in self.data.chunks_exact(3) { 33 | let r = chunk[0]; 34 | let g = chunk[1]; 35 | let b = chunk[2]; 36 | rgba_data.extend_from_slice(&[r, g, b, 255]); // Alpha = 255 for no transparency 37 | } 38 | rgba_data 39 | } 40 | /// Outputs the RGBpixmap as a RGBA flat vector, with a color set to transparent, useful for stuff 41 | pub fn to_rgba_with_color_as_transparent(&self, color: &(u8, u8, u8)) -> Vec { 42 | let mut rgba_data = Vec::with_capacity(self.width * self.height * 4); 43 | for chunk in self.data.chunks_exact(3) { 44 | let r = chunk[0]; 45 | let g = chunk[1]; 46 | let b = chunk[2]; 47 | if &(r, g, b) == color { 48 | rgba_data.extend_from_slice(&[r, g, b, 0]); 49 | } else { 50 | rgba_data.extend_from_slice(&[r, g, b, 255]); 51 | } 52 | } 53 | rgba_data 54 | } 55 | 56 | /// Sets the RGB value of a pixel at (x, y). 57 | pub fn put_pixel(&mut self, x: usize, y: usize, color: [u8; 3]) { 58 | if x < self.width && y < self.height { 59 | let index = 3 * (y * self.width + x); 60 | self.data[index..index + 3].copy_from_slice(&color); 61 | } 62 | } 63 | 64 | /// Returns the RGB value of a pixel at (x, y). 65 | pub fn get_pixel(&self, x: usize, y: usize) -> [u8; 3] { 66 | debug_assert!( 67 | x < self.width && y < self.height, 68 | "Pixel coordinates out of bounds" 69 | ); 70 | let index = 3 * (y * self.width + x); 71 | self.data[index..index + 3] 72 | .try_into() 73 | .expect("ERROR RETRIEVING VALUE FROM X Y COORDINATES") 74 | } 75 | 76 | /// Fills the entire pixmap with the specified RGB color. 77 | pub fn fill(&mut self, color: [u8; 3]) { 78 | for chunk in self.data.chunks_mut(3) { 79 | chunk.copy_from_slice(&color); 80 | } 81 | } 82 | 83 | /// Returns the width of the pixmap in pixels 84 | pub fn width(&self) -> usize { 85 | self.width 86 | } 87 | /// Returns the height of the pixmap in pixels 88 | pub fn height(&self) -> usize { 89 | self.height 90 | } 91 | 92 | /// Retuns the raw rgb data of the pixmap as a flat array 93 | pub fn data(&self) -> &[u8] { 94 | &self.data 95 | } 96 | } 97 | #[cfg(any(feature = "embedded-graphics", feature = "embedded-ttf"))] 98 | impl DrawTarget for RgbPixmap { 99 | type Color = Rgb888; 100 | type Error = Infallible; 101 | 102 | fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> 103 | where 104 | I: IntoIterator>, 105 | { 106 | for Pixel(coord, color) in pixels { 107 | // if coord.x >= 0&& coord.y >=0 {} 108 | self.put_pixel( 109 | coord.x as usize, 110 | coord.y as usize, 111 | [color.r(), color.g(), color.b()], 112 | ); 113 | } 114 | Ok(()) 115 | } 116 | } 117 | #[cfg(any(feature = "embedded-graphics", feature = "embedded-ttf"))] 118 | impl OriginDimensions for RgbPixmap { 119 | fn size(&self) -> Size { 120 | Size::new(self.width as u32, self.height as u32) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # soft_ratatui 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/soft_ratatui.svg)](https://crates.io/crates/soft_ratatui) 4 | [![Documentation](https://docs.rs/soft_ratatui/badge.svg)](https://docs.rs/soft_ratatui/latest/soft_ratatui/) 5 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/bevyengine/bevy/blob/master/LICENSE) 6 | [![Downloads](https://img.shields.io/crates/d/soft_ratatui.svg)](https://crates.io/crates/soft_ratatui) 7 | 8 | **Software rendering backend for [`ratatui`](https://github.com/ratatui/ratatui). No GPU required. TUI everywhere.** 9 | 10 | ![](new.avif) 11 | 12 | 13 | Fast, portable, no-bloat. 14 | 15 | - Optimized for speed, generally faster than running ratatui inside a terminal with crossterm. 120+ fps on normal workloads. 16 | - Choose your own rendering backend: embedded-graphics, embedded-ttf, cosmic-text, bdf-parser 17 | - Custom portable pixel rasterizer, outputs RGB/RGBA pixmaps, color-to-alpha support 18 | 19 | ## Feature Flags 20 | 21 | soft_ratatui is highly modular. Enable only the backends and features you need to reduce binary size and dependencies. 22 | 23 | | Feature | Enables | Description | 24 | |---------|---------|-------------| 25 | | `unicodefonts` | [`embedded_graphics_unicodefonts`] | Embedded-graphics fonts with Unicode support. Automatically enables `embedded-graphics`. Enabled by default. | 26 | | `embedded-graphics` | [`EmbeddedGraphics`] | Uses embedded-graphics font atlases for TUI rendering. | 27 | | `bdf-parser` | [`Bdf`] | Bitmap Distribution Format font support. | 28 | | `embedded-ttf` | [`EmbeddedTTF`] | TrueType font rendering via RustType. Automatically enables `embedded-graphics`. | 29 | | `cosmic-text` | [`CosmicText`] | Advanced text shaping, layout, and Unicode support using CosmicText engine. | 30 | 31 | ## Minimal example 32 | 33 | ```Rust 34 | use soft_ratatui::embedded_graphics_unicodefonts::{ 35 | mono_8x13_atlas, mono_8x13_bold_atlas, mono_8x13_italic_atlas, 36 | }; 37 | use ratatui::Terminal; 38 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 39 | use soft_ratatui::{EmbeddedGraphics, SoftBackend}; 40 | 41 | fn main() { 42 | let font_regular = mono_8x13_atlas(); 43 | let font_italic = mono_8x13_italic_atlas(); 44 | let font_bold = mono_8x13_bold_atlas(); 45 | let backend = SoftBackend::::new( 46 | 100, 47 | 50, 48 | font_regular, 49 | Some(font_bold), 50 | Some(font_italic), 51 | ); 52 | let mut terminal = Terminal::new(backend).unwrap(); 53 | terminal.clear(); 54 | 55 | terminal.draw(|frame| { 56 | let area = frame.area(); 57 | let textik = format!("Hello soft! The window area is {}", area); 58 | frame.render_widget( 59 | Paragraph::new(textik) 60 | .block(Block::new().title("Ratatui").borders(Borders::ALL)) 61 | .wrap(Wrap { trim: false }), 62 | area, 63 | ); 64 | }); 65 | } 66 | 67 | ``` 68 | 69 | ## Integrations 70 | 71 | - [`egui`](https://github.com/emilk/egui) integration provided by [`egui_ratatui`](https://github.com/gold-silver-copper/egui_ratatui). Have a TUI inside your GUI! 72 | - [`bevy_ratatui`](https://github.com/cxreiff/bevy_ratatui) integration allows you to turn an existing terminal app built with bevy_ratatui into a native or web app. The best way to build a terminal app!! 73 | - [`bevy`](https://github.com/bevyengine/bevy) game engine examples provided in the repo, so you can create your own game UI or world textures with ratatui 74 | - WASM compatible, deploy your ratatui application on the web! 75 | 76 | ## See also 77 | 78 | - [`mousefood`](https://github.com/j-g00da/mousefood) - a no-std embedded-graphics backend for Ratatui! 79 | - [`ratzilla`](https://github.com/orhun/ratzilla) - Build terminal-themed web applications with Rust and WebAssembly. 80 | - [`ratatui-wgpu`](https://github.com/Jesterhearts/ratatui-wgpu) - A wgpu based rendering backend for ratatui. 81 | - [`bevy_ratatui_camera`](https://github.com/cxreiff/bevy_ratatui_camera) - A bevy plugin for rendering your bevy app to the terminal using ratatui. 82 | 83 | ## Cool BDF fonts 84 | 85 | - [`spleen`](https://github.com/fcambus/spleen) (works best, many sizes) 86 | - [`cozette`](https://github.com/the-moonwitch/Cozette) (very pretty) 87 | 88 | ## License 89 | 90 | Dual-licensed under **MIT** or **Apache 2.0**. 91 | Pick whichever suits you. 92 | 93 | 94 | ## Status 95 | 96 | Comments and suggestions are appreciated. 97 | -------------------------------------------------------------------------------- /more_examples_click_me/egui_modifier_example_cosmic/src/main.rs: -------------------------------------------------------------------------------- 1 | /// A Ratatui example that demonstrates how to use modifiers. 2 | /// 3 | /// It will render a grid of combinations of foreground and background colors with all 4 | /// modifiers applied to them. 5 | /// 6 | /// This example runs with the Ratatui library code in the branch that you are currently 7 | /// reading. See the [`latest`] branch for the code which works with the most recent Ratatui 8 | /// release. 9 | /// 10 | /// [`latest`]: https://github.com/ratatui/ratatui/tree/latest 11 | use std::iter::once; 12 | 13 | use eframe::egui::{self, TextureHandle}; 14 | 15 | use itertools::Itertools; 16 | use ratatui::layout::{Constraint, Layout}; 17 | use ratatui::style::{Color, Modifier, Style, Stylize}; 18 | use ratatui::text::Line; 19 | use ratatui::widgets::Paragraph; 20 | /// A minimal example of a Ratatui application. 21 | use ratatui::{Frame, Terminal}; 22 | 23 | use soft_ratatui::{CosmicText, SoftBackend}; 24 | static FONT_DATA: &[u8] = include_bytes!("../../../assets/iosevka.ttf"); 25 | fn main() -> eframe::Result { 26 | let options = eframe::NativeOptions { 27 | viewport: egui::ViewportBuilder::default().with_inner_size([1500.0, 1000.0]), 28 | ..Default::default() 29 | }; 30 | 31 | let my_app = MyApp::new(); 32 | 33 | eframe::run_native( 34 | "Image Viewer", 35 | options, 36 | Box::new(|cc| { 37 | // This gives us image support: 38 | 39 | Ok(Box::new(my_app)) 40 | }), 41 | ) 42 | } 43 | 44 | struct MyApp { 45 | pub terminal: Terminal>, 46 | pub text_ref: Option, 47 | } 48 | 49 | impl MyApp { 50 | fn new() -> Self { 51 | let backend = SoftBackend::::new(100, 50, 17, FONT_DATA); 52 | let terminal = Terminal::new(backend).unwrap(); 53 | 54 | Self { 55 | terminal, 56 | text_ref: None, 57 | } 58 | } 59 | } 60 | 61 | impl eframe::App for MyApp { 62 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 63 | // terminal.draw(draw).expect("failed to draw frame"); 64 | self.terminal.draw(draw).unwrap(); 65 | 66 | let colorik = egui::ColorImage::from_rgb( 67 | [ 68 | self.terminal.backend().get_pixmap_width(), 69 | self.terminal.backend().get_pixmap_height(), 70 | ], 71 | self.terminal.backend().get_pixmap_data(), 72 | ); 73 | 74 | let texture = ctx.load_texture( 75 | "my-color-image", // texture ID (can be anything) 76 | colorik.clone(), // your ColorImage 77 | Default::default(), 78 | ); 79 | self.text_ref = Some(texture.clone()); 80 | 81 | egui::CentralPanel::default().show(ctx, |ui| { 82 | egui::ScrollArea::both().show(ui, |ui| { 83 | ui.image((texture.id(), texture.size_vec2())); 84 | }); 85 | }); 86 | ctx.request_repaint(); 87 | } 88 | } 89 | 90 | fn draw(frame: &mut Frame) { 91 | let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]); 92 | let [text_area, main_area] = vertical.areas(frame.area()); 93 | frame.render_widget( 94 | Paragraph::new("Note: not all terminals support all modifiers") 95 | .style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)), 96 | text_area, 97 | ); 98 | let layout = Layout::vertical([Constraint::Length(1); 50]) 99 | .split(main_area) 100 | .iter() 101 | .flat_map(|area| { 102 | Layout::horizontal([Constraint::Percentage(20); 5]) 103 | .split(*area) 104 | .to_vec() 105 | }) 106 | .collect_vec(); 107 | 108 | let colors = [ 109 | Color::Black, 110 | Color::DarkGray, 111 | Color::Gray, 112 | Color::White, 113 | Color::Red, 114 | ]; 115 | let all_modifiers = once(Modifier::empty()) 116 | .chain(Modifier::all().iter()) 117 | .collect_vec(); 118 | let mut index = 0; 119 | for bg in &colors { 120 | for fg in &colors { 121 | for modifier in &all_modifiers { 122 | let modifier_name = format!("{modifier:11?}"); 123 | let padding = (" ").repeat(12 - modifier_name.len()); 124 | let paragraph = Paragraph::new(Line::from(vec![ 125 | modifier_name.fg(*fg).bg(*bg).add_modifier(*modifier), 126 | padding.fg(*fg).bg(*bg).add_modifier(*modifier), 127 | // This is a hack to work around a bug in VHS which is used for rendering the 128 | // examples to gifs. The bug is that the background color of a paragraph seems 129 | // to bleed into the next character. 130 | ".".black().on_black(), 131 | ])); 132 | frame.render_widget(paragraph, layout[index]); 133 | index += 1; 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /more_examples_click_me/egui_modifier_example/src/main.rs: -------------------------------------------------------------------------------- 1 | /// A Ratatui example that demonstrates how to use modifiers. 2 | /// 3 | /// It will render a grid of combinations of foreground and background colors with all 4 | /// modifiers applied to them. 5 | /// 6 | /// This example runs with the Ratatui library code in the branch that you are currently 7 | /// reading. See the [`latest`] branch for the code which works with the most recent Ratatui 8 | /// release. 9 | /// 10 | /// [`latest`]: https://github.com/ratatui/ratatui/tree/latest 11 | use std::iter::once; 12 | 13 | use eframe::egui::{self, TextureHandle}; 14 | 15 | use itertools::Itertools; 16 | use ratatui::layout::{Constraint, Layout}; 17 | use ratatui::style::{Color, Modifier, Style, Stylize}; 18 | use ratatui::text::Line; 19 | use ratatui::widgets::Paragraph; 20 | /// A minimal example of a Ratatui application. 21 | use ratatui::{Frame, Terminal}; 22 | 23 | use soft_ratatui::rusttype::Font; 24 | use soft_ratatui::{EmbeddedTTF, SoftBackend}; 25 | static FONT_DATA: &[u8] = include_bytes!("../../../assets/iosevka.ttf"); 26 | static FONT_BOLD_DATA: &[u8] = include_bytes!("../../../assets/iosevka_bold.ttf"); 27 | static FONT_ITALIC_DATA: &[u8] = include_bytes!("../../../assets/iosevka_italic.ttf"); 28 | fn main() -> eframe::Result { 29 | let options = eframe::NativeOptions { 30 | viewport: egui::ViewportBuilder::default().with_inner_size([1500.0, 1000.0]), 31 | ..Default::default() 32 | }; 33 | 34 | let my_app = MyApp::new(); 35 | 36 | eframe::run_native( 37 | "Image Viewer", 38 | options, 39 | Box::new(|cc| { 40 | // This gives us image support: 41 | 42 | Ok(Box::new(my_app)) 43 | }), 44 | ) 45 | } 46 | 47 | struct MyApp { 48 | pub terminal: Terminal>, 49 | pub text_ref: Option, 50 | } 51 | 52 | impl MyApp { 53 | fn new() -> Self { 54 | let font_regular = Font::try_from_bytes(FONT_DATA).unwrap(); 55 | let font_bold = Font::try_from_bytes(FONT_BOLD_DATA).unwrap(); 56 | let font_italic = Font::try_from_bytes(FONT_ITALIC_DATA).unwrap(); 57 | let backend = SoftBackend::::new( 58 | 100, 59 | 50, 60 | 17, 61 | font_regular, 62 | Some(font_bold), 63 | Some(font_italic), 64 | ); 65 | let terminal = Terminal::new(backend).unwrap(); 66 | 67 | Self { 68 | terminal, 69 | text_ref: None, 70 | } 71 | } 72 | } 73 | 74 | impl eframe::App for MyApp { 75 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 76 | // terminal.draw(draw).expect("failed to draw frame"); 77 | self.terminal.draw(draw).unwrap(); 78 | 79 | let colorik = egui::ColorImage::from_rgb( 80 | [ 81 | self.terminal.backend().get_pixmap_width(), 82 | self.terminal.backend().get_pixmap_height(), 83 | ], 84 | self.terminal.backend().get_pixmap_data(), 85 | ); 86 | 87 | let texture = ctx.load_texture( 88 | "my-color-image", // texture ID (can be anything) 89 | colorik.clone(), // your ColorImage 90 | Default::default(), 91 | ); 92 | self.text_ref = Some(texture.clone()); 93 | 94 | egui::CentralPanel::default().show(ctx, |ui| { 95 | egui::ScrollArea::both().show(ui, |ui| { 96 | ui.image((texture.id(), texture.size_vec2())); 97 | }); 98 | }); 99 | ctx.request_repaint(); 100 | } 101 | } 102 | 103 | fn draw(frame: &mut Frame) { 104 | let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]); 105 | let [text_area, main_area] = vertical.areas(frame.area()); 106 | frame.render_widget( 107 | Paragraph::new("Note: not all terminals support all modifiers") 108 | .style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)), 109 | text_area, 110 | ); 111 | let layout = Layout::vertical([Constraint::Length(1); 50]) 112 | .split(main_area) 113 | .iter() 114 | .flat_map(|area| { 115 | Layout::horizontal([Constraint::Percentage(20); 5]) 116 | .split(*area) 117 | .to_vec() 118 | }) 119 | .collect_vec(); 120 | 121 | let colors = [ 122 | Color::Black, 123 | Color::DarkGray, 124 | Color::Gray, 125 | Color::White, 126 | Color::Red, 127 | ]; 128 | let all_modifiers = once(Modifier::empty()) 129 | .chain(Modifier::all().iter()) 130 | .collect_vec(); 131 | let mut index = 0; 132 | for bg in &colors { 133 | for fg in &colors { 134 | for modifier in &all_modifiers { 135 | let modifier_name = format!("{modifier:11?}"); 136 | let padding = (" ").repeat(12 - modifier_name.len()); 137 | let paragraph = Paragraph::new(Line::from(vec![ 138 | modifier_name.fg(*fg).bg(*bg).add_modifier(*modifier), 139 | padding.fg(*fg).bg(*bg).add_modifier(*modifier), 140 | // This is a hack to work around a bug in VHS which is used for rendering the 141 | // examples to gifs. The bug is that the background color of a paragraph seems 142 | // to bleed into the next character. 143 | ".".black().on_black(), 144 | ])); 145 | frame.render_widget(paragraph, layout[index]); 146 | index += 1; 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Ratatui Software Backend 2 | //! 3 | //! A collection of software rendering backends for [Ratatui] that turn 4 | //! terminal UIs into RGB pixmaps. Perfect for embedding Ratatui into game engines, GUIs, or wherever else. 5 | //! 6 | //! [Ratatui]: https://github.com/ratatui/ratatui 7 | //! 8 | //! ## Features 9 | //! 10 | //! - **Multiple Font Backends**: Choose from various font rendering technologies 11 | //! - **Flexible Output**: Get raw RGB data or RGBA with color-to-alpha support 12 | //! - **Real-time Updates**: Efficient redrawing, perfect for video games 13 | //! - **Ratatui Compatible**: Full implementation of Ratatui's `Backend` trait 14 | //! - **Bevy Examples**: Show you how to get started with the great game engine, also check out [bevy_ratatui] 15 | //! 16 | //! [bevy_ratatui]: https://github.com/cxreiff/bevy_ratatui 17 | //! [embedded-graphics]: https://github.com/embedded-graphics/embedded-graphics 18 | //! [cosmic-text]: https://github.com/pop-os/cosmic-text 19 | //! [bdf-parser]: https://github.com/embedded-graphics/bdf 20 | //! [unicodefonts]: https://github.com/j-g00da/embedded-graphics-unicodefonts 21 | //! [embedded-ttf]: https://github.com/peckpeck/embedded-ttf 22 | //! 23 | //! ## Feature Flags 24 | //! 25 | //! - **`unicodefonts`**: Enables embedded-graphics-[unicodefonts] (automatically activates [embedded-graphics], is the default feature) 26 | //! - **`bdf-parser`**: Enables [`Bdf`] backend for bitmap fonts ([bdf-parser]) 27 | //! - **`embedded-ttf`**: Enables [`EmbeddedTTF`] backend for TrueType fonts (automatically activates [embedded-graphics]) ([embedded-ttf]) 28 | //! - **`cosmic-text`**: Enables [`CosmicText`] backend for advanced text shaping ([cosmic-text]) 29 | //! - **`embedded-graphics`**: Enables [`EmbeddedGraphics`] backend 30 | //! 31 | //! ## Performance 32 | //! 33 | //! Generally faster than running ratatui in a terminal with crossterm. Expect hundreds of fps on a normal workload. 34 | //! 35 | //! ## Available Backends 36 | //! 37 | //! | Backend | Feature | Description | 38 | //! |---------|---------|-------------| 39 | //! | [`EmbeddedGraphics`] | `embedded-graphics` | Uses embedded-graphics font atlases | 40 | //! | [`EmbeddedTTF`] | `embedded-ttf` | TrueType font rendering via RustType | 41 | //! | [`Bdf`] | `bdf-parser` | Bitmap Distribution Format fonts | 42 | //! | [`CosmicText`] | `cosmic-text` | Advanced text shaping and layout | 43 | //! 44 | //! ## Quick Start 45 | //! 46 | //! 47 | //! ```bash 48 | //! cargo add soft_ratatui 49 | //! cargo add ratatui 50 | //! ``` 51 | //! 52 | //! ### Minimal Example 53 | //! 54 | //! ```rust 55 | //! use soft_ratatui::embedded_graphics_unicodefonts::{ 56 | //! mono_8x13_atlas, mono_8x13_bold_atlas, mono_8x13_italic_atlas, 57 | //! }; 58 | //! use ratatui::Terminal; 59 | //! use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 60 | //! use soft_ratatui::{EmbeddedGraphics, SoftBackend}; 61 | //! 62 | //! fn main() { 63 | //! let font_regular = mono_8x13_atlas(); 64 | //! let font_italic = mono_8x13_italic_atlas(); 65 | //! let font_bold = mono_8x13_bold_atlas(); 66 | //! let backend = SoftBackend::::new( 67 | //! 100, 68 | //! 50, 69 | //! font_regular, 70 | //! Some(font_bold), 71 | //! Some(font_italic), 72 | //! ); 73 | //! let mut terminal = Terminal::new(backend).unwrap(); 74 | //! terminal.clear(); 75 | //! 76 | //! terminal.draw(|frame| { 77 | //! let area = frame.area(); 78 | //! let textik = format!("Hello soft! The window area is {}", area); 79 | //! frame.render_widget( 80 | //! Paragraph::new(textik) 81 | //! .block(Block::new().title("Ratatui").borders(Borders::ALL)) 82 | //! .wrap(Wrap { trim: false }), 83 | //! area, 84 | //! ); 85 | //! }); 86 | //! } 87 | //! ``` 88 | //! 89 | //! ## Usage Patterns 90 | //! 91 | //! ### Getting Rendered Output 92 | //! 93 | //! ```rust,no_run 94 | //! # use soft_ratatui::{SoftBackend, EmbeddedGraphics}; 95 | //! # let backend = SoftBackend::::new(80, 24, todo!(), None, None); 96 | //! 97 | //! // Get raw RGB data (3 bytes per pixel: R, G, B) 98 | //! let rgb_data = backend.get_pixmap_data(); 99 | //! 100 | //! // Get RGBA data (4 bytes per pixel: R, G, B, A) 101 | //! let rgba_data = backend.get_pixmap_data_as_rgba(); 102 | //! 103 | //! // Get RGBA data with one of the colors set to fully transparent 104 | //! let rgba_data = backend.rgb_pixmap.to_rgba_with_color_as_transparent((255,0,255)); 105 | //! 106 | //! // Get dimensions 107 | //! let width = backend.get_pixmap_width(); 108 | //! let height = backend.get_pixmap_height(); 109 | //! ``` 110 | //! 111 | //! ## Examples 112 | //! 113 | //! See the `examples/` directory for complete working examples with different 114 | //! backends and font configurations. 115 | //! 116 | //! ## License 117 | //! 118 | //! This project is licensed under the MIT and Apache2.0 license. 119 | 120 | pub use pixmap::RgbPixmap; 121 | mod soft_backend; 122 | pub use soft_backend::{RasterBackend, SoftBackend}; 123 | mod colors; 124 | mod pixmap; 125 | 126 | #[cfg(feature = "embedded-graphics")] 127 | mod embedded_backend; 128 | #[cfg(feature = "embedded-graphics")] 129 | pub use embedded_backend::EmbeddedGraphics; 130 | 131 | #[cfg(feature = "cosmic-text")] 132 | pub use cosmic_backend::CosmicText; 133 | #[cfg(feature = "unicodefonts")] 134 | pub use embedded_graphics_unicodefonts; 135 | #[cfg(feature = "embedded-ttf")] 136 | pub use embedded_ttf; 137 | #[cfg(feature = "embedded-ttf")] 138 | pub use rusttype; 139 | #[cfg(feature = "embedded-ttf")] 140 | mod embedded_ttf_backend; 141 | #[cfg(feature = "embedded-ttf")] 142 | pub use embedded_ttf_backend::EmbeddedTTF; 143 | #[cfg(feature = "cosmic-text")] 144 | mod cosmic_backend; 145 | #[cfg(feature = "bdf-parser")] 146 | pub use bdf_backend::Bdf; 147 | #[cfg(feature = "bdf-parser")] 148 | mod bdf_backend; 149 | -------------------------------------------------------------------------------- /src/soft_backend.rs: -------------------------------------------------------------------------------- 1 | use crate::pixmap::RgbPixmap; 2 | use ratatui::buffer::{Buffer, Cell}; 3 | use rustc_hash::FxHashSet; 4 | 5 | use std::io; 6 | 7 | use crate::colors::*; 8 | 9 | use ratatui::backend::{Backend, WindowSize}; 10 | 11 | use ratatui::layout::{Position, Rect, Size}; 12 | 13 | /// SoftBackend is a Software rendering backend for Ratatui. It stores the generated image internally as rgb_pixmap. 14 | pub struct SoftBackend { 15 | pub buffer: Buffer, 16 | pub cursor: bool, 17 | pub cursor_pos: (u16, u16), 18 | pub char_width: usize, 19 | pub char_height: usize, 20 | pub blink_counter: u16, 21 | pub blinking_fast: bool, 22 | pub blinking_slow: bool, 23 | pub rgb_pixmap: RgbPixmap, 24 | pub always_redraw_list: FxHashSet<(u16, u16)>, 25 | pub raster_backend: R, 26 | } 27 | /// Trait for raster backends (TTF, embedded-graphics, etc.) 28 | pub trait RasterBackend { 29 | fn draw_cell( 30 | &mut self, 31 | x: u16, 32 | y: u16, 33 | rat_cell: &Cell, 34 | always_redraw_list: &mut FxHashSet<(u16, u16)>, 35 | blinking_fast: bool, 36 | blinking_slow: bool, 37 | char_width: usize, 38 | char_height: usize, 39 | rgb_pixmap: &mut RgbPixmap, 40 | ); 41 | } 42 | 43 | impl Backend for SoftBackend { 44 | fn draw<'a, I>(&mut self, content: I) -> io::Result<()> 45 | where 46 | I: Iterator, 47 | { 48 | self.update_blinking(); 49 | for (x, y, c) in content { 50 | self.buffer[(x, y)] = c.clone(); 51 | self.raster_backend.draw_cell( 52 | x, 53 | y, 54 | c, 55 | &mut self.always_redraw_list, 56 | self.blinking_fast, 57 | self.blinking_slow, 58 | self.char_width, 59 | self.char_height, 60 | &mut self.rgb_pixmap, 61 | ); 62 | } 63 | for (x, y) in self.always_redraw_list.clone().iter() { 64 | let c = &self.buffer[(*x, *y)]; 65 | self.raster_backend.draw_cell( 66 | *x, 67 | *y, 68 | c, 69 | &mut self.always_redraw_list, 70 | self.blinking_fast, 71 | self.blinking_slow, 72 | self.char_width, 73 | self.char_height, 74 | &mut self.rgb_pixmap, 75 | ); 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | fn hide_cursor(&mut self) -> io::Result<()> { 82 | self.cursor = false; 83 | Ok(()) 84 | } 85 | 86 | fn show_cursor(&mut self) -> io::Result<()> { 87 | self.cursor = true; 88 | Ok(()) 89 | } 90 | 91 | fn get_cursor_position(&mut self) -> io::Result { 92 | Ok(self.cursor_pos.into()) 93 | } 94 | 95 | fn set_cursor_position>(&mut self, position: P) -> io::Result<()> { 96 | self.cursor_pos = position.into().into(); 97 | Ok(()) 98 | } 99 | 100 | fn clear(&mut self) -> io::Result<()> { 101 | self.buffer.reset(); 102 | let clear_cell = Cell::EMPTY; 103 | let colorik = rat_to_rgb(&clear_cell.bg, false); 104 | self.rgb_pixmap.fill([colorik[0], colorik[1], colorik[2]]); 105 | Ok(()) 106 | } 107 | 108 | fn size(&self) -> io::Result { 109 | Ok(self.buffer.area.as_size()) 110 | } 111 | 112 | fn window_size(&mut self) -> io::Result { 113 | let window_pixels = Size { 114 | width: self.get_pixmap_width() as u16, 115 | height: self.get_pixmap_height() as u16, 116 | }; 117 | Ok(WindowSize { 118 | columns_rows: self.buffer.area.as_size(), 119 | pixels: window_pixels, 120 | }) 121 | } 122 | 123 | fn flush(&mut self) -> io::Result<()> { 124 | Ok(()) 125 | } 126 | } 127 | 128 | impl SoftBackend { 129 | /// Returns the raw RGB data of the pixmap as a flat array. 130 | pub fn get_pixmap_data(&self) -> &[u8] { 131 | self.rgb_pixmap.data() 132 | } 133 | 134 | /// Returns the pixmap in RGBA format as a flat vector. 135 | pub fn get_pixmap_data_as_rgba(&self) -> Vec { 136 | self.rgb_pixmap.to_rgba() 137 | } 138 | 139 | /// Returns the width of the pixmap in pixels. 140 | pub fn get_pixmap_width(&self) -> usize { 141 | self.rgb_pixmap.width() 142 | } 143 | 144 | /// Returns the height of the pixmap in pixels. 145 | pub fn get_pixmap_height(&self) -> usize { 146 | self.rgb_pixmap.height() 147 | } 148 | 149 | /// Returns a reference to the internal buffer of the `SoftBackend`. 150 | pub const fn buffer(&self) -> &Buffer { 151 | &self.buffer 152 | } 153 | 154 | /// Resizes the `SoftBackend` to the specified width and height. 155 | pub fn resize(&mut self, width: u16, height: u16) { 156 | self.buffer.resize(Rect::new(0, 0, width, height)); 157 | let rgb_pixmap = RgbPixmap::new( 158 | self.char_width as usize * width as usize, 159 | self.char_height as usize * height as usize, 160 | ); 161 | self.rgb_pixmap = rgb_pixmap; 162 | self.redraw(); 163 | } 164 | 165 | /// Redraws the pixmap 166 | pub fn redraw(&mut self) { 167 | self.always_redraw_list = FxHashSet::default(); 168 | for x in 0..self.buffer.area.width { 169 | for y in 0..self.buffer.area.height { 170 | let c = &self.buffer[(x, y)]; 171 | self.raster_backend.draw_cell( 172 | x, 173 | y, 174 | c, 175 | &mut self.always_redraw_list, 176 | self.blinking_fast, 177 | self.blinking_slow, 178 | self.char_width, 179 | self.char_height, 180 | &mut self.rgb_pixmap, 181 | ); 182 | } 183 | } 184 | } 185 | 186 | fn update_blinking(&mut self) { 187 | self.blink_counter = (self.blink_counter + 1) % 200; 188 | 189 | self.blinking_fast = matches!(self.blink_counter % 100, 0..=5); 190 | self.blinking_slow = matches!(self.blink_counter, 20..=25); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/embedded_backend.rs: -------------------------------------------------------------------------------- 1 | //! This module provides the `SoftBackend` implementation for the [`Backend`] trait. 2 | //! It is used in the integration tests to verify the correctness of the library. 3 | 4 | use rustc_hash::FxHashSet; 5 | 6 | use crate::colors::*; 7 | use crate::pixmap::RgbPixmap; 8 | use crate::soft_backend::RasterBackend; 9 | 10 | use embedded_graphics::Drawable; 11 | 12 | use embedded_graphics::mono_font::{MonoFont, MonoTextStyleBuilder}; 13 | use embedded_graphics::pixelcolor::Rgb888; 14 | use embedded_graphics::prelude::{Point, RgbColor}; 15 | use embedded_graphics::text::Text; 16 | 17 | use crate::SoftBackend; 18 | use ratatui::backend::Backend; 19 | use ratatui::buffer::{Buffer, Cell}; 20 | use ratatui::layout::Rect; 21 | use ratatui::style; 22 | 23 | /// PREFERRED: uses the embedded-graphics library for rendering, best when paired with embedded-graphics-unicodefonts (enabled by default) 24 | pub struct EmbeddedGraphics { 25 | pub font_regular: MonoFont<'static>, 26 | /// Bold font. 27 | pub font_bold: Option>, 28 | /// Italic font. 29 | pub font_italic: Option>, 30 | } 31 | 32 | impl RasterBackend for EmbeddedGraphics { 33 | fn draw_cell( 34 | &mut self, 35 | xik: u16, 36 | yik: u16, 37 | rat_cell: &Cell, 38 | always_redraw_list: &mut FxHashSet<(u16, u16)>, 39 | blinking_fast: bool, 40 | blinking_slow: bool, 41 | char_width: usize, 42 | char_height: usize, 43 | rgb_pixmap: &mut RgbPixmap, 44 | ) { 45 | let mut rat_fg = rat_to_rgb(&rat_cell.fg, true); 46 | let mut rat_bg = rat_to_rgb(&rat_cell.bg, false); 47 | 48 | let mut style_builder = MonoTextStyleBuilder::new() 49 | .font(&self.font_regular) 50 | .text_color(Rgb888::WHITE) 51 | .background_color(Rgb888::BLACK); 52 | 53 | for modifier in rat_cell.modifier.iter() { 54 | style_builder = match modifier { 55 | style::Modifier::BOLD => match &self.font_bold { 56 | None => style_builder, 57 | Some(font) => style_builder.font(font), 58 | }, 59 | style::Modifier::DIM => { 60 | (rat_fg, rat_bg) = (dim_rgb(rat_fg), dim_rgb(rat_bg)); 61 | style_builder 62 | } 63 | style::Modifier::ITALIC => match &self.font_italic { 64 | None => style_builder, 65 | Some(font) => style_builder.font(font), 66 | }, 67 | style::Modifier::UNDERLINED => style_builder.underline(), 68 | style::Modifier::SLOW_BLINK => { 69 | always_redraw_list.insert((xik, yik)); 70 | if blinking_slow { 71 | rat_fg = rat_bg; 72 | } 73 | style_builder 74 | } 75 | style::Modifier::RAPID_BLINK => { 76 | always_redraw_list.insert((xik, yik)); 77 | if blinking_fast { 78 | rat_fg = rat_bg; 79 | } 80 | style_builder 81 | } 82 | style::Modifier::REVERSED => { 83 | (rat_bg, rat_fg) = (rat_fg, rat_bg); 84 | 85 | style_builder 86 | } 87 | style::Modifier::HIDDEN => { 88 | rat_fg = rat_bg; 89 | 90 | style_builder 91 | } 92 | style::Modifier::CROSSED_OUT => style_builder.strikethrough(), 93 | _ => style_builder, 94 | } 95 | } 96 | 97 | style_builder = style_builder 98 | .text_color(Rgb888::new(rat_fg[0], rat_fg[1], rat_fg[2])) 99 | .background_color(Rgb888::new(rat_bg[0], rat_bg[1], rat_bg[2])); 100 | 101 | let begin_x = xik as usize * char_width; 102 | let begin_y = yik as usize * char_height; 103 | Text::with_baseline( 104 | rat_cell.symbol(), 105 | Point::new(begin_x as i32, begin_y as i32), 106 | style_builder.build(), 107 | embedded_graphics::text::Baseline::Top, 108 | ) 109 | .draw(rgb_pixmap) 110 | .unwrap(); 111 | } 112 | } 113 | 114 | impl SoftBackend { 115 | /// Creates a new software backend with the given monospaced font(s). 116 | /// 117 | /// (new width height font-regular font-bold font-italic) -> SoftBackend 118 | /// 119 | /// * width : u16 - Width of the terminal in cells 120 | /// * height : u16 - Height of the terminal in cells 121 | /// * font-regular : MonoFont<'static> - Required base monospaced font 122 | /// * font-bold : Option> - Optional bold font 123 | /// * font-italic : Option> - Optional italic font 124 | /// 125 | /// # Examples 126 | /// ```rust 127 | /// use embedded_graphics_unicodefonts::{mono_8x13_atlas, mono_8x13_bold_atlas, mono_8x13_italic_atlas}; 128 | /// use embedded_graphics::mono_font::{ascii::FONT_8X13, MonoFont}; 129 | /// 130 | /// let backend = SoftBackend::::new(100,50,font_regular,Some(font_bold),Some(font_italic)); 131 | /// ``` 132 | 133 | pub fn new( 134 | width: u16, 135 | height: u16, 136 | font_regular: MonoFont<'static>, 137 | font_bold: Option>, 138 | font_italic: Option>, 139 | ) -> Self { 140 | let char_width = font_regular.character_size.width as usize; 141 | let char_height = font_regular.character_size.height as usize; 142 | let rgb_pixmap = RgbPixmap::new(char_width * width as usize, char_height * height as usize); 143 | 144 | let mut return_struct = Self { 145 | buffer: Buffer::empty(Rect::new(0, 0, width, height)), 146 | cursor: false, 147 | cursor_pos: (0, 0), 148 | raster_backend: EmbeddedGraphics { 149 | font_regular: font_regular, 150 | font_bold, 151 | font_italic, 152 | }, 153 | 154 | rgb_pixmap, 155 | 156 | char_width, 157 | char_height, 158 | 159 | blink_counter: 0, 160 | blinking_fast: false, 161 | blinking_slow: false, 162 | always_redraw_list: FxHashSet::default(), 163 | }; 164 | _ = return_struct.clear(); 165 | return_struct 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /more_examples_click_me/bevy_cube_example/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Shows how to render UI to a texture. Useful for displaying UI in 3D space. 2 | 3 | use bevy::{ 4 | asset::RenderAssetUsages, 5 | camera::RenderTarget, 6 | color::palettes::css::GOLD, 7 | prelude::*, 8 | render::render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages}, 9 | }; 10 | use embedded_graphics_unicodefonts::{ 11 | mono_8x13_atlas, mono_8x13_bold_atlas, mono_8x13_italic_atlas, 12 | }; 13 | use ratatui::{ 14 | Frame, 15 | prelude::{Stylize, Terminal}, 16 | widgets::{Block, Borders, Paragraph, Wrap}, 17 | }; 18 | use soft_ratatui::{EmbeddedGraphics, SoftBackend}; 19 | use std::f32::consts::PI; 20 | 21 | fn main() { 22 | App::new() 23 | .add_plugins(DefaultPlugins) 24 | .init_resource::() 25 | .add_systems(Startup, setup) 26 | .add_systems(Update, rotator_system) 27 | .add_systems(Update, computer_test) 28 | .run(); 29 | } 30 | 31 | // Marks the cube, to which the UI texture is applied. 32 | #[derive(Component)] 33 | struct Cube; 34 | #[derive(Resource)] 35 | struct MyProcGenMaterial(Handle); 36 | 37 | fn setup( 38 | mut commands: Commands, 39 | mut meshes: ResMut>, 40 | mut materials: ResMut>, 41 | mut softatui: ResMut, 42 | mut images: ResMut>, 43 | ) { 44 | softatui 45 | .draw(|frame| { 46 | let area = frame.area(); 47 | let textik = format!("Hello bevy! The window area is {}", area); 48 | frame.render_widget( 49 | Paragraph::new(textik) 50 | .block(Block::new().title("Ratatui").borders(Borders::ALL)) 51 | .white() 52 | .on_blue() 53 | .wrap(Wrap { trim: false }), 54 | area, 55 | ); 56 | }) 57 | .expect("epic fail"); 58 | 59 | let width = softatui.backend().get_pixmap_width() as u32; 60 | let height = softatui.backend().get_pixmap_height() as u32; 61 | let data = softatui.backend().get_pixmap_data_as_rgba(); 62 | 63 | let mut image = Image::new( 64 | Extent3d { 65 | width, 66 | height, 67 | depth_or_array_layers: 1, 68 | }, 69 | TextureDimension::D2, 70 | data, 71 | TextureFormat::Rgba8UnormSrgb, 72 | RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD, 73 | ); 74 | // You need to set these texture usage flags in order to use the image as a render target 75 | image.texture_descriptor.usage = 76 | TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT; 77 | 78 | let image_handle = images.add(image); 79 | 80 | // Light 81 | commands.spawn(DirectionalLight::default()); 82 | 83 | let texture_camera = commands 84 | .spawn(( 85 | Camera2d, 86 | Camera { 87 | target: RenderTarget::Image(image_handle.clone().into()), 88 | ..default() 89 | }, 90 | )) 91 | .id(); 92 | 93 | commands 94 | .spawn(( 95 | Node { 96 | // Cover the whole image 97 | width: Val::Percent(100.), 98 | height: Val::Percent(100.), 99 | flex_direction: FlexDirection::Column, 100 | justify_content: JustifyContent::Center, 101 | align_items: AlignItems::Center, 102 | ..default() 103 | }, 104 | BackgroundColor(GOLD.into()), 105 | UiTargetCamera(texture_camera), 106 | )) 107 | .with_children(|parent| { 108 | parent.spawn(ImageNode::new(image_handle.clone())); 109 | }); 110 | 111 | let cube_size = 1.0; 112 | let cube_handle = meshes.add(Cuboid::new(cube_size, cube_size, cube_size)); 113 | 114 | // This material has the texture that has been rendered. 115 | let material_handle = materials.add(StandardMaterial { 116 | base_color_texture: Some(image_handle.clone()), 117 | reflectance: 0.02, 118 | unlit: false, 119 | 120 | ..default() 121 | }); 122 | commands.insert_resource(MyProcGenMaterial(material_handle.clone())); 123 | 124 | // Cube with material containing the rendered UI texture. 125 | commands.spawn(( 126 | Mesh3d(cube_handle), 127 | MeshMaterial3d(material_handle), 128 | Transform::from_xyz(0.0, 0.0, 1.5).with_rotation(Quat::from_rotation_x(-PI / 5.0)), 129 | Cube, 130 | )); 131 | 132 | // The main pass camera. 133 | commands.spawn(( 134 | Camera3d::default(), 135 | Transform::from_xyz(0.0, 0.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y), 136 | )); 137 | } 138 | 139 | const ROTATION_SPEED: f32 = 0.5; 140 | 141 | fn rotator_system(time: Res