Rusty Bubbles is a complete, from-scratch Rust port of Bubbles, the library of common UI components for Bubble Tea — text inputs, text areas, spinners, stopwatches, file pickers, help views, viewports and more. It tracks upstream Go releases on a rolling basis under the family's porting policies: versions mirror upstream exactly, never ahead or behind, with a hard goal of 1:1 behavioural, visual and license parity, favouring fidelity to upstream semantics over Rust-native rewrites.
It's part of the Rusty port family of the Bubble Tea ecosystem and builds on rusty-bubbletea, rusty-lipgloss and rusty-ultraviolet.
cargo add rusty-bubblesComponents follow the same model/update/view pattern as the framework they live in. Here's a complete spinner:
use rusty_bubbles::spinner;
use rusty_bubbletea::model::Model as ModelTrait;
use rusty_bubbletea::{quit, Cmd, KeyPressMsg, Msg, Program, View};
struct Model {
spinner: spinner::Model,
}
impl ModelTrait for Model {
fn init(&self) -> Cmd {
// Kick off the spinner's tick loop.
let tm = self.spinner.tick_msg();
Some(Box::new(move || Some(Box::new(tm))))
}
fn update(&mut self, msg: &dyn Msg) -> Cmd {
if let Some(k) = msg.as_any().downcast_ref::<KeyPressMsg>() {
if k.0.to_string() == "q" {
return quit();
}
}
// Forward every other message to the component.
self.spinner.update(msg)
}
fn view(&self) -> View {
View::new(&format!("{}
Press q to quit.", self.spinner.view()))
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let program = Program::new(Model {
spinner: spinner::new(vec![spinner::with_spinner(spinner::dot())]),
});
program.run()?;
Ok(())
}The same recipe works for every component: build it, forward messages from
your update, and render it from your view. Components available include
textinput,
textarea,
list,
table,
stopwatch,
filepicker,
help and
viewport.
