docs: add examples for handling state (#1849)

Added comprehensive state management examples covering both immutable
and mutable patterns and documentation to help developers choose the
right approach for their applications.
This commit is contained in:
Josh McKinney 2025-06-26 13:04:42 -07:00 committed by GitHub
parent 92bb9b2219
commit cfb65e64ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1168 additions and 1 deletions

9
Cargo.lock generated
View File

@ -2636,6 +2636,15 @@ dependencies = [
"trybuild",
]
[[package]]
name = "ratatui-state-examples"
version = "0.0.0"
dependencies = [
"color-eyre",
"crossterm",
"ratatui",
]
[[package]]
name = "ratatui-termion"
version = "0.1.0-alpha.4"

View File

@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["ratatui", "ratatui-*", "xtask", "examples/apps/*"]
members = ["ratatui", "ratatui-*", "xtask", "examples/apps/*", "examples/concepts/*"]
default-members = [
"ratatui",
"ratatui-core",
@ -11,6 +11,7 @@ default-members = [
"ratatui-termwiz",
"ratatui-widgets",
"examples/apps/*",
"examples/concepts/*",
]
[workspace.package]

View File

@ -0,0 +1,11 @@
[package]
name = "ratatui-state-examples"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre = "0.6"
crossterm = "0.29"
ratatui = { workspace = true, features = ["unstable-widget-ref"] }

View File

@ -0,0 +1,179 @@
# Ratatui State Management Examples
This collection demonstrates various patterns for handling both mutable and immutable state in
Ratatui applications. Each example solves a counter problem - incrementing a counter value - but
uses different architectural approaches. These patterns represent common solutions to state
management challenges you'll encounter when building TUI applications.
For more information about widgets in Ratatui, see the [widgets module documentation](https://docs.rs/ratatui/latest/ratatui/widgets/index.html).
## When to Use Each Pattern
Choose the pattern that best fits your application's architecture and complexity:
- **Simple applications**: Use `render-function` or `mutable-widget` patterns
- **Clean separation**: Consider `stateful-widget` or `component-trait` patterns
- **Complex hierarchies**: Use `nested-*` patterns for parent-child relationships
- **Shared state**: Use `refcell` when multiple widgets need access to the same state
- **Advanced scenarios**: Use `widget-with-mutable-ref` when you understand Rust lifetimes well
## Running the Examples
To run any example, use:
```bash
cargo run --bin example-name
```
Press any key (or resize the terminal) to increment the counter. Press `<Esc>` or `q` to exit.
## Examples
### Immutable State Patterns
These patterns keep widget state immutable during rendering, with state updates happening outside
the render cycle. They're generally easier to reason about and less prone to borrowing issues.
#### [`immutable-function.rs`] - Function-Based Immutable State
**Best for**: Simple applications with pure rendering functions
**Pros**: Pure functions, easy to test, clear separation of concerns
**Cons**: Verbose parameter passing, limited integration with Ratatui ecosystem
Uses standalone functions that take immutable references to state. State updates happen in the
application loop outside of rendering.
#### [`immutable-shared-ref.rs`] - Shared Reference Pattern (Recommended)
**Best for**: Most modern Ratatui applications
**Pros**: Reusable widgets, efficient, integrates with Ratatui ecosystem, modern best practice
**Cons**: Requires external state management for dynamic behavior
Implements [`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html) for
`&T`, allowing widgets to be rendered multiple times by reference without being consumed.
#### [`immutable-consuming.rs`] - Consuming Widget Pattern
**Best for**: Compatibility with older code, simple widgets created fresh each frame
**Pros**: Simple implementation, widely compatible, familiar pattern
**Cons**: Widget consumed on each render, requires reconstruction for reuse
Implements [`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html) directly
on the owned type, consuming the widget when rendered.
### Mutable State Patterns
These patterns allow widgets to modify their state during rendering, useful for widgets that need
to update state as part of their rendering behavior.
#### [`mutable-function.rs`] - Function-Based Mutable State
**Best for**: Simple applications with minimal mutable state
**Pros**: Easy to understand, no traits to implement, direct control
**Cons**: State gets passed around as function parameters, harder to organize as complexity grows
Uses simple functions that accept mutable state references. State is managed at the application
level and passed down to render functions.
#### [`mutable-widget.rs`] - Mutable Widget Pattern
**Best for**: Self-contained widgets with their own mutable state
**Pros**: Encapsulates state within the widget, familiar OOP-style approach
**Cons**: Requires `&mut` references, can be challenging with complex borrowing scenarios
Implements [`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html) for
`&mut T`, allowing the widget to mutate its own state during rendering.
### Intermediate Patterns
#### [`stateful-widget.rs`] - Stateful Widget Pattern
**Best for**: Clean separation of widget logic from state
**Pros**: Separates widget logic from state, reusable, idiomatic Ratatui pattern
**Cons**: State must be managed externally
Uses [`StatefulWidget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.StatefulWidget.html)
to keep rendering logic separate from state management.
#### [`component-trait.rs`] - Custom Component Trait
**Best for**: Implementing consistent behavior across multiple widget types
**Pros**: Flexible, allows custom render signatures, good for widget frameworks
**Cons**: Non-standard, requires users to learn your custom API
Creates a custom trait similar to [`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html)
but with a `&mut self` render method for direct mutation.
### Advanced Patterns
#### [`nested-mutable-widget.rs`] - Nested Mutable Widgets
**Best for**: Parent-child widget relationships with mutable state
**Pros**: Hierarchical organization, each widget manages its own state
**Cons**: Complex borrowing, requires careful lifetime management
Demonstrates how to nest widgets that both need mutable access to their state.
#### [`nested-stateful-widget.rs`] - Nested Stateful Widgets
**Best for**: Complex applications with hierarchical state management
**Pros**: Clean separation, composable, scales well with application complexity
**Cons**: More boilerplate, requires understanding of nested state patterns
Shows how to compose multiple [`StatefulWidget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.StatefulWidget.html)s
in a parent-child hierarchy.
#### [`refcell.rs`] - Interior Mutability Pattern
**Best for**: Shared state across multiple widgets, complex state sharing scenarios
**Pros**: Allows shared mutable access, works with immutable widget references
**Cons**: Runtime borrow checking, potential panics, harder to debug
Uses [`Rc<RefCell<T>>`](https://doc.rust-lang.org/std/rc/struct.Rc.html) for interior mutability
when multiple widgets need access to the same state.
#### [`widget-with-mutable-ref.rs`] - Lifetime-Based Mutable References
**Best for**: Advanced users who need precise control over state lifetime
**Pros**: Zero-cost abstraction, explicit lifetime management
**Cons**: Complex lifetimes, requires deep Rust knowledge, easy to get wrong
Stores mutable references directly in widget structs using explicit lifetimes.
## Choosing the Right Pattern
**For most applications, start with immutable patterns:**
1. **Simple apps**: Use `immutable-function` for basic rendering with external state management
2. **Modern Ratatui**: Use `immutable-shared-ref` for reusable, efficient widgets (recommended)
3. **Legacy compatibility**: Use `immutable-consuming` when working with older code patterns
**Use mutable patterns when widgets need to update state during rendering:**
1. **Simple mutable state**: Begin with `mutable-function` or `mutable-widget` for prototypes
2. **Clean separation**: Use `stateful-widget` when you want to separate widget logic from state
3. **Hierarchical widgets**: Use `nested-*` patterns for complex widget relationships
4. **Shared state**: Use `refcell` when multiple widgets need the same state
5. **Performance critical**: Consider `widget-with-mutable-ref` for advanced lifetime management
## Common Pitfalls
- **Borrowing issues**: The borrow checker can be challenging with mutable state.
[`StatefulWidget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.StatefulWidget.html)
often provides the cleanest solution.
- **Overengineering**: Don't use complex patterns like `refcell` or `widget-with-mutable-ref`
unless you actually need them.
- **State organization**: Keep state close to where it's used. Don't pass state through many
layers unnecessarily.
[`component-trait.rs`]: ./src/bin/component-trait.rs
[`immutable-consuming.rs`]: ./src/bin/immutable-consuming.rs
[`immutable-function.rs`]: ./src/bin/immutable-function.rs
[`immutable-shared-ref.rs`]: ./src/bin/immutable-shared-ref.rs
[`mutable-widget.rs`]: ./src/bin/mutable-widget.rs
[`nested-mutable-widget.rs`]: ./src/bin/nested-mutable-widget.rs
[`nested-stateful-widget.rs`]: ./src/bin/nested-stateful-widget.rs
[`refcell.rs`]: ./src/bin/refcell.rs
[`mutable-function.rs`]: ./src/bin/mutable-function.rs
[`stateful-widget.rs`]: ./src/bin/stateful-widget.rs
[`widget-with-mutable-ref.rs`]: ./src/bin/widget-with-mutable-ref.rs

View File

@ -0,0 +1,83 @@
//! # Custom Component Trait Pattern
//!
//! This example demonstrates using a custom trait instead of the standard `Widget` trait for
//! handling mutable state during rendering. This pattern is useful when you want to implement
//! consistent behavior across multiple widget types without implementing the `Widget` trait for
//! each one.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You're building a widget framework or library with custom behavior
//! - You want a consistent API across multiple widget types
//! - You need more control over the render method signature
//! - You're prototyping widget behavior before standardizing on `Widget` or `StatefulWidget`
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Flexible - you can define custom method signatures
//! - Consistent - enforces the same behavior across widget types
//! - Simple - no need to understand `StatefulWidget` complexity
//!
//! **Cons:**
//! - Non-standard - users must learn your custom API instead of Ratatui's standard traits
//! - Less discoverable - doesn't integrate with Ratatui's widget ecosystem
//! - Limited reuse - can't be used with existing Ratatui functions expecting `Widget`
//!
//! ## Example Usage
//!
//! The custom `Component` trait allows widgets to mutate their state directly during rendering
//! by taking `&mut self` instead of `self`. This is similar to the mutable widget pattern but
//! with a custom trait interface.
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the custom component trait pattern for mutable state management.
///
/// Creates a counter widget using a custom `Component` trait and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = Counter::default();
loop {
terminal.draw(|frame| counter.render(frame, frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A custom trait for components that can render themselves while mutating their state.
///
/// This trait provides an alternative to the standard `Widget` trait by allowing components to
/// take `&mut self`, enabling direct state mutation during rendering.
trait Component {
/// Render the component to the given area of the frame.
fn render(&mut self, frame: &mut Frame, area: Rect);
}
/// A simple counter component that increments its value each time it's rendered.
///
/// Demonstrates how the custom `Component` trait allows widgets to maintain and mutate
/// their own state during the rendering process.
#[derive(Default)]
struct Counter {
count: usize,
}
impl Component for Counter {
fn render(&mut self, frame: &mut Frame, area: Rect) {
self.count += 1;
frame.render_widget(format!("Counter: {count}", count = self.count), area);
}
}

View File

@ -0,0 +1,86 @@
//! # Consuming Widget Pattern with Immutable State
//!
//! This example demonstrates implementing the `Widget` trait directly on the widget type,
//! causing it to be consumed when rendered. This was the original pattern in Ratatui and
//! is still commonly used, especially for simple widgets that are created fresh each frame.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You're working with existing code that uses this pattern
//! - Your widgets are simple and created fresh each frame
//! - You want maximum compatibility with older Ratatui code
//! - You don't need to reuse widget instances
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Simple - straightforward implementation
//! - Compatible - works with all Ratatui versions
//! - Familiar - widely used pattern in existing code
//! - No borrowing - no need to manage references
//!
//! **Cons:**
//! - Consuming - widget is destroyed after each render
//! - Inefficient - requires reconstruction for repeated use
//! - Limited reuse - cannot store and reuse widget instances
//!
//! ## Example Usage
//!
//! The widget implements `Widget` directly on the owned type, meaning it's consumed
//! when rendered and must be recreated for subsequent renders.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the consuming widget pattern for immutable state rendering.
///
/// Creates a new counter widget instance each frame, showing how the consuming
/// pattern works with immutable state that's managed externally.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut count = 0;
loop {
terminal.draw(|frame| {
// Widget is created fresh each time and consumed when rendered
let counter = Counter::new(count);
frame.render_widget(counter, frame.area());
})?;
// State updates happen outside of widget lifecycle
count += 1;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A simple counter widget that displays a count value.
///
/// Implements `Widget` directly on the owned type, meaning the widget is consumed
/// when rendered. The count state is managed externally and passed in during construction.
struct Counter {
count: usize,
}
impl Counter {
/// Create a new counter widget with the given count.
fn new(count: usize) -> Self {
Self { count }
}
}
impl Widget for Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
// Widget is consumed here - self is moved, not borrowed
format!("Counter: {}", self.count).render(area, buf);
}
}

View File

@ -0,0 +1,82 @@
//! # Function-Based Pattern with Immutable State
//!
//! This example demonstrates using standalone functions for rendering widgets with immutable
//! state. This pattern keeps state management completely separate from widget rendering logic,
//! making it easy to test and reason about.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You prefer functional programming approaches
//! - Your rendering logic is simple and doesn't need complex widget hierarchies
//! - You want clear separation between state and rendering
//! - You're building simple UIs or prototyping quickly
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Simple - easy to understand and test
//! - Pure functions - no side effects in rendering
//! - Flexible - can easily compose multiple render functions
//! - Clear separation - state management is completely separate from rendering
//!
//! **Cons:**
//! - Limited - doesn't integrate with Ratatui's widget ecosystem
//! - Verbose - requires passing state explicitly to every function
//! - No reuse - can't be used with existing Ratatui widget infrastructure
//!
//! ## Example Usage
//!
//! The function takes immutable references to both the frame and state, ensuring that
//! rendering is a pure operation that doesn't modify state.
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the function-based pattern for immutable state rendering.
///
/// Creates a counter state and renders it using a pure function, incrementing the counter
/// in the application loop rather than during rendering.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter_state = Counter::default();
loop {
terminal.draw(|frame| render_counter(frame, frame.area(), &counter_state))?;
// State updates happen outside of rendering
counter_state.increment();
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// State for the counter.
///
/// This state is managed externally and passed to render functions as an immutable reference.
#[derive(Default)]
struct Counter {
count: usize,
}
impl Counter {
/// Increment the counter value.
fn increment(&mut self) {
self.count += 1;
}
}
/// Pure render function that displays the counter state.
///
/// Takes immutable references to ensure rendering has no side effects on state.
/// This function can be easily tested and composed with other render functions.
fn render_counter(frame: &mut Frame, area: Rect, state: &Counter) {
frame.render_widget(format!("Counter: {}", state.count), area);
}

View File

@ -0,0 +1,92 @@
//! # Shared Reference Pattern with Immutable State
//!
//! This example demonstrates implementing the `Widget` trait on a shared reference (`&Widget`)
//! with immutable state. This is the recommended pattern for most widgets in modern Ratatui
//! applications, as it allows widgets to be reused without being consumed.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You want to reuse widgets across multiple renders
//! - Your widget doesn't need to modify its state during rendering
//! - You want the benefits of Ratatui's widget ecosystem
//! - You're building modern, efficient Ratatui applications
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Reusable - widget can be rendered multiple times without reconstruction
//! - Efficient - no cloning or reconstruction needed
//! - Standard - integrates with Ratatui's widget ecosystem
//! - Modern - follows current Ratatui best practices
//!
//! **Cons:**
//! - Immutable - cannot modify widget state during rendering
//! - External state - requires external state management for dynamic behavior
//!
//! ## Example Usage
//!
//! The widget implements `Widget for &Counter`, allowing it to be rendered by reference
//! while keeping its internal data immutable during rendering.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the shared reference pattern for immutable widget rendering.
///
/// Creates a counter widget that can be rendered multiple times by reference,
/// with state updates happening outside the widget's render method.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = Counter::new();
loop {
terminal.draw(|frame| {
// Widget is rendered by reference, can be reused
frame.render_widget(&counter, frame.area());
})?;
// State updates happen outside of rendering
counter.increment();
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget with immutable rendering behavior.
///
/// Implements `Widget` on a shared reference, allowing the widget to be rendered
/// multiple times without being consumed while keeping its data immutable during rendering.
struct Counter {
count: usize,
}
impl Counter {
/// Create a new counter.
fn new() -> Self {
Self { count: 0 }
}
/// Increment the counter value.
///
/// This method modifies the counter's state outside of the rendering process,
/// maintaining the separation between state updates and rendering.
fn increment(&mut self) {
self.count += 1;
}
}
impl Widget for &Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
// Rendering is immutable - no state changes occur here
format!("Counter: {}", self.count).render(area, buf);
}
}

View File

@ -0,0 +1,67 @@
//! # Render Function Pattern
//!
//! This example demonstrates the simplest approach to handling mutable state - using regular
//! functions that accept mutable state references. This pattern works well for simple applications
//! and prototypes where you don't need the complexity of widget traits.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - Simple applications with minimal state management needs
//! - Prototypes and quick experiments
//! - When you prefer functional programming over object-oriented approaches
//! - Applications where state is naturally managed at the top level
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Extremely simple - no traits to implement or understand
//! - Direct and explicit - state flow is obvious
//! - Flexible - easy to modify without interface constraints
//! - Beginner-friendly - uses basic Rust concepts
//!
//! **Cons:**
//! - State must be passed through function parameters
//! - Harder to organize as application complexity grows
//! - No encapsulation - state management is scattered
//! - Less reusable than widget-based approaches
//! - Can lead to parameter passing through many layers
//!
//! ## Example Usage
//!
//! State is managed at the application level and passed to render functions as needed.
//! This approach works well when state is simple and doesn't need complex organization.
use ratatui::Frame;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the render function pattern for mutable state management.
///
/// Creates a counter using simple functions and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = 0;
loop {
terminal.draw(|frame| render(frame, &mut counter))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// Renders a counter using a simple function-based approach.
///
/// Demonstrates the functional approach to state management where state is managed externally
/// and passed in as a parameter.
fn render(frame: &mut Frame, counter: &mut usize) {
*counter += 1;
frame.render_widget(format!("Counter: {counter}"), frame.area());
}

View File

@ -0,0 +1,74 @@
//! # Mutable Widget Pattern
//!
//! This example demonstrates implementing the `Widget` trait on a mutable reference (`&mut T`)
//! to allow direct state mutation during rendering. This is one of the simplest approaches for
//! widgets that need to maintain their own state.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You have self-contained widgets with their own state
//! - You prefer an object-oriented approach to widget design
//! - Your widget's state is simple and doesn't need complex sharing
//! - You want to encapsulate state within the widget itself
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Simple and intuitive - state is encapsulated within the widget
//! - Familiar pattern for developers coming from OOP backgrounds
//! - Direct state access without external state management
//! - Works well with Rust's ownership system for simple cases
//!
//! **Cons:**
//! - Can lead to borrowing challenges in complex scenarios
//! - Requires mutable access to the widget, which may not always be available
//! - Less flexible than `StatefulWidget` for shared or complex state patterns
//! - May require careful lifetime management in nested scenarios
//!
//! ## Example Usage
//!
//! The widget implements `Widget` for `&mut Self`, allowing it to mutate its internal state
//! during the render call. Each render increments a counter, demonstrating state mutation.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the mutable widget pattern for mutable state management.
///
/// Creates a counter widget using `Widget` for `&mut Self` and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = Counter::default();
loop {
terminal.draw(|frame| frame.render_widget(&mut counter, frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget that maintains its own state and increments on each render.
///
/// Demonstrates the mutable widget pattern by implementing `Widget` for `&mut Self`.
#[derive(Default)]
struct Counter {
counter: usize,
}
impl Widget for &mut Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
self.counter += 1;
format!("Counter: {counter}", counter = self.counter).render(area, buf);
}
}

View File

@ -0,0 +1,96 @@
//! # Nested Mutable Widget Pattern
//!
//! This example demonstrates nesting widgets that both need mutable access to their state.
//! This pattern is useful when you have a parent-child widget relationship where both widgets
//! need to maintain and mutate their own state during rendering.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You have hierarchical widget relationships (parent-child)
//! - Each widget needs to maintain its own distinct state
//! - You prefer the mutable widget pattern over StatefulWidget
//! - Widgets have clear ownership of their state
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Clear hierarchical organization
//! - Each widget encapsulates its own state
//! - Intuitive parent-child relationships
//! - State ownership is explicit
//!
//! **Cons:**
//! - Complex borrowing scenarios can arise
//! - Requires careful lifetime management
//! - May lead to borrow checker issues in complex hierarchies
//! - Less flexible than StatefulWidget for state sharing
//!
//! ## Example Usage
//!
//! The parent `App` widget contains a child `Counter` widget. Both implement `Widget` for
//! `&mut Self`, allowing them to mutate their respective states during rendering. The parent
//! delegates rendering to the child while maintaining its own state structure.
use ratatui::DefaultTerminal;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the nested mutable widget pattern for mutable state management.
///
/// Creates a parent-child widget hierarchy using mutable widgets and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
let app = App::default();
ratatui::run(|terminal| app.run(terminal))
}
/// The main application widget that contains and manages child widgets.
///
/// Demonstrates the parent widget in a nested mutable widget hierarchy.
#[derive(Default)]
struct App {
counter: Counter,
}
impl App {
/// Run the application with the given terminal.
fn run(mut self, terminal: &mut DefaultTerminal) -> color_eyre::Result<()> {
loop {
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
}
}
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
self.counter.render(area, buf);
}
}
/// A counter widget that maintains its own state within a nested hierarchy.
///
/// Can be used standalone or as a child within other widgets, demonstrating
/// how mutable widgets can be composed together.
#[derive(Default)]
struct Counter {
count: usize,
}
impl Widget for &mut Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
self.count += 1;
format!("Counter: {count}", count = self.count).render(area, buf);
}
}

View File

@ -0,0 +1,103 @@
//! # Nested StatefulWidget Pattern
//!
//! This example demonstrates composing multiple `StatefulWidget`s in a parent-child hierarchy.
//! This pattern is ideal for complex applications where you need clean separation of concerns
//! and want to leverage the benefits of the StatefulWidget pattern at multiple levels.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - Complex applications with hierarchical state management needs
//! - When you want clean separation between widgets and their state
//! - Building composable widget systems
//! - Applications that need testable, reusable widget components
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Excellent separation of concerns
//! - Highly composable and reusable widgets
//! - Easy to test individual widgets and their state
//! - Scales well with application complexity
//! - Follows idiomatic Ratatui patterns
//!
//! **Cons:**
//! - More boilerplate code than simpler patterns
//! - Requires understanding of nested state management
//! - State structures can become complex
//! - May be overkill for simple applications
//!
//! ## Example Usage
//!
//! The parent `App` widget manages application-level state while delegating specific
//! functionality to child widgets like `Counter`. Each widget is responsible for its own
//! state type and rendering logic, making the system highly modular.
use ratatui::DefaultTerminal;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::{StatefulWidget, Widget};
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the nested StatefulWidget pattern for mutable state management.
///
/// Creates a parent-child widget hierarchy using StatefulWidgets and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(App::run)
}
/// The main application widget using the StatefulWidget pattern.
///
/// Demonstrates how to compose multiple StatefulWidgets together while coordinating
/// between different child widgets.
struct App;
impl App {
/// Run the application with the given terminal.
fn run(terminal: &mut DefaultTerminal) -> color_eyre::Result<()> {
let mut state = AppState { counter: 0 };
loop {
terminal.draw(|frame| frame.render_stateful_widget(App, frame.area(), &mut state))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
}
}
/// Application state that contains all the state needed by the app and its child widgets.
///
/// Demonstrates how to organize hierarchical state in the StatefulWidget pattern.
struct AppState {
counter: usize,
}
impl StatefulWidget for App {
type State = AppState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Counter.render(area, buf, &mut state.counter);
}
}
/// A counter widget that uses StatefulWidget for clean state separation.
///
/// Focuses purely on rendering logic and can be reused with different state instances.
struct Counter;
impl StatefulWidget for Counter {
type State = usize;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
*state += 1;
format!("Counter: {state}").render(area, buf);
}
}

View File

@ -0,0 +1,86 @@
//! # Interior Mutability Pattern (RefCell)
//!
//! This example demonstrates using `Rc<RefCell<T>>` for interior mutability, allowing multiple
//! widgets to share and mutate the same state. This pattern is useful when you need shared
//! mutable state but can't use mutable references due to borrowing constraints.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - Multiple widgets need to access and modify the same state
//! - You can't use mutable references due to borrowing constraints
//! - You need shared ownership of mutable data
//! - Complex widget hierarchies where state needs to be accessed from multiple locations
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Allows shared mutable access to state
//! - Works with immutable widget references
//! - Enables complex state sharing patterns
//! - Can be cloned cheaply (reference counting)
//!
//! **Cons:**
//! - Runtime borrow checking - potential for panics if you violate borrowing rules
//! - Less efficient than compile-time borrow checking
//! - Harder to debug when borrow violations occur
//! - More complex than simpler state management patterns
//! - Can lead to subtle bugs if not used carefully
//!
//! ## Important Safety Notes
//!
//! - Only one mutable borrow can exist at a time
//! - Violating this rule will cause a panic at runtime
//! - Always minimize the scope of borrows to avoid conflicts
//!
//! ## Example Usage
//!
//! The widget wraps its state in `Rc<RefCell<T>>`, allowing the state to be shared and mutated
//! even when the widget itself is used by value (as required by the `Widget` trait).
use std::cell::RefCell;
use std::ops::AddAssign;
use std::rc::Rc;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the interior mutability pattern for mutable state management.
///
/// Creates a counter widget using `Rc<RefCell<T>>` and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let counter = Counter::default();
loop {
terminal.draw(|frame| frame.render_widget(counter.clone(), frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget that uses interior mutability for shared state management.
///
/// Demonstrates how `Rc<RefCell<T>>` enables mutable state access even when the
/// widget itself is used by value.
#[derive(Default, Clone)]
struct Counter {
count: Rc<RefCell<usize>>,
}
impl Widget for Counter {
fn render(self, area: Rect, buf: &mut Buffer) {
self.count.borrow_mut().add_assign(1);
format!("Counter: {count}", count = self.count.borrow()).render(area, buf);
}
}

View File

@ -0,0 +1,78 @@
//! # StatefulWidget Pattern (Recommended)
//!
//! This example demonstrates the `StatefulWidget` trait, which is the recommended approach for
//! handling mutable state in Ratatui applications. This pattern separates the widget's rendering
//! logic from its state, making it more flexible and reusable.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - Most Ratatui applications (this is the recommended default)
//! - When building reusable widget libraries
//! - When you need clean separation between rendering logic and state
//! - When multiple widgets might share similar state structures
//! - When you want to follow idiomatic Ratatui patterns
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Clean separation of concerns between widget and state
//! - Reusable - the same widget can work with different state instances
//! - Testable - state and rendering logic can be tested independently
//! - Composable - works well with complex application architectures
//! - Idiomatic - follows Ratatui's recommended patterns
//!
//! **Cons:**
//! - Slightly more verbose than direct mutation patterns
//! - Requires understanding of the `StatefulWidget` trait
//! - State must be managed externally
//!
//! ## Example Usage
//!
//! The widget defines its rendering behavior through `StatefulWidget`, while the state is
//! managed separately. This allows the same widget to be used with different state instances
//! and makes testing easier.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::{StatefulWidget, Widget};
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the StatefulWidget pattern for mutable state management.
///
/// Creates a counter widget using `StatefulWidget` and runs the application loop,
/// updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut counter = 0;
loop {
terminal.draw(|frame| {
frame.render_stateful_widget(CounterWidget, frame.area(), &mut counter)
})?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget that uses the StatefulWidget pattern for state management.
///
/// Demonstrates the separation of rendering logic from state, making the widget reusable
/// with different state instances and easier to test.
struct CounterWidget;
impl StatefulWidget for CounterWidget {
type State = usize;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
*state += 1;
format!("Counter: {state}").render(area, buf);
}
}

View File

@ -0,0 +1,84 @@
//! # Lifetime-Based Mutable References Pattern
//!
//! This example demonstrates storing mutable references directly in widget structs using explicit
//! lifetimes. This is an advanced pattern that provides zero-cost state access but requires
//! careful lifetime management.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//!
//! ## When to Use This Pattern
//!
//! - You need maximum performance with zero runtime overhead
//! - You have a good understanding of Rust lifetimes and borrowing
//! - State lifetime is clearly defined and relatively simple
//! - You're building performance-critical applications
//!
//! ## Trade-offs
//!
//! **Pros:**
//! - Zero runtime cost - no reference counting or runtime borrow checking
//! - Compile-time safety - borrow checker ensures memory safety
//! - Direct access to state without indirection
//! - Maximum performance for state access
//!
//! **Cons:**
//! - Complex lifetime management - requires deep Rust knowledge
//! - Easy to create compilation errors that are hard to understand
//! - Inflexible - lifetime constraints can make code harder to refactor
//! - Not suitable for beginners - requires advanced Rust skills
//! - Widget structs become less reusable due to lifetime constraints
//!
//! ## Important Considerations
//!
//! - The widget's lifetime is tied to the state's lifetime
//! - You must ensure the state outlives the widget
//! - Lifetime annotations can become complex in larger applications
//! - Consider simpler patterns unless performance is critical
//!
//! ## Example Usage
//!
//! The widget stores a mutable reference to external state, allowing direct access without
//! runtime overhead. The widget must be recreated for each render call due to the lifetime
//! constraints.
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::Widget;
use ratatui_state_examples::is_exit_key_pressed;
/// Demonstrates the lifetime-based mutable references pattern for mutable state management.
///
/// Creates a counter widget using mutable references with explicit lifetimes and runs the
/// application loop, updating the counter on each render cycle until the user exits.
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| {
let mut count = 0;
loop {
let counter = CounterWidget { count: &mut count };
terminal.draw(|frame| frame.render_widget(counter, frame.area()))?;
if is_exit_key_pressed()? {
break Ok(());
}
}
})
}
/// A counter widget that holds a mutable reference to external state.
///
/// Demonstrates the lifetime-based pattern where the widget directly stores a
/// mutable reference to external state.
struct CounterWidget<'a> {
count: &'a mut usize,
}
impl Widget for CounterWidget<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
*self.count += 1;
format!("Counter: {count}", count = self.count).render(area, buf);
}
}

View File

@ -0,0 +1,8 @@
//! Helper functions for checking if exit keys are pressed
use crossterm::event::{self, KeyCode};
pub fn is_exit_key_pressed() -> std::io::Result<bool> {
Ok(event::read()?
.as_key_press_event()
.is_some_and(|key| matches!(key.code, KeyCode::Esc | KeyCode::Char('q'))))
}

View File

@ -268,6 +268,34 @@
//! (`&MyWidget` or `&mut MyWidget`). This provides reusability and automatic [`WidgetRef`] support.
//! You can optionally implement the consuming version for backward compatibility.
//!
//! ## State Management Patterns
//!
//! For a comprehensive exploration of different approaches to handling both mutable and immutable
//! state in widgets, see the [state examples] in the Ratatui repository. These examples demonstrate
//! various patterns including:
//!
//! **Immutable State Patterns** (recommended for most use cases):
//! - Function-based immutable state (`fn render(frame: &mut Frame, area: Rect, state: &State)`)
//! - Shared reference widgets (`impl Widget for &MyWidget`)
//! - Consuming widgets (`impl Widget for MyWidget`)
//!
//! **Mutable State Patterns** (for widgets that modify state during rendering):
//! - Function-based mutable state (`fn render(frame: &mut Frame, area: Rect, state: &mut State)`)
//! - Mutable widget references (`impl Widget for &mut MyWidget`)
//! - `StatefulWidget` pattern (`impl StatefulWidget for MyWidget`)
//! - Custom component traits (`trait MyComponent { fn render(&mut self, frame: &mut Frame, area:
//! Rect) }`)
//! - Interior mutability with `RefCell` (`struct MyWidget { state: Rc<RefCell<State>> }`)
//! - Lifetime-based mutable references (`struct MyWidget<'a> { state: &'a mut State }`)
//! - Nested widget hierarchies (compositions with owned or external state)
//!
//! Each pattern has different trade-offs in terms of complexity, performance, and architectural
//! fit, making them suitable for different use cases and application designs. For most
//! applications, start with immutable patterns as they are simpler to reason about and less prone
//! to borrowing issues.
//!
//! [state examples]: https://github.com/ratatui/ratatui/tree/main/examples/concepts/state
//!
//! ## Shared References (`&Widget`)
//!
//! The recommended pattern for most new widgets implements [`Widget`] on a shared reference,