Expand description
§Bubbletea
A powerful TUI (Terminal User Interface) framework based on The Elm Architecture.
Bubbletea provides a functional approach to building terminal applications with:
- A simple Model-Update-View architecture
- Command-based side effects
- Type-safe messages with downcasting
- Full keyboard and mouse support
- Frame-rate limited rendering (60 FPS default)
§Role in charmed_rust
Bubbletea is the core runtime and event loop for the entire ecosystem:
- bubbles builds reusable widgets on top of the Model/Msg/Cmd pattern.
- huh composes form flows using bubbletea models.
- wish serves bubbletea programs over SSH.
- glow uses bubbletea for pager-style Markdown viewing.
- demo_showcase is the flagship multi-page bubbletea app.
§The Elm Architecture
Bubbletea follows the Elm Architecture pattern:
- Model: Your application state
- Update: A pure function that processes messages and returns commands
- View: A pure function that renders state to a string
- Cmd: Lazy IO operations that produce messages
§Quick Start
ⓘ
use bubbletea::{Program, Model, Message, Cmd, KeyMsg, KeyType};
struct Counter {
count: i32,
}
struct IncrementMsg;
struct DecrementMsg;
impl Model for Counter {
fn init(&self) -> Option<Cmd> {
None
}
fn update(&mut self, msg: Message) -> Option<Cmd> {
if msg.is::<IncrementMsg>() {
self.count += 1;
} else if msg.is::<DecrementMsg>() {
self.count -= 1;
} else if let Some(key) = msg.downcast_ref::<KeyMsg>() {
match key.key_type {
KeyType::CtrlC | KeyType::Esc => return Some(bubbletea::quit()),
KeyType::Runes if key.runes == vec!['q'] => return Some(bubbletea::quit()),
_ => {}
}
}
None
}
fn view(&self) -> String {
format!(
"Count: {}\n\nPress +/- to change, q to quit",
self.count
)
}
}
fn main() -> Result<(), bubbletea::Error> {
let model = Counter { count: 0 };
let final_model = Program::new(model)
.with_alt_screen()
.run()?;
println!("Final count: {}", final_model.count);
Ok(())
}§Messages
Messages are type-erased using Message. You can create custom message types
and downcast them in your update function:
use bubbletea::Message;
struct MyCustomMsg { value: i32 }
let msg = Message::new(MyCustomMsg { value: 42 });
// Check type
if msg.is::<MyCustomMsg>() {
// Downcast to access
if let Some(custom) = msg.downcast::<MyCustomMsg>() {
assert_eq!(custom.value, 42);
}
}§Commands
Commands are lazy IO operations that produce messages:
use bubbletea::{Cmd, Message, batch, sequence};
use std::time::Duration;
// Simple command
let cmd = Cmd::new(|| Message::new("done"));
// Batch commands (run concurrently)
let cmds = batch(vec![
Some(Cmd::new(|| Message::new(1))),
Some(Cmd::new(|| Message::new(2))),
]);
// Sequence commands (run in order)
let cmds = sequence(vec![
Some(Cmd::new(|| Message::new(1))),
Some(Cmd::new(|| Message::new(2))),
]);§Keyboard Input
Keyboard events are delivered as KeyMsg:
use bubbletea::{KeyMsg, KeyType, Message};
fn handle_key(msg: Message) {
if let Some(key) = msg.downcast_ref::<KeyMsg>() {
match key.key_type {
KeyType::Enter => println!("Enter pressed"),
KeyType::CtrlC => println!("Ctrl+C pressed"),
KeyType::Runes => println!("Typed: {:?}", key.runes),
_ => {}
}
}
}§Mouse Input
Enable mouse tracking with with_mouse_cell_motion() or with_mouse_all_motion():
ⓘ
use bubbletea::{Program, MouseMsg, MouseButton, MouseAction};
let program = Program::new(model)
.with_mouse_cell_motion() // Track clicks and drags
.run()?;
// In update:
if let Some(mouse) = msg.downcast_ref::<MouseMsg>() {
if mouse.button == MouseButton::Left && mouse.action == MouseAction::Press {
println!("Click at ({}, {})", mouse.x, mouse.y);
}
}§Screen Control
Control terminal features with screen commands:
use bubbletea::screen;
// In update, return a command:
let cmd = screen::enter_alt_screen();
let cmd = screen::hide_cursor();
let cmd = screen::enable_mouse_cell_motion();Re-exports§
pub use command::Cmd;pub use command::batch;pub use command::every;pub use command::printf;pub use command::println;pub use command::quit;pub use command::sequence;pub use command::set_window_title;pub use command::tick;pub use command::window_size;pub use key::KeyMsg;pub use key::KeyType;pub use key::parse_sequence;pub use key::parse_sequence_prefix;pub use message::BlurMsg;pub use message::FocusMsg;pub use message::InterruptMsg;pub use message::Message;pub use message::QuitMsg;pub use message::ResumeMsg;pub use message::SuspendMsg;pub use message::WindowSizeMsg;pub use mouse::MouseAction;pub use mouse::MouseButton;pub use mouse::MouseMsg;pub use mouse::parse_mouse_event_sequence;pub use program::Error;pub use program::Model;pub use program::Program;pub use program::ProgramHandle;pub use program::ProgramOptions;pub use program::Result;
Modules§
- command
- Commands for side effects.
- key
- Keyboard input handling.
- message
- Message types for the Elm Architecture.
- mouse
- Mouse input handling.
- prelude
- Prelude module for convenient imports.
- program
- Program lifecycle and event loop.
- screen
- Screen control commands.
- simulator
- Program simulator for testing lifecycle without a real terminal.