bubbletea/lib.rs
1#![forbid(unsafe_code)]
2// Allow pedantic lints for API ergonomics in early development.
3#![allow(clippy::cast_possible_truncation)]
4#![allow(clippy::doc_markdown)]
5#![allow(clippy::missing_const_for_fn)]
6#![allow(clippy::must_use_candidate)]
7#![allow(clippy::nursery)]
8#![allow(clippy::pedantic)]
9
10//! # Bubbletea
11//!
12//! A powerful TUI (Terminal User Interface) framework based on The Elm Architecture.
13//!
14//! Bubbletea provides a functional approach to building terminal applications with:
15//! - A simple **Model-Update-View** architecture
16//! - **Command-based** side effects
17//! - **Type-safe messages** with downcasting
18//! - Full **keyboard and mouse** support
19//! - **Frame-rate limited** rendering (60 FPS default)
20//!
21//! ## Role in `charmed_rust`
22//!
23//! Bubbletea is the core runtime and event loop for the entire ecosystem:
24//! - **bubbles** builds reusable widgets on top of the Model/Msg/Cmd pattern.
25//! - **huh** composes form flows using bubbletea models.
26//! - **wish** serves bubbletea programs over SSH.
27//! - **glow** uses bubbletea for pager-style Markdown viewing.
28//! - **demo_showcase** is the flagship multi-page bubbletea app.
29//!
30//! ## The Elm Architecture
31//!
32//! Bubbletea follows the Elm Architecture pattern:
33//!
34//! - **Model**: Your application state
35//! - **Update**: A pure function that processes messages and returns commands
36//! - **View**: A pure function that renders state to a string
37//! - **Cmd**: Lazy IO operations that produce messages
38//!
39//! ## Quick Start
40//!
41//! ```rust,ignore
42//! use bubbletea::{Program, Model, Message, Cmd, KeyMsg, KeyType};
43//!
44//! struct Counter {
45//! count: i32,
46//! }
47//!
48//! struct IncrementMsg;
49//! struct DecrementMsg;
50//!
51//! impl Model for Counter {
52//! fn init(&self) -> Option<Cmd> {
53//! None
54//! }
55//!
56//! fn update(&mut self, msg: Message) -> Option<Cmd> {
57//! if msg.is::<IncrementMsg>() {
58//! self.count += 1;
59//! } else if msg.is::<DecrementMsg>() {
60//! self.count -= 1;
61//! } else if let Some(key) = msg.downcast_ref::<KeyMsg>() {
62//! match key.key_type {
63//! KeyType::CtrlC | KeyType::Esc => return Some(bubbletea::quit()),
64//! KeyType::Runes if key.runes == vec!['q'] => return Some(bubbletea::quit()),
65//! _ => {}
66//! }
67//! }
68//! None
69//! }
70//!
71//! fn view(&self) -> String {
72//! format!(
73//! "Count: {}\n\nPress +/- to change, q to quit",
74//! self.count
75//! )
76//! }
77//! }
78//!
79//! fn main() -> Result<(), bubbletea::Error> {
80//! let model = Counter { count: 0 };
81//! let final_model = Program::new(model)
82//! .with_alt_screen()
83//! .run()?;
84//! println!("Final count: {}", final_model.count);
85//! Ok(())
86//! }
87//! ```
88//!
89//! ## Messages
90//!
91//! Messages are type-erased using [`Message`]. You can create custom message types
92//! and downcast them in your update function:
93//!
94//! ```rust
95//! use bubbletea::Message;
96//!
97//! struct MyCustomMsg { value: i32 }
98//!
99//! let msg = Message::new(MyCustomMsg { value: 42 });
100//!
101//! // Check type
102//! if msg.is::<MyCustomMsg>() {
103//! // Downcast to access
104//! if let Some(custom) = msg.downcast::<MyCustomMsg>() {
105//! assert_eq!(custom.value, 42);
106//! }
107//! }
108//! ```
109//!
110//! ## Commands
111//!
112//! Commands are lazy IO operations that produce messages:
113//!
114//! ```rust
115//! use bubbletea::{Cmd, Message, batch, sequence};
116//! use std::time::Duration;
117//!
118//! // Simple command
119//! let cmd = Cmd::new(|| Message::new("done"));
120//!
121//! // Batch commands (run concurrently)
122//! let cmds = batch(vec![
123//! Some(Cmd::new(|| Message::new(1))),
124//! Some(Cmd::new(|| Message::new(2))),
125//! ]);
126//!
127//! // Sequence commands (run in order)
128//! let cmds = sequence(vec![
129//! Some(Cmd::new(|| Message::new(1))),
130//! Some(Cmd::new(|| Message::new(2))),
131//! ]);
132//! ```
133//!
134//! ## Keyboard Input
135//!
136//! Keyboard events are delivered as [`KeyMsg`]:
137//!
138//! ```rust
139//! use bubbletea::{KeyMsg, KeyType, Message};
140//!
141//! fn handle_key(msg: Message) {
142//! if let Some(key) = msg.downcast_ref::<KeyMsg>() {
143//! match key.key_type {
144//! KeyType::Enter => println!("Enter pressed"),
145//! KeyType::CtrlC => println!("Ctrl+C pressed"),
146//! KeyType::Runes => println!("Typed: {:?}", key.runes),
147//! _ => {}
148//! }
149//! }
150//! }
151//! ```
152//!
153//! ## Mouse Input
154//!
155//! Enable mouse tracking with `with_mouse_cell_motion()` or `with_mouse_all_motion()`:
156//!
157//! ```rust,ignore
158//! use bubbletea::{Program, MouseMsg, MouseButton, MouseAction};
159//!
160//! let program = Program::new(model)
161//! .with_mouse_cell_motion() // Track clicks and drags
162//! .run()?;
163//!
164//! // In update:
165//! if let Some(mouse) = msg.downcast_ref::<MouseMsg>() {
166//! if mouse.button == MouseButton::Left && mouse.action == MouseAction::Press {
167//! println!("Click at ({}, {})", mouse.x, mouse.y);
168//! }
169//! }
170//! ```
171//!
172//! ## Screen Control
173//!
174//! Control terminal features with screen commands:
175//!
176//! ```rust
177//! use bubbletea::screen;
178//!
179//! // In update, return a command:
180//! let cmd = screen::enter_alt_screen();
181//! let cmd = screen::hide_cursor();
182//! let cmd = screen::enable_mouse_cell_motion();
183//! ```
184
185pub mod command;
186pub mod key;
187pub mod message;
188pub mod mouse;
189pub mod program;
190pub mod screen;
191pub mod simulator;
192
193// Re-exports
194pub use command::{
195 Cmd, batch, every, printf, println, quit, sequence, set_window_title, tick, window_size,
196};
197
198#[cfg(feature = "async")]
199pub use command::{AsyncCmd, every_async, tick_async};
200pub use key::{KeyMsg, KeyType, parse_sequence, parse_sequence_prefix};
201pub use message::{
202 BlurMsg, FocusMsg, InterruptMsg, Message, QuitMsg, ResumeMsg, SuspendMsg, WindowSizeMsg,
203};
204pub use mouse::{MouseAction, MouseButton, MouseMsg, parse_mouse_event_sequence};
205pub use program::{Error, Model, Program, ProgramHandle, ProgramOptions, Result};
206
207// Re-export derive macro when macros feature is enabled.
208// Derive macros and traits live in different namespaces, so both can be named `Model`.
209// Users can write `#[derive(bubbletea::Model)]` for the macro and `impl bubbletea::Model` for the trait.
210#[cfg(feature = "macros")]
211#[doc(hidden)]
212pub use bubbletea_macros::*;
213
214/// Prelude module for convenient imports.
215pub mod prelude {
216 pub use crate::command::{Cmd, batch, every, printf, println, quit, sequence, tick};
217 pub use crate::key::{KeyMsg, KeyType};
218 pub use crate::message::{Message, QuitMsg, WindowSizeMsg};
219 pub use crate::mouse::{MouseAction, MouseButton, MouseMsg};
220 pub use crate::program::{Model, Program};
221
222 #[cfg(feature = "async")]
223 pub use crate::command::{AsyncCmd, every_async, tick_async};
224}