<p align="center">
<a href="rusty-bubbles.png"><img src="rusty-bubbles.png" width="313" alt="Rusty Bubbles"></a><br>
<a href="https://crates.io/crates/rusty-bubbles"><img src="https://img.shields.io/crates/v/rusty-bubbles.svg" alt="crates.io"></a>
<a href="https://github.com/coderbants/rusty-bubbles/actions"><img src="https://github.com/coderbants/rusty-bubbles/actions/workflows/ci.yml/badge.svg" alt="Build Status"></a>
<a href="coverage.svg"><img src="coverage.svg" alt="coverage"></a>
</p>
# Rusty Bubbles (`rusty-bubbles`)
**Rusty Bubbles** is a complete, from-scratch Rust port of [Bubbles](https://github.com/charmbracelet/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](./POLICIES.md): 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](https://github.com/coderbants/rusty-bubbletea), [rusty-lipgloss](https://github.com/coderbants/rusty-lipgloss) and [rusty-ultraviolet](https://github.com/coderbants/rusty-ultraviolet).
## Installation
```sh
cargo add rusty-bubbles
```
## Usage
Components follow the same [model/update/view](https://github.com/coderbants/rusty-bubbletea)
pattern as the framework they live in. Here's a complete spinner:
```rust
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](https://github.com/coderbants/rusty-bubbles/tree/dev/src/textinput.rs),
[textarea](https://github.com/coderbants/rusty-bubbles/tree/dev/src/textarea.rs),
[list](https://github.com/coderbants/rusty-bubbles/tree/dev/src/list.rs),
[table](https://github.com/coderbants/rusty-bubbles/tree/dev/src/table.rs),
[stopwatch](https://github.com/coderbants/rusty-bubbles/tree/dev/src/stopwatch.rs),
[filepicker](https://github.com/coderbants/rusty-bubbles/tree/dev/src/filepicker.rs),
[help](https://github.com/coderbants/rusty-bubbles/tree/dev/src/help.rs) and
[viewport](https://github.com/coderbants/rusty-bubbles/tree/dev/src/viewport.rs).
## License
[MIT](LICENSE)