Crates.io | term_render |
lib.rs | term_render |
version | 0.1.3-alpha.1 |
created_at | 2025-09-22 05:10:46.122543+00 |
updated_at | 2025-09-25 18:03:48.923745+00 |
description | A terminal UI library for building rich terminal applications in Rust. |
homepage | |
repository | https://github.com/AndrewDMorgan/TermRender |
max_upload_size | |
id | 1849512 |
size | 243,804 |
A high-performance, async-ready terminal UI library for Rust, built with flexibility and performance in mind. TermRender provides a modern widget system, efficient rendering, and comprehensive input handling for creating rich terminal applications.
High Performance: Optimized rendering with dirty-rectangle tracking and efficient escape sequence handling
Flexible Widget System: Build custom UI components with a trait-based approach
Comprehensive Input: Full keyboard and mouse support with modifier keys
Rich Text Support: ANSI colors, 24-bit RGB, and text styling
Async Ready: Built on Tokio for modern async/await workflows
Dynamic Layouts: Responsive layouts that adapt to terminal resizing
Extensible: Easy to create custom widgets and renderers
Add TermRender to your Cargo.toml:
toml
[dependencies]
term_render = "0.1.2"
tokio = { version = "1.47.1", features = ["full"] }
use term_render::{self, event_handler::KeyCode};
use term_render::widget_impls::WidgetBuilder;
// this acts as the callback that is called every frame
// this is the entry point and any logic needs to branch out from here
fn app_callback(app: &mut term_render::App<AppData>, data: &mut AppData) -> Result<bool, ()> {
// place app logic here
if app.events.read().contains_key_code(KeyCode::Return) {
return Ok(true);
}
if data.time.elapsed().as_secs_f64() > 15.0 {
return Ok(true);
}
Ok(false) // return true to exit the app
}
// defining the application data structure
// this can contain any data you want to use in the app callback
struct AppData {
pub time: std::time::Instant,
}
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
async fn main() -> tokio::io::Result<()> {
// creating an instance of the app
let mut app = term_render::App::new()?;
let data = AppData {
time: std::time::Instant::now(),
};
// creating a new scene to attach widgets to
let mut scene = term_render::widget::Scene::new();
// creating a widget using the builder trait.
let (widget, window) =
term_render::widget_impls::StaticWidgetBuilder::<AppData>::builder(String::from("name"))
.with_border(true)
.with_renderer(Box::new(|_size, _position| {
Some(vec![])
}))
.with_position((10, 10))
.with_size((50, 10))
.build(&app.area.read()).unwrap();
scene.add_widget(widget, window, &mut *app.renderer.write()).unwrap();
app.scene = Some(scene);
// running the application with the callback, and provided data for state tracking.
app.run(data, |data, app_instance| {
app_callback(app_instance, data)
}).await.unwrap();
Ok(())
}
The basis of a TermRender application is the App
struct, which manages the terminal state, event handling, and rendering. You create an instance of App
, set up your UI components (widgets), and then call run
with a callback function that contains your application logic.
The callback function is called every frame, allowing you to update your application state and respond to events.
The AppData
struct is a user-defined structure that holds any state you want to maintain across frames. In this example, it tracks the elapsed time since the application started.
The App
instance optionally can have a Scene
, which is a container for widgets. You can create widgets using the provided builders or implement your own by adhering to the Widget
trait.
When a name/string identifier is requested for a widget, it should be unique to that scene, as the backend renderer references Window
's (similar to a Widget
, but lower level) not by the widget instance itself, but rather that string
TermRender provides a flexible widget system:
StaticWidget: Basic widget with custom rendering logic
DynamicWidget: An interactable widget. In the future, more advanced versions will be implemented which will default as buttons or other such higher-level widgets.
ButtonWidget: A simple button with automatic click handling.
More coming soon!
Creating Custom Widgets Implement the Widget trait to create custom components:
use term_render::{Widget, SendSync, event_handler::KeyParser};
// Where type C is the user defined app data state
struct MyCustomWidget<C> {
// Your widget state
}
impl<C> Widget<C> for MyCustomWidget<C> {
fn get_window_ref(&self) -> String {
"my_custom_widget".to_string() // note: each widget instance requires a unique identifyer
}
fn update_with_events(&mut self, events: &SendSync<KeyParser>) {
// Handle events
}
fn update_render(&mut self, window: &mut Window, area: &Rect) {
// Update rendering
}
// ... implement other required methods
}
TermRender includes several optimizations:
Dirty Rectangle Rendering: Only updates changed portions of the screen
Efficient Event Handling: Minimal overhead input processing
Smart Caching: Redundant render calls are avoided
Async Rendering: Non-blocking UI updates
Check out the examples directory for complete implementations (more coming soon):
Full API documentation is available:
https://docs.rs/term_render/latest/term_render/
Contributions are welcome! Please feel free to submit pull requests, open issues, or suggest new features.
Fork the repository
Create your feature branch (git checkout -b feature/amazing-feature)
Commit your changes (git commit -m 'Add amazing feature')
Push to the branch (git push origin feature/amazing-feature)
Open a Pull Request
This project is licensed under:
MIT license (LICENSE-MIT)
Inspired by Ratatui
Built with performance and developer experience in mind
Thanks to all contributors and the Rust community
TermRender - Build beautiful terminal applications with Rust