mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-24 21:53:21 +00:00
chore(core): move core types to ratatui-core (#1460)
The buffer, layout, style, symbols, text, and the top level of widgets modules are moved to ratatui-core. This is the first step in modularizing the library so that the core types can be used in other projects without the need for the backend / widgets types. This helps reduce the need for updating other crates as often due to semver changes outside of the core types. --------- Co-authored-by: Orhun Parmaksız <orhun@archlinux.org> Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
This commit is contained in:
parent
0a47ebd94b
commit
98df774d7f
43 changed files with 1073 additions and 928 deletions
|
@ -10,6 +10,8 @@ GitHub with a [breaking change] label.
|
|||
|
||||
This is a quick summary of the sections below:
|
||||
|
||||
- [Unreleased](#unreleased)
|
||||
- The `From` impls for backend types are now replaced with more specific traits
|
||||
- [v0.29.0](#v0290)
|
||||
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const
|
||||
- Removed public fields from `Rect` iterators
|
||||
|
@ -104,7 +106,7 @@ let crossterm_attribute = crossterm::style::types::Attribute::Bold;
|
|||
+ let crossterm_attribute = ratatui_modifier.into_crossterm();
|
||||
```
|
||||
|
||||
Similar conversions for `ContentStyle` -> `Style` and `Attributes` -> `Modifier` exist for
|
||||
Similar conversions for `ContentStyle` -> `Style` and `Attributes` -> `Modifier` exist for
|
||||
Crossterm and the various Termion and Termwiz types as well.
|
||||
|
||||
## [v0.29.0](https://github.com/ratatui/ratatui/releases/tag/v0.29.0)
|
||||
|
|
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -2090,9 +2090,7 @@ version = "0.29.0"
|
|||
dependencies = [
|
||||
"argh",
|
||||
"bitflags 2.6.0",
|
||||
"cassowary",
|
||||
"color-eyre",
|
||||
"compact_str",
|
||||
"criterion",
|
||||
"crossterm",
|
||||
"document-features",
|
||||
|
@ -2102,13 +2100,12 @@ dependencies = [
|
|||
"indoc",
|
||||
"instability",
|
||||
"itertools 0.13.0",
|
||||
"lru",
|
||||
"octocrab",
|
||||
"palette",
|
||||
"paste",
|
||||
"pretty_assertions",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"ratatui-core",
|
||||
"rstest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -2121,6 +2118,28 @@ dependencies = [
|
|||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui-core"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools 0.13.0",
|
||||
"lru",
|
||||
"palette",
|
||||
"paste",
|
||||
"pretty_assertions",
|
||||
"rstest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
@ -2454,18 +2473,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.213"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.213"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
18
Cargo.toml
18
Cargo.toml
|
@ -1,7 +1,7 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["ratatui", "xtask"]
|
||||
default-members = ["ratatui"]
|
||||
members = ["ratatui", "ratatui-core", "xtask"]
|
||||
default-members = ["ratatui", "ratatui-core"]
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Florian Dehau <work@fdehau.com>", "The Ratatui Developers"]
|
||||
|
@ -23,6 +23,20 @@ exclude = [
|
|||
edition = "2021"
|
||||
rust-version = "1.74.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
bitflags = "2.6.0"
|
||||
indoc = "2.0.5"
|
||||
instability = "0.3.1"
|
||||
itertools = "0.13.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
ratatui-core = { path = "ratatui-core" }
|
||||
rstest = "0.23.0"
|
||||
serde = { version = "1.0.214", features = ["derive"] }
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
unicode-segmentation = "1.12.0"
|
||||
# See <https://github.com/ratatui/ratatui/issues/1271> for information about why we pin unicode-width
|
||||
unicode-width = "=0.2.0"
|
||||
|
||||
# Improve benchmark consistency
|
||||
[profile.bench]
|
||||
codegen-units = 1
|
||||
|
|
70
ratatui-core/Cargo.toml
Normal file
70
ratatui-core/Cargo.toml
Normal file
|
@ -0,0 +1,70 @@
|
|||
[package]
|
||||
name = "ratatui-core"
|
||||
description = """
|
||||
Core types and traits for the Ratatui Terminal UI library.
|
||||
Widget libraries should use this crate. Applications should use the main Ratatui crate.
|
||||
"""
|
||||
version = "0.3.0"
|
||||
authors.workspace = true
|
||||
documentation.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
exclude.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[features]
|
||||
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
|
||||
palette = ["dep:palette"]
|
||||
|
||||
## enables the backend code that sets the underline color. Underline color is only supported by
|
||||
## the Crossterm backend, and is not supported on Windows 7.
|
||||
underline-color = []
|
||||
|
||||
## enables serialization and deserialization of style and color types using the [`serde`] crate.
|
||||
## This is useful if you want to save themes to a file.
|
||||
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
|
||||
|
||||
## enable all unstable features.
|
||||
unstable = ["unstable-widget-ref"]
|
||||
|
||||
## enables the [`WidgetRef`] and [`StatefulWidgetRef`] traits which are experimental and may change
|
||||
## in the future.
|
||||
##
|
||||
## [`WidgetRef`]: widgets::WidgetRef
|
||||
## [`StatefulWidgetRef`]: widgets::StatefulWidgetRef
|
||||
unstable-widget-ref = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.3"
|
||||
cassowary = "0.3"
|
||||
compact_str = "0.8.0"
|
||||
instability.workspace = true
|
||||
indoc.workspace = true
|
||||
itertools.workspace = true
|
||||
lru = "0.12.0"
|
||||
palette = { version = "0.7.6", optional = true }
|
||||
paste = "1.0.2"
|
||||
serde = { workspace = true, optional = true }
|
||||
strum.workspace = true
|
||||
unicode-segmentation.workspace = true
|
||||
unicode-truncate = "1"
|
||||
unicode-width.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
rstest.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
serde_json = "1.0.132"
|
||||
|
||||
[lints.clippy]
|
||||
# we often split up a module into multiple files with the main type in a file named after the
|
||||
# module, so we want to allow this pattern
|
||||
module_inception = "allow"
|
34
ratatui-core/README.md
Normal file
34
ratatui-core/README.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# ratatui-core
|
||||
|
||||
[![Crates.io](https://img.shields.io/crates/v/ratatui-core)](https://crates.io/crates/ratatui-core)
|
||||
[![Documentation](https://docs.rs/ratatui-core/badge.svg)](https://docs.rs/ratatui-core)
|
||||
[![License](https://img.shields.io/crates/l/ratatui-core)](../LICENSE)
|
||||
|
||||
## Overview
|
||||
|
||||
**ratatui-core** is the core library of the [ratatui](https://github.com/ratatui/ratatui) project,
|
||||
providing the essential building blocks for creating rich terminal user interfaces in Rust.
|
||||
|
||||
### Why ratatui-core?
|
||||
|
||||
The `ratatui-core` crate is split from the main [`ratatui`](https://crates.io/crates/ratatui) crate
|
||||
to offer better stability for widget library authors. Widget libraries should generally depend on
|
||||
`ratatui-core`, benefiting from a stable API and reducing the need for frequent updates.
|
||||
Applications, on the other hand, should depend on the main `ratatui` crate, which includes built-in
|
||||
widgets and additional features.
|
||||
|
||||
## Installation
|
||||
|
||||
Add `ratatui-core` to your `Cargo.toml`:
|
||||
|
||||
```shell
|
||||
cargo add ratatui-core
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions from the community! Please see our [CONTRIBUTING](../CONTRIBUTING.md) guide for more details on how to get involved.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the [LICENSE](../LICENSE) file for details.
|
|
@ -23,7 +23,7 @@ use crate::{
|
|||
/// # Examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// style::{Color, Style},
|
||||
|
@ -168,7 +168,7 @@ impl Buffer {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
||||
|
@ -199,7 +199,7 @@ impl Buffer {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// style::{Color, Style},
|
||||
|
@ -227,7 +227,7 @@ impl Buffer {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
|
||||
/// // Global coordinates to the top corner of this buffer's area
|
||||
|
@ -239,7 +239,7 @@ impl Buffer {
|
|||
/// Panics when given an coordinate that is outside of this Buffer's area.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let buffer = Buffer::empty(Rect::new(200, 100, 10, 10));
|
||||
/// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
|
||||
|
@ -282,7 +282,7 @@ impl Buffer {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let rect = Rect::new(200, 100, 10, 10);
|
||||
/// let buffer = Buffer::empty(rect);
|
||||
|
@ -295,7 +295,7 @@ impl Buffer {
|
|||
/// Panics when given an index that is outside the Buffer's content.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
|
||||
/// let buffer = Buffer::empty(rect);
|
||||
|
@ -526,7 +526,7 @@ impl<P: Into<Position>> Index<P> for Buffer {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
||||
|
@ -556,7 +556,7 @@ impl<P: Into<Position>> IndexMut<P> for Buffer {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::{Buffer, Cell},
|
||||
/// layout::{Position, Rect},
|
||||
/// };
|
|
@ -26,7 +26,7 @@ use strum::EnumIs;
|
|||
/// `Constraint` provides helper methods to create lists of constraints from various input formats.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::Constraint;
|
||||
/// use ratatui_core::layout::Constraint;
|
||||
///
|
||||
/// // Create a layout with specified lengths for each element
|
||||
/// let constraints = Constraint::from_lengths([10, 20, 10]);
|
||||
|
@ -224,7 +224,7 @@ impl Constraint {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_lengths([1, 2, 3]);
|
||||
|
@ -242,7 +242,7 @@ impl Constraint {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
|
||||
|
@ -260,7 +260,7 @@ impl Constraint {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_percentages([25, 50, 25]);
|
||||
|
@ -278,7 +278,7 @@ impl Constraint {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_maxes([1, 2, 3]);
|
||||
|
@ -296,7 +296,7 @@ impl Constraint {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_mins([1, 2, 3]);
|
||||
|
@ -314,7 +314,7 @@ impl Constraint {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let constraints = Constraint::from_fills([1, 2, 3]);
|
||||
|
@ -337,7 +337,7 @@ impl From<u16> for Constraint {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
|
||||
///
|
||||
/// # let area = Rect::default();
|
||||
/// let layout = Layout::new(Direction::Vertical, [1, 2, 3]).split(area);
|
|
@ -148,20 +148,18 @@ impl From<i16> for Spacing {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::{Constraint, Direction, Layout, Rect},
|
||||
/// widgets::Paragraph,
|
||||
/// Frame,
|
||||
/// text::Text,
|
||||
/// widgets::Widget,
|
||||
/// };
|
||||
///
|
||||
/// fn render(frame: &mut Frame, area: Rect) {
|
||||
/// let layout = Layout::new(
|
||||
/// Direction::Vertical,
|
||||
/// [Constraint::Length(5), Constraint::Min(0)],
|
||||
/// )
|
||||
/// .split(Rect::new(0, 0, 10, 10));
|
||||
/// frame.render_widget(Paragraph::new("foo"), layout[0]);
|
||||
/// frame.render_widget(Paragraph::new("bar"), layout[1]);
|
||||
/// fn render(area: Rect, buf: &mut ratatui_core::buffer::Buffer) {
|
||||
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
|
||||
/// let [left, right] = layout.areas(area);
|
||||
/// Text::from("foo").render(left, buf);
|
||||
/// Text::from("bar").render(right, buf);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
|
@ -206,7 +204,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout};
|
||||
/// use ratatui_core::layout::{Constraint, Direction, Layout};
|
||||
///
|
||||
/// Layout::new(
|
||||
/// Direction::Horizontal,
|
||||
|
@ -240,7 +238,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout};
|
||||
/// use ratatui_core::layout::{Constraint, Layout};
|
||||
///
|
||||
/// let layout = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
|
||||
/// ```
|
||||
|
@ -260,7 +258,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout};
|
||||
/// use ratatui_core::layout::{Constraint, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Constraint::Length(5), Constraint::Min(0)]);
|
||||
/// ```
|
||||
|
@ -289,7 +287,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .direction(Direction::Horizontal)
|
||||
|
@ -324,7 +322,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .constraints([
|
||||
|
@ -369,7 +367,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .constraints([Constraint::Min(0)])
|
||||
|
@ -391,7 +389,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .constraints([Constraint::Min(0)])
|
||||
|
@ -410,7 +408,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// let layout = Layout::default()
|
||||
/// .constraints([Constraint::Min(0)])
|
||||
|
@ -442,7 +440,7 @@ impl Layout {
|
|||
/// In this example, the items in the layout will be aligned to the start.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint::*, Flex, Layout};
|
||||
/// use ratatui_core::layout::{Constraint::*, Flex, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Start);
|
||||
/// ```
|
||||
|
@ -451,7 +449,7 @@ impl Layout {
|
|||
/// space.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint::*, Flex, Layout};
|
||||
/// use ratatui_core::layout::{Constraint::*, Flex, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).flex(Flex::Legacy);
|
||||
/// ```
|
||||
|
@ -479,7 +477,7 @@ impl Layout {
|
|||
/// In this example, the spacing between each item in the layout is set to 2 cells.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint::*, Layout};
|
||||
/// use ratatui_core::layout::{Constraint::*, Layout};
|
||||
///
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(2);
|
||||
/// ```
|
||||
|
@ -488,7 +486,7 @@ impl Layout {
|
|||
/// three segments will have an overlapping border.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Constraint::*, Layout};
|
||||
/// use ratatui_core::layout::{Constraint::*, Layout};
|
||||
/// let layout = Layout::horizontal([Length(20), Length(20), Length(20)]).spacing(-1);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
|
@ -515,16 +513,14 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{layout::{Layout, Constraint}, Frame};
|
||||
/// use ratatui_core::layout::{Layout, Constraint, Rect};
|
||||
///
|
||||
/// # fn render(frame: &mut Frame) {
|
||||
/// let area = frame.area();
|
||||
/// let area = Rect::new(0, 0, 10, 10);
|
||||
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
|
||||
/// let [top, main] = layout.areas(area);
|
||||
///
|
||||
/// // or explicitly specify the number of constraints:
|
||||
/// let areas = layout.areas::<2>(area);
|
||||
/// # }
|
||||
pub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N] {
|
||||
let (areas, _) = self.split_with_spacers(area);
|
||||
areas.as_ref().try_into().expect("invalid number of rects")
|
||||
|
@ -548,17 +544,16 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{layout::{Layout, Constraint}, Frame};
|
||||
/// use ratatui_core::layout::{Constraint, Layout, Rect};
|
||||
///
|
||||
/// # fn render(frame: &mut Frame) {
|
||||
/// let area = frame.area();
|
||||
/// let area = Rect::new(0, 0, 10, 10);
|
||||
/// let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
|
||||
/// let [top, main] = layout.areas(area);
|
||||
/// let [before, inbetween, after] = layout.spacers(area);
|
||||
///
|
||||
/// // or explicitly specify the number of constraints:
|
||||
/// let spacers = layout.spacers::<2>(area);
|
||||
/// # }
|
||||
/// let spacers = layout.spacers::<3>(area);
|
||||
/// ```
|
||||
pub fn spacers<const N: usize>(&self, area: Rect) -> [Rect; N] {
|
||||
let (_, spacers) = self.split_with_spacers(area);
|
||||
spacers
|
||||
|
@ -588,7 +583,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
|
||||
/// let layout = Layout::default()
|
||||
/// .direction(Direction::Vertical)
|
||||
/// .constraints([Constraint::Length(5), Constraint::Min(0)])
|
||||
|
@ -620,7 +615,7 @@ impl Layout {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
/// use ratatui_core::layout::{Constraint, Direction, Layout, Rect};
|
||||
///
|
||||
/// let (areas, spacers) = Layout::default()
|
||||
/// .direction(Direction::Vertical)
|
||||
|
@ -1413,8 +1408,12 @@ mod tests {
|
|||
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Constraint::*, Direction, Flex, Layout, Rect},
|
||||
widgets::{Paragraph, Widget},
|
||||
layout::{
|
||||
Constraint::{self, *},
|
||||
Direction, Flex, Layout, Rect,
|
||||
},
|
||||
text::Text,
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
/// Test that the given constraints applied to the given area result in the expected layout.
|
||||
|
@ -1436,7 +1435,7 @@ mod tests {
|
|||
let mut buffer = Buffer::empty(area);
|
||||
for (c, &area) in ('a'..='z').take(constraints.len()).zip(layout.iter()) {
|
||||
let s = c.to_string().repeat(area.width as usize);
|
||||
Paragraph::new(s).render(area, &mut buffer);
|
||||
Text::from(s).render(area, &mut buffer);
|
||||
}
|
||||
assert_eq!(buffer, Buffer::with_lines([expected]));
|
||||
}
|
|
@ -12,7 +12,7 @@ use crate::layout::Rect;
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::layout::{Position, Rect};
|
||||
/// use ratatui_core::layout::{Position, Rect};
|
||||
///
|
||||
/// // the following are all equivalent
|
||||
/// let position = Position { x: 1, y: 2 };
|
|
@ -64,7 +64,7 @@ impl Rect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::layout::Rect;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
///
|
||||
/// let rect = Rect::new(1, 2, 3, 4);
|
||||
/// ```
|
||||
|
@ -213,7 +213,7 @@ impl Rect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::layout::{Position, Rect};
|
||||
/// use ratatui_core::layout::{Position, Rect};
|
||||
///
|
||||
/// let rect = Rect::new(1, 2, 3, 4);
|
||||
/// assert!(rect.contains(Position { x: 1, y: 2 }));
|
||||
|
@ -243,12 +243,11 @@ impl Rect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{layout::Rect, Frame};
|
||||
/// use ratatui_core::layout::Rect;
|
||||
///
|
||||
/// # fn render(frame: &mut Frame) {
|
||||
/// let area = frame.area();
|
||||
/// let rect = Rect::new(0, 0, 100, 100).clamp(area);
|
||||
/// # }
|
||||
/// let area = Rect::new(0, 0, 100, 100);
|
||||
/// let rect = Rect::new(80, 80, 30, 30).clamp(area);
|
||||
/// assert_eq!(rect, Rect::new(70, 70, 30, 30));
|
||||
/// ```
|
||||
#[must_use = "method returns the modified value"]
|
||||
pub fn clamp(self, other: Self) -> Self {
|
||||
|
@ -264,7 +263,7 @@ impl Rect {
|
|||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// for row in area.rows() {
|
||||
|
@ -281,14 +280,11 @@ impl Rect {
|
|||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// widgets::{Block, Borders, Widget},
|
||||
/// };
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Text, widgets::Widget};
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// if let Some(left) = area.columns().next() {
|
||||
/// Block::new().borders(Borders::LEFT).render(left, buf);
|
||||
/// for (i, column) in area.columns().enumerate() {
|
||||
/// Text::from(format!("{}", i)).render(column, buf);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -303,7 +299,7 @@ impl Rect {
|
|||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// for position in area.positions() {
|
||||
|
@ -320,7 +316,7 @@ impl Rect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::layout::Rect;
|
||||
/// use ratatui_core::layout::Rect;
|
||||
///
|
||||
/// let rect = Rect::new(1, 2, 3, 4);
|
||||
/// let position = rect.as_position();
|
7
ratatui-core/src/lib.rs
Normal file
7
ratatui-core/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
pub mod buffer;
|
||||
pub mod layout;
|
||||
pub mod style;
|
||||
pub mod symbols;
|
||||
pub mod text;
|
||||
pub mod widgets;
|
|
@ -13,7 +13,7 @@
|
|||
//! ## Example
|
||||
//!
|
||||
//! ```
|
||||
//! use ratatui::{
|
||||
//! use ratatui_core::{
|
||||
//! style::{Color, Modifier, Style},
|
||||
//! text::Span,
|
||||
//! };
|
||||
|
@ -43,10 +43,9 @@
|
|||
//! ## Example
|
||||
//!
|
||||
//! ```
|
||||
//! use ratatui::{
|
||||
//! use ratatui_core::{
|
||||
//! style::{Color, Modifier, Style, Stylize},
|
||||
//! text::Span,
|
||||
//! widgets::Paragraph,
|
||||
//! text::{Span, Text},
|
||||
//! };
|
||||
//!
|
||||
//! assert_eq!(
|
||||
|
@ -61,8 +60,8 @@
|
|||
//! );
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! Paragraph::new("hello").red().on_blue().bold(),
|
||||
//! Paragraph::new("hello").style(
|
||||
//! Text::from("hello").red().on_blue().bold(),
|
||||
//! Text::from("hello").style(
|
||||
//! Style::default()
|
||||
//! .fg(Color::Red)
|
||||
//! .bg(Color::Blue)
|
||||
|
@ -97,7 +96,7 @@ bitflags! {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::Modifier;
|
||||
/// use ratatui_core::style::Modifier;
|
||||
///
|
||||
/// let m = Modifier::BOLD | Modifier::ITALIC;
|
||||
/// ```
|
||||
|
@ -133,7 +132,7 @@ impl fmt::Debug for Modifier {
|
|||
/// Style lets you control the main characteristics of the displayed elements.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// Style::default()
|
||||
/// .fg(Color::Black)
|
||||
|
@ -144,7 +143,7 @@ impl fmt::Debug for Modifier {
|
|||
/// Styles can also be created with a [shorthand notation](crate::style#using-style-shorthands).
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Style, Stylize};
|
||||
/// use ratatui_core::style::{Style, Stylize};
|
||||
///
|
||||
/// Style::new().black().on_green().italic().bold();
|
||||
/// ```
|
||||
|
@ -155,7 +154,7 @@ impl fmt::Debug for Modifier {
|
|||
/// anywhere that accepts `Into<Style>`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Line,
|
||||
/// };
|
||||
|
@ -174,7 +173,7 @@ impl fmt::Debug for Modifier {
|
|||
/// just S3.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Color, Modifier, Style},
|
||||
|
@ -214,7 +213,7 @@ impl fmt::Debug for Modifier {
|
|||
/// reset all properties until that point use [`Style::reset`].
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Color, Modifier, Style},
|
||||
|
@ -304,7 +303,7 @@ impl Style {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Style};
|
||||
/// use ratatui_core::style::{Color, Style};
|
||||
///
|
||||
/// let style = Style::default().fg(Color::Blue);
|
||||
/// let diff = Style::default().fg(Color::Red);
|
||||
|
@ -321,7 +320,7 @@ impl Style {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Style};
|
||||
/// use ratatui_core::style::{Color, Style};
|
||||
///
|
||||
/// let style = Style::default().bg(Color::Blue);
|
||||
/// let diff = Style::default().bg(Color::Red);
|
||||
|
@ -346,7 +345,7 @@ impl Style {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// let style = Style::default()
|
||||
/// .underline_color(Color::Blue)
|
||||
|
@ -375,7 +374,7 @@ impl Style {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Modifier, Style};
|
||||
/// use ratatui_core::style::{Modifier, Style};
|
||||
///
|
||||
/// let style = Style::default().add_modifier(Modifier::BOLD);
|
||||
/// let diff = Style::default().add_modifier(Modifier::ITALIC);
|
||||
|
@ -397,7 +396,7 @@ impl Style {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Modifier, Style};
|
||||
/// use ratatui_core::style::{Modifier, Style};
|
||||
///
|
||||
/// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
|
||||
/// let diff = Style::default().remove_modifier(Modifier::ITALIC);
|
||||
|
@ -420,7 +419,7 @@ impl Style {
|
|||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// let style_1 = Style::default().fg(Color::Yellow);
|
||||
/// let style_2 = Style::default().bg(Color::Red);
|
||||
|
@ -506,7 +505,7 @@ impl From<Color> for Style {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Style};
|
||||
/// use ratatui_core::style::{Color, Style};
|
||||
///
|
||||
/// let style = Style::from(Color::Red);
|
||||
/// ```
|
||||
|
@ -521,7 +520,7 @@ impl From<(Color, Color)> for Style {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Style};
|
||||
/// use ratatui_core::style::{Color, Style};
|
||||
///
|
||||
/// // red foreground, blue background
|
||||
/// let style = Style::from((Color::Red, Color::Blue));
|
||||
|
@ -544,7 +543,7 @@ impl From<Modifier> for Style {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Style, Modifier};
|
||||
/// use ratatui_core::style::{Style, Modifier};
|
||||
///
|
||||
/// // add bold and italic
|
||||
/// let style = Style::from(Modifier::BOLD|Modifier::ITALIC);
|
||||
|
@ -559,7 +558,7 @@ impl From<(Modifier, Modifier)> for Style {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Modifier, Style};
|
||||
/// use ratatui_core::style::{Modifier, Style};
|
||||
///
|
||||
/// // add bold and italic, remove dim
|
||||
/// let style = Style::from((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
|
||||
|
@ -579,7 +578,7 @@ impl From<(Color, Modifier)> for Style {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// // red foreground, add bold and italic
|
||||
/// let style = Style::from((Color::Red, Modifier::BOLD | Modifier::ITALIC));
|
||||
|
@ -597,7 +596,7 @@ impl From<(Color, Color, Modifier)> for Style {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// // red foreground, blue background, add bold and italic
|
||||
/// let style = Style::from((Color::Red, Color::Blue, Modifier::BOLD | Modifier::ITALIC));
|
||||
|
@ -614,7 +613,7 @@ impl From<(Color, Color, Modifier, Modifier)> for Style {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::{Color, Modifier, Style};
|
||||
/// use ratatui_core::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// // red foreground, blue background, add bold and italic, remove dim
|
||||
/// let style = Style::from((
|
|
@ -44,7 +44,7 @@ use crate::style::stylize::{ColorDebug, ColorDebugKind};
|
|||
/// ```
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// use ratatui::style::Color;
|
||||
/// use ratatui_core::style::Color;
|
||||
///
|
||||
/// assert_eq!(Color::from_str("red"), Ok(Color::Red));
|
||||
/// assert_eq!("red".parse(), Ok(Color::Red));
|
||||
|
@ -112,14 +112,12 @@ pub enum Color {
|
|||
/// Notably versions of Windows Terminal prior to Windows 10 and macOS Terminal.app do not
|
||||
/// support this.
|
||||
///
|
||||
/// If the terminal does not support true color, code using the [`TermwizBackend`] will
|
||||
/// If the terminal does not support true color, code using the `TermwizBackend` will
|
||||
/// fallback to the default text color. Crossterm and Termion do not have this capability and
|
||||
/// the display will be unpredictable (e.g. Terminal.app may display glitched blinking text).
|
||||
/// See <https://github.com/ratatui/ratatui/issues/475> for an example of this problem.
|
||||
///
|
||||
/// See also: <https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit>
|
||||
///
|
||||
/// [`TermwizBackend`]: crate::backend::TermwizBackend
|
||||
Rgb(u8, u8, u8),
|
||||
/// An 8-bit 256 color.
|
||||
///
|
||||
|
@ -170,7 +168,7 @@ impl<'de> serde::Deserialize<'de> for Color {
|
|||
/// ```
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// use ratatui::style::Color;
|
||||
/// use ratatui_core::style::Color;
|
||||
///
|
||||
/// #[derive(Debug, serde::Deserialize)]
|
||||
/// struct Theme {
|
||||
|
@ -265,7 +263,7 @@ impl std::error::Error for ParseColorError {}
|
|||
/// ```
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// use ratatui::style::Color;
|
||||
/// use ratatui_core::style::Color;
|
||||
///
|
||||
/// let color: Color = Color::from_str("blue").unwrap();
|
||||
/// assert_eq!(color, Color::Blue);
|
||||
|
@ -385,7 +383,8 @@ impl Color {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{palette::Hsl, style::Color};
|
||||
/// use palette::Hsl;
|
||||
/// use ratatui_core::style::Color;
|
||||
///
|
||||
/// // Minimum Lightness is black
|
||||
/// let color: Color = Color::from_hsl(Hsl::new(0.0, 0.0, 0.0));
|
||||
|
@ -437,7 +436,8 @@ impl Color {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::{palette::Hsluv, style::Color};
|
||||
/// use palette::Hsluv;
|
||||
/// use ratatui_core::style::Color;
|
||||
///
|
||||
/// // Minimum Lightness is black
|
||||
/// let color: Color = Color::from_hsluv(Hsluv::new(0.0, 100.0, 0.0));
|
|
@ -403,7 +403,7 @@
|
|||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use ratatui::style::{
|
||||
//! use ratatui_core::style::{
|
||||
//! palette::material::{BLUE, RED},
|
||||
//! Color,
|
||||
//! };
|
|
@ -268,7 +268,7 @@
|
|||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use ratatui::style::{
|
||||
//! use ratatui_core::style::{
|
||||
//! palette::tailwind::{BLUE, RED},
|
||||
//! Color,
|
||||
//! };
|
|
@ -15,7 +15,7 @@ use crate::style::Color;
|
|||
///
|
||||
/// ```
|
||||
/// use palette::Srgb;
|
||||
/// use ratatui::style::Color;
|
||||
/// use ratatui_core::style::Color;
|
||||
///
|
||||
/// let color = Color::from(Srgb::new(1.0f32, 0.0, 0.0));
|
||||
/// assert_eq!(color, Color::Rgb(255, 0, 0));
|
||||
|
@ -36,7 +36,7 @@ impl<T: IntoStimulus<u8>> From<Srgb<T>> for Color {
|
|||
///
|
||||
/// ```
|
||||
/// use palette::LinSrgb;
|
||||
/// use ratatui::style::Color;
|
||||
/// use ratatui_core::style::Color;
|
||||
///
|
||||
/// let color = Color::from(LinSrgb::new(1.0f32, 0.0, 0.0));
|
||||
/// assert_eq!(color, Color::Rgb(255, 0, 0));
|
|
@ -195,8 +195,8 @@ macro_rules! modifier {
|
|||
/// by `not_`). The `reset()` method is also provided to reset the style.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use ratatui::{
|
||||
/// ```ignore
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::Line,
|
||||
/// widgets::{Block, Paragraph},
|
|
@ -1,25 +1,25 @@
|
|||
//! Primitives for styled text.
|
||||
//!
|
||||
//! A terminal UI is at its root a lot of strings. In order to make it accessible and stylish,
|
||||
//! those strings may be associated to a set of styles. `ratatui` has three ways to represent them:
|
||||
//! A terminal UI is at its root a lot of strings. In order to make it accessible and stylish, those
|
||||
//! strings may be associated to a set of styles. `ratatui` has three ways to represent them:
|
||||
//! - A single line string where all graphemes have the same style is represented by a [`Span`].
|
||||
//! - A single line string where each grapheme may have its own style is represented by [`Line`].
|
||||
//! - A multiple line string where each grapheme may have its own style is represented by a
|
||||
//! [`Text`].
|
||||
//!
|
||||
//! These types form a hierarchy: [`Line`] is a collection of [`Span`] and each line of [`Text`]
|
||||
//! is a [`Line`].
|
||||
//! These types form a hierarchy: [`Line`] is a collection of [`Span`] and each line of [`Text`] is
|
||||
//! a [`Line`].
|
||||
//!
|
||||
//! Keep it mind that a lot of widgets will use those types to advertise what kind of string is
|
||||
//! supported for their properties. Moreover, `ratatui` provides convenient `From` implementations
|
||||
//! so that you can start by using simple `String` or `&str` and then promote them to the previous
|
||||
//! primitives when you need additional styling capabilities.
|
||||
//!
|
||||
//! For example, for the [`crate::widgets::Block`] widget, all the following calls are valid to set
|
||||
//! its `title` property (which is a [`Line`] under the hood):
|
||||
//! For example, for the `Block` widget, all the following calls are valid to set its `title`
|
||||
//! property (which is a [`Line`] under the hood):
|
||||
//!
|
||||
//! ```rust
|
||||
//! use ratatui::{
|
||||
//! ```rust,ignore
|
||||
//! use ratatui_core::{
|
||||
//! style::{Color, Style},
|
||||
//! text::{Line, Span},
|
||||
//! widgets::Block,
|
|
@ -28,7 +28,7 @@ impl<'a> StyledGrapheme<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_whitespace(&self) -> bool {
|
||||
pub fn is_whitespace(&self) -> bool {
|
||||
let symbol = self.symbol;
|
||||
symbol == ZWSP || symbol.chars().all(char::is_whitespace) && symbol != NBSP
|
||||
}
|
|
@ -75,7 +75,7 @@ use crate::{
|
|||
/// [`Style`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Span},
|
||||
/// };
|
||||
|
@ -102,7 +102,7 @@ use crate::{
|
|||
/// methods of the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
|
@ -121,7 +121,7 @@ use crate::{
|
|||
/// ignored and the line is truncated.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{layout::Alignment, text::Line};
|
||||
/// use ratatui_core::{layout::Alignment, text::Line};
|
||||
///
|
||||
/// let line = Line::from("Hello world!").alignment(Alignment::Right);
|
||||
/// let line = Line::from("Hello world!").centered();
|
||||
|
@ -134,13 +134,12 @@ use crate::{
|
|||
/// `Line` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// widgets::Widget,
|
||||
/// Frame,
|
||||
/// };
|
||||
///
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
|
@ -148,19 +147,23 @@ use crate::{
|
|||
/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
|
||||
/// line.render(area, buf);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Or you can use the `render_widget` method on the `Frame` in a `Terminal::draw` closure.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use ratatui::{Frame, layout::Rect, text::Line};
|
||||
/// # fn draw(frame: &mut Frame, area: Rect) {
|
||||
/// // in a terminal.draw closure
|
||||
/// let line = Line::from("Hello world!").style(Style::new().yellow().italic());
|
||||
/// let line = Line::from("Hello world!");
|
||||
/// frame.render_widget(line, area);
|
||||
/// # }
|
||||
/// ```
|
||||
/// ## Rendering Lines with a Paragraph widget
|
||||
///
|
||||
/// Usually apps will use the [`Paragraph`] widget instead of rendering a [`Line`] directly as it
|
||||
/// Usually apps will use the `Paragraph` widget instead of rendering a [`Line`] directly as it
|
||||
/// provides more functionality.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
|
@ -177,7 +180,6 @@ use crate::{
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Line<'a> {
|
||||
|
@ -241,7 +243,7 @@ impl<'a> Line<'a> {
|
|||
/// ```rust
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// use ratatui::text::Line;
|
||||
/// use ratatui_core::text::Line;
|
||||
///
|
||||
/// Line::raw("test content");
|
||||
/// Line::raw(String::from("test content"));
|
||||
|
@ -272,7 +274,7 @@ impl<'a> Line<'a> {
|
|||
/// ```rust
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
|
@ -304,7 +306,7 @@ impl<'a> Line<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{style::Stylize, text::Line};
|
||||
/// use ratatui_core::{style::Stylize, text::Line};
|
||||
///
|
||||
/// let line = Line::default().spans(vec!["Hello".blue(), " world!".green()]);
|
||||
/// let line = Line::default().spans([1, 2, 3].iter().map(|i| format!("Item {}", i)));
|
||||
|
@ -332,7 +334,7 @@ impl<'a> Line<'a> {
|
|||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
|
@ -356,7 +358,7 @@ impl<'a> Line<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{layout::Alignment, text::Line};
|
||||
/// use ratatui_core::{layout::Alignment, text::Line};
|
||||
///
|
||||
/// let mut line = Line::from("Hi, what's up?");
|
||||
/// assert_eq!(None, line.alignment);
|
||||
|
@ -382,7 +384,7 @@ impl<'a> Line<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Line;
|
||||
/// use ratatui_core::text::Line;
|
||||
///
|
||||
/// let line = Line::from("Hi, what's up?").left_aligned();
|
||||
/// ```
|
||||
|
@ -400,7 +402,7 @@ impl<'a> Line<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Line;
|
||||
/// use ratatui_core::text::Line;
|
||||
///
|
||||
/// let line = Line::from("Hi, what's up?").centered();
|
||||
/// ```
|
||||
|
@ -418,7 +420,7 @@ impl<'a> Line<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Line;
|
||||
/// use ratatui_core::text::Line;
|
||||
///
|
||||
/// let line = Line::from("Hi, what's up?").right_aligned();
|
||||
/// ```
|
||||
|
@ -432,7 +434,7 @@ impl<'a> Line<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{style::Stylize, text::Line};
|
||||
/// use ratatui_core::{style::Stylize, text::Line};
|
||||
///
|
||||
/// let line = Line::from(vec!["Hello".blue(), " world!".green()]);
|
||||
/// assert_eq!(12, line.width());
|
||||
|
@ -454,7 +456,7 @@ impl<'a> Line<'a> {
|
|||
/// ```rust
|
||||
/// use std::iter::Iterator;
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Style},
|
||||
/// text::{Line, StyledGrapheme},
|
||||
/// };
|
||||
|
@ -498,7 +500,7 @@ impl<'a> Line<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier},
|
||||
/// text::Line,
|
||||
/// };
|
||||
|
@ -527,7 +529,7 @@ impl<'a> Line<'a> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # let style = Style::default().yellow();
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Line,
|
||||
/// };
|
||||
|
@ -559,7 +561,7 @@ impl<'a> Line<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::{Line, Span};
|
||||
/// use ratatui_core::text::{Line, Span};
|
||||
///
|
||||
/// let mut line = Line::from("Hello, ");
|
||||
/// line.push_span(Span::raw("world!"));
|
|
@ -4,23 +4,23 @@ use crate::text::Text;
|
|||
|
||||
/// A wrapper around a string that is masked when displayed.
|
||||
///
|
||||
/// The masked string is displayed as a series of the same character.
|
||||
/// This might be used to display a password field or similar secure data.
|
||||
/// The masked string is displayed as a series of the same character. This might be used to display
|
||||
/// a password field or similar secure data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Masked,
|
||||
/// widgets::{Paragraph, Widget},
|
||||
/// text::{Masked, Text},
|
||||
/// widgets::Widget,
|
||||
/// };
|
||||
///
|
||||
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
/// let password = Masked::new("12345", 'x');
|
||||
///
|
||||
/// Paragraph::new(password).render(buffer.area, &mut buffer);
|
||||
/// Text::from(password).render(buffer.area, &mut buffer);
|
||||
/// assert_eq!(buffer, Buffer::with_lines(["xxxxx"]));
|
||||
/// ```
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
|
@ -42,7 +42,7 @@ use crate::{
|
|||
/// any type convertible to [`Cow<str>`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Span;
|
||||
/// use ratatui_core::text::Span;
|
||||
///
|
||||
/// let span = Span::raw("test content");
|
||||
/// let span = Span::raw(String::from("test content"));
|
||||
|
@ -56,7 +56,7 @@ use crate::{
|
|||
/// the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
|
@ -73,7 +73,7 @@ use crate::{
|
|||
/// defined in the [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{style::Stylize, text::Span};
|
||||
/// use ratatui_core::{style::Stylize, text::Span};
|
||||
///
|
||||
/// let span = Span::raw("test content").green().on_yellow().italic();
|
||||
/// let span = Span::raw(String::from("test content"))
|
||||
|
@ -82,11 +82,11 @@ use crate::{
|
|||
/// .italic();
|
||||
/// ```
|
||||
///
|
||||
/// `Span` implements the [`Widget`] trait, which allows it to be rendered to a [`Buffer`]. Usually
|
||||
/// apps will use the [`Paragraph`] widget instead of rendering `Span` directly, as it handles text
|
||||
/// `Span` implements the [`Widget`] trait, which allows it to be rendered to a [`Buffer`]. Often
|
||||
/// apps will use the `Paragraph` widget instead of rendering `Span` directly, as it handles text
|
||||
/// wrapping and alignment for you.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// use ratatui::{style::Stylize, Frame};
|
||||
///
|
||||
/// # fn render_frame(frame: &mut Frame) {
|
||||
|
@ -94,7 +94,6 @@ use crate::{
|
|||
/// # }
|
||||
/// ```
|
||||
/// [`Line`]: crate::text::Line
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
/// [`Cow<str>`]: std::borrow::Cow
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
||||
|
@ -125,7 +124,7 @@ impl<'a> Span<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Span;
|
||||
/// use ratatui_core::text::Span;
|
||||
///
|
||||
/// Span::raw("test content");
|
||||
/// Span::raw(String::from("test content"));
|
||||
|
@ -151,7 +150,7 @@ impl<'a> Span<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
|
@ -183,7 +182,7 @@ impl<'a> Span<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Span;
|
||||
/// use ratatui_core::text::Span;
|
||||
///
|
||||
/// let mut span = Span::default().content("content");
|
||||
/// ```
|
||||
|
@ -209,7 +208,7 @@ impl<'a> Span<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
|
@ -234,7 +233,7 @@ impl<'a> Span<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
|
@ -260,7 +259,7 @@ impl<'a> Span<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Span,
|
||||
/// };
|
||||
|
@ -295,7 +294,7 @@ impl<'a> Span<'a> {
|
|||
/// ```rust
|
||||
/// use std::iter::Iterator;
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::{Span, StyledGrapheme},
|
||||
/// };
|
||||
|
@ -332,7 +331,7 @@ impl<'a> Span<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::Stylize;
|
||||
/// use ratatui_core::style::Stylize;
|
||||
///
|
||||
/// let line = "Test Content".green().italic().into_left_aligned_line();
|
||||
/// ```
|
||||
|
@ -352,7 +351,7 @@ impl<'a> Span<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::Stylize;
|
||||
/// use ratatui_core::style::Stylize;
|
||||
///
|
||||
/// let line = "Test Content".green().italic().into_centered_line();
|
||||
/// ```
|
||||
|
@ -372,7 +371,7 @@ impl<'a> Span<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::style::Stylize;
|
||||
/// use ratatui_core::style::Stylize;
|
||||
///
|
||||
/// let line = "Test Content".green().italic().into_right_aligned_line();
|
||||
/// ```
|
|
@ -68,7 +68,7 @@ use crate::{
|
|||
/// ```rust
|
||||
/// use std::{borrow::Cow, iter};
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Span, Text},
|
||||
/// };
|
||||
|
@ -108,7 +108,7 @@ use crate::{
|
|||
/// [`Stylize`] trait.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style, Stylize},
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
|
@ -129,7 +129,7 @@ use crate::{
|
|||
/// Lines composing the text can also be individually aligned with [`Line::alignment`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// layout::Alignment,
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
|
@ -146,19 +146,23 @@ use crate::{
|
|||
///
|
||||
/// ## Rendering Text
|
||||
/// `Text` implements the [`Widget`] trait, which means it can be rendered to a [`Buffer`] or to a
|
||||
/// [`Frame`].
|
||||
/// `Frame`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui::{text::Text, widgets::Widget, Frame};
|
||||
/// # use ratatui_core::{buffer::Buffer, layout::Rect};
|
||||
/// use ratatui_core::{text::Text, widgets::Widget};
|
||||
///
|
||||
/// // within another widget's `render` method:
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// text.render(area, buf);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// // within a terminal.draw closure:
|
||||
/// Or you can use the `render_widget` method on a `Frame` within a `Terminal::draw` closure.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use ratatui::{Frame, layout::Rect, text::Text};
|
||||
/// # fn draw(frame: &mut Frame, area: Rect) {
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// frame.render_widget(text, area);
|
||||
|
@ -167,10 +171,10 @@ use crate::{
|
|||
///
|
||||
/// ## Rendering Text with a Paragraph Widget
|
||||
///
|
||||
/// Usually apps will use the [`Paragraph`] widget instead of rendering a `Text` directly as it
|
||||
/// Usually apps will use the `Paragraph` widget instead of rendering a `Text` directly as it
|
||||
/// provides more functionality.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
|
@ -189,7 +193,6 @@ use crate::{
|
|||
///
|
||||
/// [`Paragraph`]: crate::widgets::Paragraph
|
||||
/// [`Stylize`]: crate::style::Stylize
|
||||
/// [`Frame`]: crate::Frame
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Text<'a> {
|
||||
/// The alignment of this text.
|
||||
|
@ -228,7 +231,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// use ratatui_core::text::Text;
|
||||
///
|
||||
/// Text::raw("The first line\nThe second line");
|
||||
/// Text::raw(String::from("The first line\nThe second line"));
|
||||
|
@ -254,7 +257,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Text,
|
||||
/// };
|
||||
|
@ -280,7 +283,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// use ratatui_core::text::Text;
|
||||
///
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(15, text.width());
|
||||
|
@ -294,7 +297,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// use ratatui_core::text::Text;
|
||||
///
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(2, text.height());
|
||||
|
@ -316,7 +319,7 @@ impl<'a> Text<'a> {
|
|||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Style, Stylize},
|
||||
/// text::Text,
|
||||
/// };
|
||||
|
@ -348,7 +351,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier},
|
||||
/// text::Text,
|
||||
/// };
|
||||
|
@ -381,7 +384,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// style::{Color, Modifier, Style},
|
||||
/// text::Text,
|
||||
/// };
|
||||
|
@ -412,7 +415,7 @@ impl<'a> Text<'a> {
|
|||
/// Set alignment to the whole text.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{layout::Alignment, text::Text};
|
||||
/// use ratatui_core::{layout::Alignment, text::Text};
|
||||
///
|
||||
/// let mut text = Text::from("Hi, what's up?");
|
||||
/// assert_eq!(None, text.alignment);
|
||||
|
@ -425,7 +428,7 @@ impl<'a> Text<'a> {
|
|||
/// Set a default alignment and override it on a per line basis.
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{
|
||||
/// use ratatui_core::{
|
||||
/// layout::Alignment,
|
||||
/// text::{Line, Text},
|
||||
/// };
|
||||
|
@ -466,7 +469,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// use ratatui_core::text::Text;
|
||||
///
|
||||
/// let text = Text::from("Hi, what's up?").left_aligned();
|
||||
/// ```
|
||||
|
@ -486,7 +489,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// use ratatui_core::text::Text;
|
||||
///
|
||||
/// let text = Text::from("Hi, what's up?").centered();
|
||||
/// ```
|
||||
|
@ -506,7 +509,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// use ratatui_core::text::Text;
|
||||
///
|
||||
/// let text = Text::from("Hi, what's up?").right_aligned();
|
||||
/// ```
|
||||
|
@ -533,7 +536,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::{Line, Span, Text};
|
||||
/// use ratatui_core::text::{Line, Span, Text};
|
||||
///
|
||||
/// let mut text = Text::from("Hello, world!");
|
||||
/// text.push_line(Line::from("How are you?"));
|
||||
|
@ -552,7 +555,7 @@ impl<'a> Text<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::{Span, Text};
|
||||
/// use ratatui_core::text::{Span, Text};
|
||||
///
|
||||
/// let mut text = Text::from("Hello, world!");
|
||||
/// text.push_span(Span::from("How are you?"));
|
693
ratatui-core/src/widgets.rs
Normal file
693
ratatui-core/src/widgets.rs
Normal file
|
@ -0,0 +1,693 @@
|
|||
#![warn(missing_docs)]
|
||||
//! The `widgets` module contains the `Widget` and `StatefulWidget` traits, which are used to
|
||||
//! render UI elements on the screen.
|
||||
use crate::{buffer::Buffer, layout::Rect, style::Style};
|
||||
|
||||
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
|
||||
///
|
||||
/// Prior to Ratatui 0.26.0, widgets generally were created for each frame as they were consumed
|
||||
/// during rendering. This meant that they were not meant to be stored but used as *commands* to
|
||||
/// draw common figures in the UI.
|
||||
///
|
||||
/// Starting with Ratatui 0.26.0, all the internal widgets implement Widget for a reference to
|
||||
/// themselves. This allows you to store a reference to a widget and render it later. Widget crates
|
||||
/// should consider also doing this to allow for more flexibility in how widgets are used.
|
||||
///
|
||||
/// In Ratatui 0.26.0, we also added an unstable [`WidgetRef`] trait and implemented this on all the
|
||||
/// internal widgets. In addition to the above benefit of rendering references to widgets, this also
|
||||
/// allows you to render boxed widgets. This is useful when you want to store a collection of
|
||||
/// widgets with different types. You can then iterate over the collection and render each widget.
|
||||
/// See <https://github.com/ratatui/ratatui/issues/1287> for more information.
|
||||
///
|
||||
/// In general where you expect a widget to immutably work on its data, we recommended to implement
|
||||
/// `Widget` for a reference to the widget (`impl Widget for &MyWidget`). If you need to store state
|
||||
/// between draw calls, implement `StatefulWidget` if you want the Widget to be immutable, or
|
||||
/// implement `Widget` for a mutable reference to the widget (`impl Widget for &mut MyWidget`) if
|
||||
/// you want the widget to be mutable. The mutable widget pattern is used infrequently in apps, but
|
||||
/// can be quite useful.
|
||||
///
|
||||
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
|
||||
/// Widget is also implemented for `&str` and `String` types.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use ratatui::{
|
||||
/// backend::TestBackend,
|
||||
/// widgets::{Clear, Widget},
|
||||
/// Terminal,
|
||||
/// };
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
///
|
||||
/// terminal.draw(|frame| {
|
||||
/// frame.render_widget(Clear, frame.area());
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// It's common to render widgets inside other widgets:
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
///
|
||||
/// struct MyWidget;
|
||||
///
|
||||
/// impl Widget for MyWidget {
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Hello").render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Widget {
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom widget.
|
||||
fn render(self, area: Rect, buf: &mut Buffer)
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// A `StatefulWidget` is a widget that can take advantage of some local state to remember things
|
||||
/// between two draw calls.
|
||||
///
|
||||
/// Most widgets can be drawn directly based on the input parameters. However, some features may
|
||||
/// require some kind of associated state to be implemented.
|
||||
///
|
||||
/// For example, the `List` widget can highlight the item currently selected. This can be translated
|
||||
/// in an offset, which is the number of elements to skip in order to have the selected item within
|
||||
/// the viewport currently allocated to this widget. The widget can therefore only provide the
|
||||
/// following behavior: whenever the selected item is out of the viewport scroll to a predefined
|
||||
/// position (making the selected item the last viewable item or the one in the middle for example).
|
||||
/// Nonetheless, if the widget has access to the last computed offset then it can implement a
|
||||
/// natural scrolling experience where the last offset is reused until the selected item is out of
|
||||
/// the viewport.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use std::io;
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// backend::TestBackend,
|
||||
/// widgets::{List, ListItem, ListState, StatefulWidget, Widget},
|
||||
/// Terminal,
|
||||
/// };
|
||||
///
|
||||
/// // Let's say we have some events to display.
|
||||
/// struct Events {
|
||||
/// // `items` is the state managed by your application.
|
||||
/// items: Vec<String>,
|
||||
/// // `state` is the state that can be modified by the UI. It stores the index of the selected
|
||||
/// // item as well as the offset computed during the previous draw call (used to implement
|
||||
/// // natural scrolling).
|
||||
/// state: ListState,
|
||||
/// }
|
||||
///
|
||||
/// impl Events {
|
||||
/// fn new(items: Vec<String>) -> Events {
|
||||
/// Events {
|
||||
/// items,
|
||||
/// state: ListState::default(),
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// pub fn set_items(&mut self, items: Vec<String>) {
|
||||
/// self.items = items;
|
||||
/// // We reset the state as the associated items have changed. This effectively reset
|
||||
/// // the selection as well as the stored offset.
|
||||
/// self.state = ListState::default();
|
||||
/// }
|
||||
///
|
||||
/// // Select the next item. This will not be reflected until the widget is drawn in the
|
||||
/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
|
||||
/// pub fn next(&mut self) {
|
||||
/// let i = match self.state.selected() {
|
||||
/// Some(i) => {
|
||||
/// if i >= self.items.len() - 1 {
|
||||
/// 0
|
||||
/// } else {
|
||||
/// i + 1
|
||||
/// }
|
||||
/// }
|
||||
/// None => 0,
|
||||
/// };
|
||||
/// self.state.select(Some(i));
|
||||
/// }
|
||||
///
|
||||
/// // Select the previous item. This will not be reflected until the widget is drawn in the
|
||||
/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
|
||||
/// pub fn previous(&mut self) {
|
||||
/// let i = match self.state.selected() {
|
||||
/// Some(i) => {
|
||||
/// if i == 0 {
|
||||
/// self.items.len() - 1
|
||||
/// } else {
|
||||
/// i - 1
|
||||
/// }
|
||||
/// }
|
||||
/// None => 0,
|
||||
/// };
|
||||
/// self.state.select(Some(i));
|
||||
/// }
|
||||
///
|
||||
/// // Unselect the currently selected item if any. The implementation of `ListState` makes
|
||||
/// // sure that the stored offset is also reset.
|
||||
/// pub fn unselect(&mut self) {
|
||||
/// self.state.select(None);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
///
|
||||
/// let mut events = Events::new(vec![String::from("Item 1"), String::from("Item 2")]);
|
||||
///
|
||||
/// loop {
|
||||
/// terminal.draw(|f| {
|
||||
/// // The items managed by the application are transformed to something
|
||||
/// // that is understood by ratatui.
|
||||
/// let items: Vec<ListItem> = events
|
||||
/// .items
|
||||
/// .iter()
|
||||
/// .map(|i| ListItem::new(i.as_str()))
|
||||
/// .collect();
|
||||
/// // The `List` widget is then built with those items.
|
||||
/// let list = List::new(items);
|
||||
/// // Finally the widget is rendered using the associated state. `events.state` is
|
||||
/// // effectively the only thing that we will "remember" from this draw call.
|
||||
/// f.render_stateful_widget(list, f.size(), &mut events.state);
|
||||
/// });
|
||||
///
|
||||
/// // In response to some input events or an external http request or whatever:
|
||||
/// events.next();
|
||||
/// }
|
||||
/// ```
|
||||
pub trait StatefulWidget {
|
||||
/// State associated with the stateful widget.
|
||||
///
|
||||
/// If you don't need this then you probably want to implement [`Widget`] instead.
|
||||
type State;
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom stateful widget.
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
|
||||
}
|
||||
|
||||
/// A `WidgetRef` is a trait that allows rendering a widget by reference.
|
||||
///
|
||||
/// This trait is useful when you want to store a reference to a widget and render it later. It also
|
||||
/// allows you to render boxed widgets.
|
||||
///
|
||||
/// Boxed widgets allow you to store widgets with a type that is not known at compile time. This is
|
||||
/// useful when you want to store a collection of widgets with different types. You can then iterate
|
||||
/// over the collection and render each widget.
|
||||
///
|
||||
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal widgets. It
|
||||
/// is currently marked as unstable as we are still evaluating the API and may make changes in the
|
||||
/// future. See <https://github.com/ratatui/ratatui/issues/1287> for more information.
|
||||
///
|
||||
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
|
||||
///
|
||||
/// A blanket implementation of `WidgetRef` for `Option<W>` where `W` implements `WidgetRef` is
|
||||
/// provided. This is a convenience approach to make it easier to attach child widgets to parent
|
||||
/// widgets. It allows you to render an optional widget by reference.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Line,
|
||||
/// widgets::{Widget, WidgetRef},
|
||||
/// };
|
||||
///
|
||||
/// struct Greeting;
|
||||
///
|
||||
/// struct Farewell;
|
||||
///
|
||||
/// impl WidgetRef for Greeting {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Hello").render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// Only needed for backwards compatibility
|
||||
/// impl Widget for Greeting {
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
/// self.render_ref(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl WidgetRef for Farewell {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Goodbye").right_aligned().render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// Only needed for backwards compatibility
|
||||
/// impl Widget for Farewell {
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
/// self.render_ref(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let greeting = Greeting;
|
||||
/// let farewell = Farewell;
|
||||
///
|
||||
/// // these calls do not consume the widgets, so they can be used again later
|
||||
/// greeting.render_ref(area, buf);
|
||||
/// farewell.render_ref(area, buf);
|
||||
///
|
||||
/// // a collection of widgets with different types
|
||||
/// let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(greeting), Box::new(farewell)];
|
||||
/// for widget in widgets {
|
||||
/// widget.render_ref(area, buf);
|
||||
/// }
|
||||
/// # }
|
||||
/// # }
|
||||
/// ```
|
||||
#[instability::unstable(feature = "widget-ref")]
|
||||
pub trait WidgetRef {
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom widget.
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer);
|
||||
}
|
||||
|
||||
/// This allows you to render a widget by reference.
|
||||
impl<W: WidgetRef> Widget for &W {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// A blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
|
||||
///
|
||||
/// This is a convenience implementation that makes it easy to attach child widgets to parent
|
||||
/// widgets. It allows you to render an optional widget by reference.
|
||||
///
|
||||
/// The internal widgets use this pattern to render the optional `Block` widgets that are included
|
||||
/// on most widgets.
|
||||
/// Blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Line,
|
||||
/// widgets::{Widget, WidgetRef},
|
||||
/// };
|
||||
///
|
||||
/// struct Parent {
|
||||
/// child: Option<Child>,
|
||||
/// }
|
||||
///
|
||||
/// struct Child;
|
||||
///
|
||||
/// impl WidgetRef for Child {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Hello from child").render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl WidgetRef for Parent {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
/// self.child.render_ref(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
impl<W: WidgetRef> WidgetRef for Option<W> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
if let Some(widget) = self {
|
||||
widget.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `StatefulWidgetRef` is a trait that allows rendering a stateful widget by reference.
|
||||
///
|
||||
/// This is the stateful equivalent of `WidgetRef`. It is useful when you want to store a reference
|
||||
/// to a stateful widget and render it later. It also allows you to render boxed stateful widgets.
|
||||
///
|
||||
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal stateful
|
||||
/// widgets. It is currently marked as unstable as we are still evaluating the API and may make
|
||||
/// changes in the future. See <https://github.com/ratatui/ratatui/issues/1287> for more
|
||||
/// information.
|
||||
///
|
||||
/// A blanket implementation of `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef`
|
||||
/// is provided.
|
||||
///
|
||||
/// See the documentation for [`WidgetRef`] for more information on boxed widgets.
|
||||
/// See the documentation for [`StatefulWidget`] for more information on stateful widgets.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui_core::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::Stylize,
|
||||
/// text::Line,
|
||||
/// widgets::{StatefulWidget, StatefulWidgetRef, Widget},
|
||||
/// };
|
||||
///
|
||||
/// struct PersonalGreeting;
|
||||
///
|
||||
/// impl StatefulWidgetRef for PersonalGreeting {
|
||||
/// type State = String;
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
/// Line::raw(format!("Hello {}", state)).render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl StatefulWidget for PersonalGreeting {
|
||||
/// type State = String;
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
/// (&self).render_ref(area, buf, state);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let widget = PersonalGreeting;
|
||||
/// let mut state = "world".to_string();
|
||||
/// widget.render(area, buf, &mut state);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[instability::unstable(feature = "widget-ref")]
|
||||
pub trait StatefulWidgetRef {
|
||||
/// State associated with the stateful widget.
|
||||
///
|
||||
/// If you don't need this then you probably want to implement [`WidgetRef`] instead.
|
||||
type State;
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom stateful widget.
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
|
||||
}
|
||||
|
||||
// Note: while StatefulWidgetRef is marked as unstable, the blanket implementation of StatefulWidget
|
||||
// cannot be implemented as W::State is effectively pub(crate) and not accessible from outside the
|
||||
// crate. Once stabilized, this blanket implementation can be added and the specific implementations
|
||||
// on Table and List can be removed.
|
||||
//
|
||||
// /// Blanket implementation of `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef`.
|
||||
// ///
|
||||
// /// This allows you to render a stateful widget by reference.
|
||||
// impl<W: StatefulWidgetRef> StatefulWidget for &W {
|
||||
// type State = W::State;
|
||||
// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
// StatefulWidgetRef::render_ref(self, area, buf, state);
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Renders a string slice as a widget.
|
||||
///
|
||||
/// This implementation allows a string slice (`&str`) to act as a widget, meaning it can be drawn
|
||||
/// onto a [`Buffer`] in a specified [`Rect`]. The slice represents a static string which can be
|
||||
/// rendered by reference, thereby avoiding the need for string cloning or ownership transfer when
|
||||
/// drawing the text to the screen.
|
||||
impl Widget for &str {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the ability to render a string slice by reference.
|
||||
///
|
||||
/// This trait implementation ensures that a string slice, which is an immutable view over a
|
||||
/// `String`, can be drawn on demand without requiring ownership of the string itself. It utilizes
|
||||
/// the default text style when rendering onto the provided [`Buffer`] at the position defined by
|
||||
/// [`Rect`].
|
||||
impl WidgetRef for &str {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a `String` object as a widget.
|
||||
///
|
||||
/// This implementation enables an owned `String` to be treated as a widget, which can be rendered
|
||||
/// on a [`Buffer`] within the bounds of a given [`Rect`].
|
||||
impl Widget for String {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the ability to render a `String` by reference.
|
||||
///
|
||||
/// This trait allows for a `String` to be rendered onto the [`Buffer`], similarly using the default
|
||||
/// style settings. It ensures that an owned `String` can be rendered efficiently by reference,
|
||||
/// without the need to give up ownership of the underlying text.
|
||||
impl WidgetRef for String {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::text::Line;
|
||||
|
||||
#[fixture]
|
||||
fn buf() -> Buffer {
|
||||
Buffer::empty(Rect::new(0, 0, 20, 1))
|
||||
}
|
||||
|
||||
mod widget {
|
||||
use super::*;
|
||||
|
||||
struct Greeting;
|
||||
|
||||
impl Widget for Greeting {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Hello").render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer) {
|
||||
let widget = Greeting;
|
||||
widget.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod widget_ref {
|
||||
use super::*;
|
||||
|
||||
struct Greeting;
|
||||
struct Farewell;
|
||||
|
||||
impl WidgetRef for Greeting {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Hello").render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for Farewell {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Goodbye").right_aligned().render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer) {
|
||||
let widget = Greeting;
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
/// Ensure that the blanket implementation of `Widget` for `&W` where `W` implements
|
||||
/// `WidgetRef` works as expected.
|
||||
#[rstest]
|
||||
fn blanket_render(mut buf: Buffer) {
|
||||
let widget = &Greeting;
|
||||
widget.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn box_render_ref(mut buf: Buffer) {
|
||||
let widget: Box<dyn WidgetRef> = Box::new(Greeting);
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn vec_box_render(mut buf: Buffer) {
|
||||
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(Greeting), Box::new(Farewell)];
|
||||
for widget in widgets {
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
}
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"]));
|
||||
}
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn state() -> String {
|
||||
"world".to_string()
|
||||
}
|
||||
|
||||
mod stateful_widget {
|
||||
use super::*;
|
||||
|
||||
struct PersonalGreeting;
|
||||
|
||||
impl StatefulWidget for PersonalGreeting {
|
||||
type State = String;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
Line::from(format!("Hello {state}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer, mut state: String) {
|
||||
let widget = PersonalGreeting;
|
||||
widget.render(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod stateful_widget_ref {
|
||||
use super::*;
|
||||
|
||||
struct PersonalGreeting;
|
||||
|
||||
impl StatefulWidgetRef for PersonalGreeting {
|
||||
type State = String;
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
Line::from(format!("Hello {state}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer, mut state: String) {
|
||||
let widget = PersonalGreeting;
|
||||
widget.render_ref(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
|
||||
// Note this cannot be tested until the blanket implementation of StatefulWidget for &W
|
||||
// where W implements StatefulWidgetRef is added. (see the comment in the blanket
|
||||
// implementation for more).
|
||||
// /// This test is to ensure that the blanket implementation of `StatefulWidget` for `&W`
|
||||
// where /// `W` implements `StatefulWidgetRef` works as expected.
|
||||
// #[rstest]
|
||||
// fn stateful_widget_blanket_render(mut buf: Buffer, mut state: String) {
|
||||
// let widget = &PersonalGreeting;
|
||||
// widget.render(buf.area, &mut buf, &mut state);
|
||||
// assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
// }
|
||||
|
||||
#[rstest]
|
||||
fn box_render_render(mut buf: Buffer, mut state: String) {
|
||||
let widget = Box::new(PersonalGreeting);
|
||||
widget.render_ref(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod option_widget_ref {
|
||||
use super::*;
|
||||
|
||||
struct Greeting;
|
||||
|
||||
impl WidgetRef for Greeting {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Hello").render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref_some(mut buf: Buffer) {
|
||||
let widget = Some(Greeting);
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref_none(mut buf: Buffer) {
|
||||
let widget: Option<Greeting> = None;
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([" "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod str {
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer) {
|
||||
"hello world".render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_area(mut buf: Buffer) {
|
||||
let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
|
||||
"hello world, just hello".render(area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer) {
|
||||
"hello world".render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn option_render(mut buf: Buffer) {
|
||||
Some("hello world").render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn option_render_ref(mut buf: Buffer) {
|
||||
Some("hello world").render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod string {
|
||||
use super::*;
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer) {
|
||||
String::from("hello world").render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_area(mut buf: Buffer) {
|
||||
let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
|
||||
String::from("hello world, just hello").render(area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer) {
|
||||
String::from("hello world").render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn option_render(mut buf: Buffer) {
|
||||
Some(String::from("hello world")).render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn option_render_ref(mut buf: Buffer) {
|
||||
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,25 +15,21 @@ edition.workspace = true
|
|||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.3"
|
||||
cassowary = "0.3"
|
||||
compact_str = "0.8.0"
|
||||
bitflags.workspace = true
|
||||
crossterm = { version = "0.28.1", optional = true }
|
||||
document-features = { version = "0.2.7", optional = true }
|
||||
indoc = "2"
|
||||
instability = "0.3.1"
|
||||
itertools = "0.13"
|
||||
lru = "0.12.0"
|
||||
paste = "1.0.2"
|
||||
instability.workspace = true
|
||||
itertools.workspace = true
|
||||
palette = { version = "0.7.6", optional = true }
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
ratatui-core = { workspace = true, features = ["unstable-widget-ref"] }
|
||||
serde = { workspace = true, optional = true }
|
||||
strum.workspace = true
|
||||
termwiz = { version = "0.22.0", optional = true }
|
||||
time = { version = "0.3.11", optional = true, features = ["local-offset"] }
|
||||
unicode-segmentation = "1.10"
|
||||
unicode-truncate = "1"
|
||||
unicode-segmentation.workspace = true
|
||||
# See <https://github.com/ratatui/ratatui/issues/1271> for information about why we pin unicode-width
|
||||
unicode-width = "=0.2.0"
|
||||
unicode-width.workspace = true
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
# termion is not supported on Windows
|
||||
|
@ -124,13 +120,13 @@ termwiz = ["dep:termwiz"]
|
|||
#! The following optional features are available for all backends:
|
||||
## enables serialization and deserialization of style and color types using the [`serde`] crate.
|
||||
## This is useful if you want to save themes to a file.
|
||||
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
|
||||
serde = ["dep:serde", "ratatui-core/serde"]
|
||||
|
||||
## enables the [`border!`] macro.
|
||||
macros = []
|
||||
|
||||
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
|
||||
palette = ["dep:palette"]
|
||||
palette = ["ratatui-core/palette", "dep:palette"]
|
||||
|
||||
## Use terminal scrolling regions to make some operations less prone to
|
||||
## flickering. (i.e. Terminal::insert_before).
|
||||
|
@ -149,7 +145,7 @@ widget-calendar = ["dep:time"]
|
|||
## enables the backend code that sets the underline color.
|
||||
## Underline color is only supported by the [`CrosstermBackend`](backend::CrosstermBackend) backend,
|
||||
## and is not supported on Windows 7.
|
||||
underline-color = ["dep:crossterm"]
|
||||
underline-color = ["dep:crossterm", "ratatui-core/underline-color"]
|
||||
|
||||
#! The following features are unstable and may change in the future:
|
||||
|
||||
|
@ -166,9 +162,12 @@ unstable = [
|
|||
## See [Issue 293](https://github.com/ratatui/ratatui/issues/293) for more details.
|
||||
unstable-rendered-line-info = []
|
||||
|
||||
## Enables the [`WidgetRef`](widgets::WidgetRef) and [`StatefulWidgetRef`](widgets::StatefulWidgetRef) traits which are experimental and may change in
|
||||
## the future.
|
||||
unstable-widget-ref = []
|
||||
## enables the [`WidgetRef`] and [`StatefulWidgetRef`] traits which are experimental and may change
|
||||
## in the future.
|
||||
##
|
||||
## [`WidgetRef`]: widgets::WidgetRef
|
||||
## [`StatefulWidgetRef`]: widgets::StatefulWidgetRef
|
||||
unstable-widget-ref = ["ratatui-core/unstable-widget-ref"]
|
||||
|
||||
## Enables getting access to backends' writers.
|
||||
unstable-backend-writer = []
|
||||
|
|
|
@ -10,13 +10,13 @@ use std::{error::Error, io};
|
|||
use crate::{
|
||||
backend::{Backend, WindowSize},
|
||||
buffer::Cell,
|
||||
layout::Size,
|
||||
layout::{Position, Size},
|
||||
style::{Color, Modifier, Style},
|
||||
termwiz::{
|
||||
caps::Capabilities,
|
||||
cell::{AttributeChange, Blink, CellAttributes, Intensity, Underline},
|
||||
color::{AnsiColor, ColorAttribute, ColorSpec, LinearRgba, RgbColor, SrgbaTuple},
|
||||
surface::{Change, CursorVisibility, Position},
|
||||
surface::{Change, CursorVisibility, Position as TermwizPosition},
|
||||
terminal::{buffered::BufferedTerminal, ScreenSize, SystemTerminal, Terminal},
|
||||
},
|
||||
};
|
||||
|
@ -117,8 +117,8 @@ impl Backend for TermwizBackend {
|
|||
for (x, y, cell) in content {
|
||||
self.buffered_terminal.add_changes(vec![
|
||||
Change::CursorPosition {
|
||||
x: Position::Absolute(x as usize),
|
||||
y: Position::Absolute(y as usize),
|
||||
x: TermwizPosition::Absolute(x as usize),
|
||||
y: TermwizPosition::Absolute(y as usize),
|
||||
},
|
||||
Change::Attribute(AttributeChange::Foreground(cell.fg.into_termwiz())),
|
||||
Change::Attribute(AttributeChange::Background(cell.bg.into_termwiz())),
|
||||
|
@ -192,19 +192,16 @@ impl Backend for TermwizBackend {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_cursor_position(&mut self) -> io::Result<crate::layout::Position> {
|
||||
fn get_cursor_position(&mut self) -> io::Result<Position> {
|
||||
let (x, y) = self.buffered_terminal.cursor_position();
|
||||
Ok((x as u16, y as u16).into())
|
||||
Ok(Position::new(x as u16, y as u16))
|
||||
}
|
||||
|
||||
fn set_cursor_position<P: Into<crate::layout::Position>>(
|
||||
&mut self,
|
||||
position: P,
|
||||
) -> io::Result<()> {
|
||||
let crate::layout::Position { x, y } = position.into();
|
||||
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
|
||||
let Position { x, y } = position.into();
|
||||
self.buffered_terminal.add_change(Change::CursorPosition {
|
||||
x: Position::Absolute(x as usize),
|
||||
y: Position::Absolute(y as usize),
|
||||
x: TermwizPosition::Absolute(x as usize),
|
||||
y: TermwizPosition::Absolute(y as usize),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -144,7 +144,7 @@ impl TestBackend {
|
|||
#[track_caller]
|
||||
pub fn assert_buffer(&self, expected: &Buffer) {
|
||||
// TODO: use assert_eq!()
|
||||
crate::assert_buffer_eq!(&self.buffer, expected);
|
||||
ratatui_core::assert_buffer_eq!(&self.buffer, expected);
|
||||
}
|
||||
|
||||
/// Asserts that the `TestBackend`'s scrollback buffer is equal to the expected buffer.
|
||||
|
|
|
@ -344,11 +344,9 @@ pub use termion;
|
|||
pub use termwiz;
|
||||
|
||||
pub mod backend;
|
||||
pub mod buffer;
|
||||
pub mod layout;
|
||||
pub use ratatui_core::{buffer, layout};
|
||||
pub mod prelude;
|
||||
pub mod style;
|
||||
pub mod symbols;
|
||||
pub use ratatui_core::{style, symbols};
|
||||
mod terminal;
|
||||
pub mod text;
|
||||
pub use ratatui_core::text;
|
||||
pub mod widgets;
|
||||
|
|
|
@ -39,6 +39,10 @@ mod sparkline;
|
|||
mod table;
|
||||
mod tabs;
|
||||
|
||||
pub use ratatui_core::widgets::{StatefulWidget, Widget};
|
||||
#[instability::unstable(feature = "widget-ref")]
|
||||
pub use ratatui_core::widgets::{StatefulWidgetRef, WidgetRef};
|
||||
|
||||
pub use self::{
|
||||
barchart::{Bar, BarChart, BarGroup},
|
||||
block::{Block, BorderType, Padding},
|
||||
|
@ -54,693 +58,3 @@ pub use self::{
|
|||
table::{Cell, HighlightSpacing, Row, Table, TableState},
|
||||
tabs::Tabs,
|
||||
};
|
||||
use crate::{buffer::Buffer, layout::Rect, style::Style};
|
||||
|
||||
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
|
||||
///
|
||||
/// Prior to Ratatui 0.26.0, widgets generally were created for each frame as they were consumed
|
||||
/// during rendering. This meant that they were not meant to be stored but used as *commands* to
|
||||
/// draw common figures in the UI.
|
||||
///
|
||||
/// Starting with Ratatui 0.26.0, all the internal widgets implement Widget for a reference to
|
||||
/// themselves. This allows you to store a reference to a widget and render it later. Widget crates
|
||||
/// should consider also doing this to allow for more flexibility in how widgets are used.
|
||||
///
|
||||
/// In Ratatui 0.26.0, we also added an unstable [`WidgetRef`] trait and implemented this on all the
|
||||
/// internal widgets. In addition to the above benefit of rendering references to widgets, this also
|
||||
/// allows you to render boxed widgets. This is useful when you want to store a collection of
|
||||
/// widgets with different types. You can then iterate over the collection and render each widget.
|
||||
/// See <https://github.com/ratatui/ratatui/issues/1287> for more information.
|
||||
///
|
||||
/// In general where you expect a widget to immutably work on its data, we recommended to implement
|
||||
/// `Widget` for a reference to the widget (`impl Widget for &MyWidget`). If you need to store state
|
||||
/// between draw calls, implement `StatefulWidget` if you want the Widget to be immutable, or
|
||||
/// implement `Widget` for a mutable reference to the widget (`impl Widget for &mut MyWidget`) if
|
||||
/// you want the widget to be mutable. The mutable widget pattern is used infrequently in apps, but
|
||||
/// can be quite useful.
|
||||
///
|
||||
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
|
||||
/// Widget is also implemented for `&str` and `String` types.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::{
|
||||
/// backend::TestBackend,
|
||||
/// widgets::{Clear, Widget},
|
||||
/// Terminal,
|
||||
/// };
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
///
|
||||
/// terminal.draw(|frame| {
|
||||
/// frame.render_widget(Clear, frame.area());
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// It's common to render widgets inside other widgets:
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
///
|
||||
/// struct MyWidget;
|
||||
///
|
||||
/// impl Widget for MyWidget {
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Hello").render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Widget {
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom widget.
|
||||
fn render(self, area: Rect, buf: &mut Buffer)
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// A `StatefulWidget` is a widget that can take advantage of some local state to remember things
|
||||
/// between two draw calls.
|
||||
///
|
||||
/// Most widgets can be drawn directly based on the input parameters. However, some features may
|
||||
/// require some kind of associated state to be implemented.
|
||||
///
|
||||
/// For example, the [`List`] widget can highlight the item currently selected. This can be
|
||||
/// translated in an offset, which is the number of elements to skip in order to have the selected
|
||||
/// item within the viewport currently allocated to this widget. The widget can therefore only
|
||||
/// provide the following behavior: whenever the selected item is out of the viewport scroll to a
|
||||
/// predefined position (making the selected item the last viewable item or the one in the middle
|
||||
/// for example). Nonetheless, if the widget has access to the last computed offset then it can
|
||||
/// implement a natural scrolling experience where the last offset is reused until the selected
|
||||
/// item is out of the viewport.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use std::io;
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// backend::TestBackend,
|
||||
/// widgets::{List, ListItem, ListState, StatefulWidget, Widget},
|
||||
/// Terminal,
|
||||
/// };
|
||||
///
|
||||
/// // Let's say we have some events to display.
|
||||
/// struct Events {
|
||||
/// // `items` is the state managed by your application.
|
||||
/// items: Vec<String>,
|
||||
/// // `state` is the state that can be modified by the UI. It stores the index of the selected
|
||||
/// // item as well as the offset computed during the previous draw call (used to implement
|
||||
/// // natural scrolling).
|
||||
/// state: ListState,
|
||||
/// }
|
||||
///
|
||||
/// impl Events {
|
||||
/// fn new(items: Vec<String>) -> Events {
|
||||
/// Events {
|
||||
/// items,
|
||||
/// state: ListState::default(),
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// pub fn set_items(&mut self, items: Vec<String>) {
|
||||
/// self.items = items;
|
||||
/// // We reset the state as the associated items have changed. This effectively reset
|
||||
/// // the selection as well as the stored offset.
|
||||
/// self.state = ListState::default();
|
||||
/// }
|
||||
///
|
||||
/// // Select the next item. This will not be reflected until the widget is drawn in the
|
||||
/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
|
||||
/// pub fn next(&mut self) {
|
||||
/// let i = match self.state.selected() {
|
||||
/// Some(i) => {
|
||||
/// if i >= self.items.len() - 1 {
|
||||
/// 0
|
||||
/// } else {
|
||||
/// i + 1
|
||||
/// }
|
||||
/// }
|
||||
/// None => 0,
|
||||
/// };
|
||||
/// self.state.select(Some(i));
|
||||
/// }
|
||||
///
|
||||
/// // Select the previous item. This will not be reflected until the widget is drawn in the
|
||||
/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
|
||||
/// pub fn previous(&mut self) {
|
||||
/// let i = match self.state.selected() {
|
||||
/// Some(i) => {
|
||||
/// if i == 0 {
|
||||
/// self.items.len() - 1
|
||||
/// } else {
|
||||
/// i - 1
|
||||
/// }
|
||||
/// }
|
||||
/// None => 0,
|
||||
/// };
|
||||
/// self.state.select(Some(i));
|
||||
/// }
|
||||
///
|
||||
/// // Unselect the currently selected item if any. The implementation of `ListState` makes
|
||||
/// // sure that the stored offset is also reset.
|
||||
/// pub fn unselect(&mut self) {
|
||||
/// self.state.select(None);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
///
|
||||
/// let mut events = Events::new(vec![String::from("Item 1"), String::from("Item 2")]);
|
||||
///
|
||||
/// loop {
|
||||
/// terminal.draw(|f| {
|
||||
/// // The items managed by the application are transformed to something
|
||||
/// // that is understood by ratatui.
|
||||
/// let items: Vec<ListItem> = events
|
||||
/// .items
|
||||
/// .iter()
|
||||
/// .map(|i| ListItem::new(i.as_str()))
|
||||
/// .collect();
|
||||
/// // The `List` widget is then built with those items.
|
||||
/// let list = List::new(items);
|
||||
/// // Finally the widget is rendered using the associated state. `events.state` is
|
||||
/// // effectively the only thing that we will "remember" from this draw call.
|
||||
/// f.render_stateful_widget(list, f.size(), &mut events.state);
|
||||
/// });
|
||||
///
|
||||
/// // In response to some input events or an external http request or whatever:
|
||||
/// events.next();
|
||||
/// }
|
||||
/// ```
|
||||
pub trait StatefulWidget {
|
||||
/// State associated with the stateful widget.
|
||||
///
|
||||
/// If you don't need this then you probably want to implement [`Widget`] instead.
|
||||
type State;
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom stateful widget.
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
|
||||
}
|
||||
|
||||
/// A `WidgetRef` is a trait that allows rendering a widget by reference.
|
||||
///
|
||||
/// This trait is useful when you want to store a reference to a widget and render it later. It also
|
||||
/// allows you to render boxed widgets.
|
||||
///
|
||||
/// Boxed widgets allow you to store widgets with a type that is not known at compile time. This is
|
||||
/// useful when you want to store a collection of widgets with different types. You can then iterate
|
||||
/// over the collection and render each widget.
|
||||
///
|
||||
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal widgets. It
|
||||
/// is currently marked as unstable as we are still evaluating the API and may make changes in the
|
||||
/// future. See <https://github.com/ratatui/ratatui/issues/1287> for more information.
|
||||
///
|
||||
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
|
||||
///
|
||||
/// A blanket implementation of `WidgetRef` for `Option<W>` where `W` implements `WidgetRef` is
|
||||
/// provided. This is a convenience approach to make it easier to attach child widgets to parent
|
||||
/// widgets. It allows you to render an optional widget by reference.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Line,
|
||||
/// widgets::{Widget, WidgetRef},
|
||||
/// };
|
||||
///
|
||||
/// struct Greeting;
|
||||
///
|
||||
/// struct Farewell;
|
||||
///
|
||||
/// impl WidgetRef for Greeting {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Hello").render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// Only needed for backwards compatibility
|
||||
/// impl Widget for Greeting {
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
/// self.render_ref(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl WidgetRef for Farewell {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Goodbye").right_aligned().render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// Only needed for backwards compatibility
|
||||
/// impl Widget for Farewell {
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
/// self.render_ref(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let greeting = Greeting;
|
||||
/// let farewell = Farewell;
|
||||
///
|
||||
/// // these calls do not consume the widgets, so they can be used again later
|
||||
/// greeting.render_ref(area, buf);
|
||||
/// farewell.render_ref(area, buf);
|
||||
///
|
||||
/// // a collection of widgets with different types
|
||||
/// let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(greeting), Box::new(farewell)];
|
||||
/// for widget in widgets {
|
||||
/// widget.render_ref(area, buf);
|
||||
/// }
|
||||
/// # }
|
||||
/// # }
|
||||
/// ```
|
||||
#[instability::unstable(feature = "widget-ref")]
|
||||
pub trait WidgetRef {
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom widget.
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer);
|
||||
}
|
||||
|
||||
/// This allows you to render a widget by reference.
|
||||
impl<W: WidgetRef> Widget for &W {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// A blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
|
||||
///
|
||||
/// This is a convenience implementation that makes it easy to attach child widgets to parent
|
||||
/// widgets. It allows you to render an optional widget by reference.
|
||||
///
|
||||
/// The internal widgets use this pattern to render the optional `Block` widgets that are included
|
||||
/// on most widgets.
|
||||
/// Blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// text::Line,
|
||||
/// widgets::{Widget, WidgetRef},
|
||||
/// };
|
||||
///
|
||||
/// struct Parent {
|
||||
/// child: Option<Child>,
|
||||
/// }
|
||||
///
|
||||
/// struct Child;
|
||||
///
|
||||
/// impl WidgetRef for Child {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
/// Line::raw("Hello from child").render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl WidgetRef for Parent {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
/// self.child.render_ref(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
impl<W: WidgetRef> WidgetRef for Option<W> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
if let Some(widget) = self {
|
||||
widget.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `StatefulWidgetRef` is a trait that allows rendering a stateful widget by reference.
|
||||
///
|
||||
/// This is the stateful equivalent of `WidgetRef`. It is useful when you want to store a reference
|
||||
/// to a stateful widget and render it later. It also allows you to render boxed stateful widgets.
|
||||
///
|
||||
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal stateful
|
||||
/// widgets. It is currently marked as unstable as we are still evaluating the API and may make
|
||||
/// changes in the future. See <https://github.com/ratatui/ratatui/issues/1287> for more
|
||||
/// information.
|
||||
///
|
||||
/// A blanket implementation of `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef`
|
||||
/// is provided.
|
||||
///
|
||||
/// See the documentation for [`WidgetRef`] for more information on boxed widgets.
|
||||
/// See the documentation for [`StatefulWidget`] for more information on stateful widgets.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "unstable-widget-ref")] {
|
||||
/// use ratatui::{
|
||||
/// buffer::Buffer,
|
||||
/// layout::Rect,
|
||||
/// style::Stylize,
|
||||
/// text::Line,
|
||||
/// widgets::{StatefulWidget, StatefulWidgetRef, Widget},
|
||||
/// };
|
||||
///
|
||||
/// struct PersonalGreeting;
|
||||
///
|
||||
/// impl StatefulWidgetRef for PersonalGreeting {
|
||||
/// type State = String;
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
/// Line::raw(format!("Hello {}", state)).render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl StatefulWidget for PersonalGreeting {
|
||||
/// type State = String;
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
/// (&self).render_ref(area, buf, state);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn render(area: Rect, buf: &mut Buffer) {
|
||||
/// let widget = PersonalGreeting;
|
||||
/// let mut state = "world".to_string();
|
||||
/// widget.render(area, buf, &mut state);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[instability::unstable(feature = "widget-ref")]
|
||||
pub trait StatefulWidgetRef {
|
||||
/// State associated with the stateful widget.
|
||||
///
|
||||
/// If you don't need this then you probably want to implement [`WidgetRef`] instead.
|
||||
type State;
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom stateful widget.
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
|
||||
}
|
||||
|
||||
// Note: while StatefulWidgetRef is marked as unstable, the blanket implementation of StatefulWidget
|
||||
// cannot be implemented as W::State is effectively pub(crate) and not accessible from outside the
|
||||
// crate. Once stabilized, this blanket implementation can be added and the specific implementations
|
||||
// on Table and List can be removed.
|
||||
//
|
||||
// /// Blanket implementation of `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef`.
|
||||
// ///
|
||||
// /// This allows you to render a stateful widget by reference.
|
||||
// impl<W: StatefulWidgetRef> StatefulWidget for &W {
|
||||
// type State = W::State;
|
||||
// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
// StatefulWidgetRef::render_ref(self, area, buf, state);
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Renders a string slice as a widget.
|
||||
///
|
||||
/// This implementation allows a string slice (`&str`) to act as a widget, meaning it can be drawn
|
||||
/// onto a [`Buffer`] in a specified [`Rect`]. The slice represents a static string which can be
|
||||
/// rendered by reference, thereby avoiding the need for string cloning or ownership transfer when
|
||||
/// drawing the text to the screen.
|
||||
impl Widget for &str {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the ability to render a string slice by reference.
|
||||
///
|
||||
/// This trait implementation ensures that a string slice, which is an immutable view over a
|
||||
/// `String`, can be drawn on demand without requiring ownership of the string itself. It utilizes
|
||||
/// the default text style when rendering onto the provided [`Buffer`] at the position defined by
|
||||
/// [`Rect`].
|
||||
impl WidgetRef for &str {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a `String` object as a widget.
|
||||
///
|
||||
/// This implementation enables an owned `String` to be treated as a widget, which can be rendered
|
||||
/// on a [`Buffer`] within the bounds of a given [`Rect`].
|
||||
impl Widget for String {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the ability to render a `String` by reference.
|
||||
///
|
||||
/// This trait allows for a `String` to be rendered onto the [`Buffer`], similarly using the default
|
||||
/// style settings. It ensures that an owned `String` can be rendered efficiently by reference,
|
||||
/// without the need to give up ownership of the underlying text.
|
||||
impl WidgetRef for String {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::text::Line;
|
||||
|
||||
#[fixture]
|
||||
fn buf() -> Buffer {
|
||||
Buffer::empty(Rect::new(0, 0, 20, 1))
|
||||
}
|
||||
|
||||
mod widget {
|
||||
use super::*;
|
||||
|
||||
struct Greeting;
|
||||
|
||||
impl Widget for Greeting {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Hello").render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer) {
|
||||
let widget = Greeting;
|
||||
widget.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod widget_ref {
|
||||
use super::*;
|
||||
|
||||
struct Greeting;
|
||||
struct Farewell;
|
||||
|
||||
impl WidgetRef for Greeting {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Hello").render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for Farewell {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Goodbye").right_aligned().render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer) {
|
||||
let widget = Greeting;
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
/// Ensure that the blanket implementation of `Widget` for `&W` where `W` implements
|
||||
/// `WidgetRef` works as expected.
|
||||
#[rstest]
|
||||
fn blanket_render(mut buf: Buffer) {
|
||||
let widget = &Greeting;
|
||||
widget.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn box_render_ref(mut buf: Buffer) {
|
||||
let widget: Box<dyn WidgetRef> = Box::new(Greeting);
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn vec_box_render(mut buf: Buffer) {
|
||||
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(Greeting), Box::new(Farewell)];
|
||||
for widget in widgets {
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
}
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"]));
|
||||
}
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn state() -> String {
|
||||
"world".to_string()
|
||||
}
|
||||
|
||||
mod stateful_widget {
|
||||
use super::*;
|
||||
|
||||
struct PersonalGreeting;
|
||||
|
||||
impl StatefulWidget for PersonalGreeting {
|
||||
type State = String;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
Line::from(format!("Hello {state}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer, mut state: String) {
|
||||
let widget = PersonalGreeting;
|
||||
widget.render(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod stateful_widget_ref {
|
||||
use super::*;
|
||||
|
||||
struct PersonalGreeting;
|
||||
|
||||
impl StatefulWidgetRef for PersonalGreeting {
|
||||
type State = String;
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
Line::from(format!("Hello {state}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer, mut state: String) {
|
||||
let widget = PersonalGreeting;
|
||||
widget.render_ref(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
|
||||
// Note this cannot be tested until the blanket implementation of StatefulWidget for &W
|
||||
// where W implements StatefulWidgetRef is added. (see the comment in the blanket
|
||||
// implementation for more).
|
||||
// /// This test is to ensure that the blanket implementation of `StatefulWidget` for `&W`
|
||||
// where /// `W` implements `StatefulWidgetRef` works as expected.
|
||||
// #[rstest]
|
||||
// fn stateful_widget_blanket_render(mut buf: Buffer, mut state: String) {
|
||||
// let widget = &PersonalGreeting;
|
||||
// widget.render(buf.area, &mut buf, &mut state);
|
||||
// assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
// }
|
||||
|
||||
#[rstest]
|
||||
fn box_render_render(mut buf: Buffer, mut state: String) {
|
||||
let widget = Box::new(PersonalGreeting);
|
||||
widget.render_ref(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod option_widget_ref {
|
||||
use super::*;
|
||||
|
||||
struct Greeting;
|
||||
|
||||
impl WidgetRef for Greeting {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Hello").render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref_some(mut buf: Buffer) {
|
||||
let widget = Some(Greeting);
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref_none(mut buf: Buffer) {
|
||||
let widget: Option<Greeting> = None;
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([" "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod str {
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer) {
|
||||
"hello world".render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_area(mut buf: Buffer) {
|
||||
let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
|
||||
"hello world, just hello".render(area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer) {
|
||||
"hello world".render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn option_render(mut buf: Buffer) {
|
||||
Some("hello world").render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn option_render_ref(mut buf: Buffer) {
|
||||
Some("hello world").render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
mod string {
|
||||
use super::*;
|
||||
#[rstest]
|
||||
fn render(mut buf: Buffer) {
|
||||
String::from("hello world").render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_area(mut buf: Buffer) {
|
||||
let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
|
||||
String::from("hello world, just hello").render(area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer) {
|
||||
String::from("hello world").render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn option_render(mut buf: Buffer) {
|
||||
Some(String::from("hello world")).render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn option_render_ref(mut buf: Buffer) {
|
||||
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["hello world "]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue