mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-26 20:40:44 +00:00
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:
parent
92bb9b2219
commit
cfb65e64ba
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -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"
|
||||
|
@ -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]
|
||||
|
11
examples/concepts/state/Cargo.toml
Normal file
11
examples/concepts/state/Cargo.toml
Normal 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"] }
|
179
examples/concepts/state/README.md
Normal file
179
examples/concepts/state/README.md
Normal 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
|
83
examples/concepts/state/src/bin/component-trait.rs
Normal file
83
examples/concepts/state/src/bin/component-trait.rs
Normal 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);
|
||||
}
|
||||
}
|
86
examples/concepts/state/src/bin/immutable-consuming.rs
Normal file
86
examples/concepts/state/src/bin/immutable-consuming.rs
Normal 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);
|
||||
}
|
||||
}
|
82
examples/concepts/state/src/bin/immutable-function.rs
Normal file
82
examples/concepts/state/src/bin/immutable-function.rs
Normal 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);
|
||||
}
|
92
examples/concepts/state/src/bin/immutable-shared-ref.rs
Normal file
92
examples/concepts/state/src/bin/immutable-shared-ref.rs
Normal 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);
|
||||
}
|
||||
}
|
67
examples/concepts/state/src/bin/mutable-function.rs
Normal file
67
examples/concepts/state/src/bin/mutable-function.rs
Normal 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());
|
||||
}
|
74
examples/concepts/state/src/bin/mutable-widget.rs
Normal file
74
examples/concepts/state/src/bin/mutable-widget.rs
Normal 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);
|
||||
}
|
||||
}
|
96
examples/concepts/state/src/bin/nested-mutable-widget.rs
Normal file
96
examples/concepts/state/src/bin/nested-mutable-widget.rs
Normal 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);
|
||||
}
|
||||
}
|
103
examples/concepts/state/src/bin/nested-stateful-widget.rs
Normal file
103
examples/concepts/state/src/bin/nested-stateful-widget.rs
Normal 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);
|
||||
}
|
||||
}
|
86
examples/concepts/state/src/bin/refcell.rs
Normal file
86
examples/concepts/state/src/bin/refcell.rs
Normal 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);
|
||||
}
|
||||
}
|
78
examples/concepts/state/src/bin/stateful-widget.rs
Normal file
78
examples/concepts/state/src/bin/stateful-widget.rs
Normal 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);
|
||||
}
|
||||
}
|
84
examples/concepts/state/src/bin/widget-with-mutable-ref.rs
Normal file
84
examples/concepts/state/src/bin/widget-with-mutable-ref.rs
Normal 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);
|
||||
}
|
||||
}
|
8
examples/concepts/state/src/lib.rs
Normal file
8
examples/concepts/state/src/lib.rs
Normal 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'))))
|
||||
}
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user