Compare commits
7 Commits
jm/statefu
...
v0.27.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f2f2cd6ab | ||
|
|
efa965e1e8 | ||
|
|
127d706ee4 | ||
|
|
1365620606 | ||
|
|
cd64367e24 | ||
|
|
07efde5233 | ||
|
|
308c1df649 |
@@ -55,20 +55,6 @@ This is a quick summary of the sections below:
|
||||
|
||||
## Unreleased
|
||||
|
||||
### `StatefulWidgetRef::render_ref` renamed to `render_stateful_ref` [#1184]
|
||||
|
||||
[#1184]: https://github.com/ratatui-org/ratatui/pull/1184
|
||||
|
||||
This change helps avoid collisions with `WidgetRef::render_ref`.
|
||||
|
||||
```diff
|
||||
trait StatefulWidgetRef {
|
||||
type State;
|
||||
- fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { }
|
||||
+ fn render_stateful_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { }
|
||||
}
|
||||
```
|
||||
|
||||
### Prelude items added / removed ([#1149])
|
||||
|
||||
The following items have been removed from the prelude:
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -61,6 +61,9 @@ rand = "0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
rstest = "0.21.0"
|
||||
serde_json = "1.0.109"
|
||||
tracing = "0.1.40"
|
||||
tracing-appender = "0.2.3"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
@@ -291,6 +294,11 @@ name = "line_gauge"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "hyperlink"
|
||||
required-features = ["crossterm", "unstable-widget-ref"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "list"
|
||||
required-features = ["crossterm"]
|
||||
@@ -348,6 +356,11 @@ name = "tabs"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "tracing"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "user_input"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
18
README.md
18
README.md
@@ -25,10 +25,10 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
[![Crate Badge]][Crate] [![Docs Badge]][API Docs] [![CI Badge]][CI Workflow] [![License
|
||||
Badge]](./LICENSE) [![Sponsors Badge]][GitHub Sponsors]<br>
|
||||
[![Codecov Badge]][Codecov] [![Deps.rs Badge]][Deps.rs] [![Discord Badge]][Discord Server]
|
||||
[![Matrix Badge]][Matrix]<br>
|
||||
[![Crate Badge]][Crate] [![Docs Badge]][API Docs] [![CI Badge]][CI Workflow] [![Deps.rs
|
||||
Badge]][Deps.rs]<br> [![Codecov Badge]][Codecov] [![License Badge]](./LICENSE) [![Sponsors
|
||||
Badge]][GitHub Sponsors]<br> [![Discord Badge]][Discord Server] [![Matrix Badge]][Matrix]
|
||||
[![Forum Badge]][Forum]<br>
|
||||
|
||||
[Ratatui Website] · [API Docs] · [Examples] · [Changelog] · [Breaking Changes]<br>
|
||||
[Contributing] · [Report a bug] · [Request a Feature] · [Create a Pull Request]
|
||||
@@ -67,6 +67,7 @@ terminal user interfaces and showcases the features of Ratatui, along with a hel
|
||||
## Other documentation
|
||||
|
||||
- [Ratatui Website] - explains the library's concepts and provides step-by-step tutorials
|
||||
- [Ratatui Forum][Forum] - a place to ask questions and discuss the library
|
||||
- [API Docs] - the full API documentation for the library on docs.rs.
|
||||
- [Examples] - a collection of examples that demonstrate how to use the library.
|
||||
- [Contributing] - Please read this if you are interested in contributing to the project.
|
||||
@@ -339,6 +340,8 @@ Running this example produces the following output:
|
||||
[Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square&logoColor=E05D44
|
||||
[Matrix Badge]: https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3
|
||||
[Matrix]: https://matrix.to/#/#ratatui:matrix.org
|
||||
[Forum Badge]: https://img.shields.io/discourse/likes?server=https%3A%2F%2Fforum.ratatui.rs&style=flat-square&logo=discourse&label=forum&color=C43AC3
|
||||
[Forum]: https://forum.ratatui.rs
|
||||
[Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square&color=1370D3
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
@@ -354,9 +357,10 @@ In order to organize ourselves, we currently use a [Discord server](https://disc
|
||||
feel free to join and come chat! There is also a [Matrix](https://matrix.org/) bridge available at
|
||||
[#ratatui:matrix.org](https://matrix.to/#/#ratatui:matrix.org).
|
||||
|
||||
While we do utilize Discord for coordinating, it's not essential for contributing.
|
||||
Our primary open-source workflow is centered around GitHub.
|
||||
For significant discussions, we rely on GitHub — please open an issue, a discussion or a PR.
|
||||
While we do utilize Discord for coordinating, it's not essential for contributing. We have recently
|
||||
launched the [Ratatui Forum][Forum], and our primary open-source workflow is centered around GitHub.
|
||||
For bugs and features, we rely on GitHub. Please [Report a bug], [Request a Feature] or [Create a
|
||||
Pull Request].
|
||||
|
||||
Please make sure you read the updated [contributing](./CONTRIBUTING.md) guidelines, especially if
|
||||
you are interested in working on a PR or issue opened in the previous repository.
|
||||
|
||||
151
examples/hyperlink.rs
Normal file
151
examples/hyperlink.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
//! # [Ratatui] Hyperlink examplew
|
||||
//!
|
||||
//! Shows how to use [OSC 8] to create hyperlinks in the terminal.
|
||||
//!
|
||||
//! The latest version of this example is available in the [examples] folder in the repository.
|
||||
//!
|
||||
//! Please note that the examples are designed to be run against the `main` branch of the Github
|
||||
//! repository. This means that you may not be able to compile with the latest release version on
|
||||
//! crates.io, or the one that you have installed locally.
|
||||
//!
|
||||
//! See the [examples readme] for more information on finding examples that match the version of the
|
||||
//! library you are using.
|
||||
//!
|
||||
//! [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
||||
//! [Ratatui]: https://github.com/ratatui-org/ratatui
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
use std::{
|
||||
io::{self, stdout, Stdout},
|
||||
panic,
|
||||
};
|
||||
|
||||
use color_eyre::{
|
||||
config::{EyreHook, HookBuilder, PanicHook},
|
||||
eyre, Result,
|
||||
};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::WidgetRef};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
init_error_handling()?;
|
||||
let mut terminal = init_terminal()?;
|
||||
let app = App::new();
|
||||
app.run(&mut terminal)?;
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct App {
|
||||
hyperlink: Hyperlink<'static>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> Self {
|
||||
let text = Line::from(vec!["Example ".into(), "hyperlink".blue()]);
|
||||
let hyperlink = Hyperlink::new(text, "https://example.com");
|
||||
Self { hyperlink }
|
||||
}
|
||||
|
||||
fn run(self, terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> io::Result<()> {
|
||||
loop {
|
||||
terminal.draw(|frame| frame.render_widget(&self.hyperlink, frame.size()))?;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if matches!(key.code, KeyCode::Char('q') | KeyCode::Esc) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A hyperlink widget that renders a hyperlink in the terminal using [OSC 8].
|
||||
///
|
||||
/// [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
||||
struct Hyperlink<'content> {
|
||||
text: Text<'content>,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl<'content> Hyperlink<'content> {
|
||||
fn new(text: impl Into<Text<'content>>, url: impl Into<String>) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
url: url.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for Hyperlink<'_> {
|
||||
fn render_ref(&self, area: Rect, buffer: &mut Buffer) {
|
||||
self.text.render_ref(area, buffer);
|
||||
|
||||
// this is a hacky workaround for https://github.com/ratatui-org/ratatui/issues/902, a bug
|
||||
// in the terminal code that incorrectly calculates the width of ANSI escape sequences. It
|
||||
// works by rendering the hyperlink as a series of 2-character chunks, which is the
|
||||
// calculated width of the hyperlink text.
|
||||
for (i, two_chars) in self
|
||||
.text
|
||||
.to_string()
|
||||
.chars()
|
||||
.chunks(2)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let text = two_chars.collect::<String>();
|
||||
let hyperlink = format!("\x1B]8;;{}\x07{}\x1B]8;;\x07", self.url, text);
|
||||
buffer
|
||||
.get_mut(area.x + i as u16 * 2, area.y)
|
||||
.set_symbol(hyperlink.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the terminal with raw mode and alternate screen.
|
||||
fn init_terminal() -> io::Result<Terminal<CrosstermBackend<Stdout>>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
Terminal::new(CrosstermBackend::new(stdout()))
|
||||
}
|
||||
|
||||
/// Restore the terminal to its original state.
|
||||
fn restore_terminal() -> io::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
execute!(stdout(), LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initialize error handling with color-eyre.
|
||||
pub fn init_error_handling() -> Result<()> {
|
||||
let (panic_hook, eyre_hook) = HookBuilder::default().into_hooks();
|
||||
set_panic_hook(panic_hook);
|
||||
set_error_hook(eyre_hook)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a panic hook that restores the terminal before printing the panic.
|
||||
fn set_panic_hook(panic_hook: PanicHook) {
|
||||
let panic_hook = panic_hook.into_panic_hook();
|
||||
panic::set_hook(Box::new(move |panic_info| {
|
||||
let _ = restore_terminal();
|
||||
panic_hook(panic_info);
|
||||
}));
|
||||
}
|
||||
|
||||
/// Install an error hook that restores the terminal before printing the error.
|
||||
fn set_error_hook(eyre_hook: EyreHook) -> Result<()> {
|
||||
let eyre_hook = eyre_hook.into_eyre_hook();
|
||||
eyre::set_hook(Box::new(move |error| {
|
||||
let _ = restore_terminal();
|
||||
eyre_hook(error)
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
154
examples/tracing.rs
Normal file
154
examples/tracing.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
//! # [Ratatui] Tracing example
|
||||
//!
|
||||
//! The latest version of this example is available in the [examples] folder in the repository.
|
||||
//!
|
||||
//! Please note that the examples are designed to be run against the `main` branch of the Github
|
||||
//! repository. This means that you may not be able to compile with the latest release version on
|
||||
//! crates.io, or the one that you have installed locally.
|
||||
//!
|
||||
//! See the [examples readme] for more information on finding examples that match the version of the
|
||||
//! library you are using.
|
||||
//!
|
||||
//! [Ratatui]: https://github.com/ratatui-org/ratatui
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
// A simple example demonstrating how to use the [tracing] with Ratatui to log to a file.
|
||||
//
|
||||
// This example demonstrates how to use the [tracing] crate with Ratatui to log to a file. The
|
||||
// example sets up a simple logger that logs to a file named `tracing.log` in the current directory.
|
||||
//
|
||||
// Run the example with `cargo run --example tracing` and then view the `tracing.log` file to see
|
||||
// the logs. To see more logs, you can run the example with `RUST_LOG=tracing=debug cargo run
|
||||
// --example`
|
||||
//
|
||||
// For a helpful widget that handles logging, see the [tui-logger] crate.
|
||||
//
|
||||
// [tracing]: https://crates.io/crates/tracing
|
||||
// [tui-logger]: https://crates.io/crates/tui-logger
|
||||
|
||||
use std::{fs::File, io::stdout, panic, time::Duration};
|
||||
|
||||
use color_eyre::{
|
||||
config::HookBuilder,
|
||||
eyre::{self, Context},
|
||||
Result,
|
||||
};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
terminal::Terminal,
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
use tracing::{debug, info, instrument, trace, Level};
|
||||
use tracing_appender::{non_blocking, non_blocking::WorkerGuard};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
init_error_hooks()?;
|
||||
let _guard = init_tracing()?;
|
||||
info!("Starting tracing example");
|
||||
|
||||
let mut terminal = init_terminal()?;
|
||||
let mut events = vec![]; // a buffer to store the recent events to display in the UI
|
||||
while !should_exit(&events) {
|
||||
handle_events(&mut events)?;
|
||||
terminal.draw(|frame| ui(frame, &events))?;
|
||||
}
|
||||
restore_terminal()?;
|
||||
info!("Exiting tracing example");
|
||||
println!("See the tracing.log file for the logs");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_exit(events: &[Event]) -> bool {
|
||||
events
|
||||
.iter()
|
||||
.any(|event| matches!(event, Event::Key(key) if key.code == KeyCode::Char('q')))
|
||||
}
|
||||
|
||||
/// Handle events and insert them into the events vector keeping only the last 10 events
|
||||
#[instrument(skip(events))]
|
||||
fn handle_events(events: &mut Vec<Event>) -> Result<()> {
|
||||
// Render the UI at least once every 100ms
|
||||
if event::poll(Duration::from_millis(100))? {
|
||||
let event = event::read()?;
|
||||
debug!(?event);
|
||||
events.insert(0, event);
|
||||
}
|
||||
events.truncate(10);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
fn ui(frame: &mut ratatui::Frame, events: &[Event]) {
|
||||
// To view this event, run the example with `RUST_LOG=tracing=debug cargo run --example tracing`
|
||||
trace!(frame_count = frame.count(), event_count = events.len());
|
||||
let area = frame.size();
|
||||
let events = events.iter().map(|e| format!("{e:?}")).collect::<Vec<_>>();
|
||||
let paragraph = Paragraph::new(events.join("\n"))
|
||||
.block(Block::bordered().title("Tracing example. Press 'q' to quit."));
|
||||
frame.render_widget(paragraph, area);
|
||||
}
|
||||
|
||||
/// Initialize the tracing subscriber to log to a file
|
||||
///
|
||||
/// This function initializes the tracing subscriber to log to a file named `tracing.log` in the
|
||||
/// current directory. The function returns a [`WorkerGuard`] that must be kept alive for the
|
||||
/// duration of the program to ensure that logs are flushed to the file on shutdown. The logs are
|
||||
/// written in a non-blocking fashion to ensure that the logs do not block the main thread.
|
||||
fn init_tracing() -> Result<WorkerGuard> {
|
||||
let file = File::create("tracing.log").wrap_err("failed to create tracing.log")?;
|
||||
let (non_blocking, guard) = non_blocking(file);
|
||||
|
||||
// By default, the subscriber is configured to log all events with a level of `DEBUG` or higher,
|
||||
// but this can be changed by setting the `RUST_LOG` environment variable.
|
||||
let env_filter = EnvFilter::builder()
|
||||
.with_default_directive(Level::DEBUG.into())
|
||||
.from_env_lossy();
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_writer(non_blocking)
|
||||
.with_env_filter(env_filter)
|
||||
.init();
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
/// Initialize the error hooks to ensure that the terminal is restored to a sane state before
|
||||
/// exiting
|
||||
fn init_error_hooks() -> Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn init_terminal() -> Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
debug!("terminal initialized");
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn restore_terminal() -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
debug!("terminal restored");
|
||||
Ok(())
|
||||
}
|
||||
15
examples/vhs/hyperlink.tape
Normal file
15
examples/vhs/hyperlink.tape
Normal file
@@ -0,0 +1,15 @@
|
||||
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
|
||||
# To run this script, install vhs and run `vhs ./examples/hello_world.tape`
|
||||
Output "target/hyperlink.gif"
|
||||
Set Theme "Aardvark Blue"
|
||||
Set Width 600
|
||||
Set Height 150
|
||||
Hide
|
||||
Type "cargo run --example=hyperlink --features=crossterm,unstable-widget-ref"
|
||||
Enter
|
||||
Sleep 2s
|
||||
Show
|
||||
Sleep 1s
|
||||
Hide
|
||||
Type "q"
|
||||
|
||||
12
examples/vhs/tracing.tape
Normal file
12
examples/vhs/tracing.tape
Normal file
@@ -0,0 +1,12 @@
|
||||
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
|
||||
# To run this script, install vhs and run `vhs ./examples/barchart.tape`
|
||||
Output "target/tracing.gif"
|
||||
Set Theme "Aardvark Blue"
|
||||
Set Width 1200
|
||||
Set Height 800
|
||||
Type "RUST_LOG=trace cargo run --example=tracing" Enter
|
||||
Sleep 1s
|
||||
Type @100ms "jjjjq"
|
||||
Sleep 1s
|
||||
Type "cat tracing.log" Enter
|
||||
Sleep 10s
|
||||
11
src/lib.rs
11
src/lib.rs
@@ -2,10 +2,10 @@
|
||||
//!
|
||||
//! <div align="center">
|
||||
//!
|
||||
//! [![Crate Badge]][Crate] [![Docs Badge]][API Docs] [![CI Badge]][CI Workflow] [![License
|
||||
//! Badge]](./LICENSE) [![Sponsors Badge]][GitHub Sponsors]<br>
|
||||
//! [![Codecov Badge]][Codecov] [![Deps.rs Badge]][Deps.rs] [![Discord Badge]][Discord Server]
|
||||
//! [![Matrix Badge]][Matrix]<br>
|
||||
//! [![Crate Badge]][Crate] [![Docs Badge]][API Docs] [![CI Badge]][CI Workflow] [![Deps.rs
|
||||
//! Badge]][Deps.rs]<br> [![Codecov Badge]][Codecov] [![License Badge]](./LICENSE) [![Sponsors
|
||||
//! Badge]][GitHub Sponsors]<br> [![Discord Badge]][Discord Server] [![Matrix Badge]][Matrix]
|
||||
//! [![Forum Badge]][Forum]<br>
|
||||
//!
|
||||
//! [Ratatui Website] · [API Docs] · [Examples] · [Changelog] · [Breaking Changes]<br>
|
||||
//! [Contributing] · [Report a bug] · [Request a Feature] · [Create a Pull Request]
|
||||
@@ -44,6 +44,7 @@
|
||||
//! ## Other documentation
|
||||
//!
|
||||
//! - [Ratatui Website] - explains the library's concepts and provides step-by-step tutorials
|
||||
//! - [Ratatui Forum][Forum] - a place to ask questions and discuss the library
|
||||
//! - [API Docs] - the full API documentation for the library on docs.rs.
|
||||
//! - [Examples] - a collection of examples that demonstrate how to use the library.
|
||||
//! - [Contributing] - Please read this if you are interested in contributing to the project.
|
||||
@@ -318,6 +319,8 @@
|
||||
//! [Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square&logoColor=E05D44
|
||||
//! [Matrix Badge]: https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3
|
||||
//! [Matrix]: https://matrix.to/#/#ratatui:matrix.org
|
||||
//! [Forum Badge]: https://img.shields.io/discourse/likes?server=https%3A%2F%2Fforum.ratatui.rs&style=flat-square&logo=discourse&label=forum&color=C43AC3
|
||||
//! [Forum]: https://forum.ratatui.rs
|
||||
//! [Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square&color=1370D3
|
||||
|
||||
// show the feature flags in the generated documentation
|
||||
|
||||
361
src/symbols.rs
361
src/symbols.rs
@@ -1,5 +1,8 @@
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
pub mod border;
|
||||
pub mod line;
|
||||
|
||||
pub mod block {
|
||||
pub const FULL: &str = "█";
|
||||
pub const SEVEN_EIGHTHS: &str = "▉";
|
||||
@@ -114,364 +117,6 @@ pub mod bar {
|
||||
};
|
||||
}
|
||||
|
||||
pub mod line {
|
||||
pub const VERTICAL: &str = "│";
|
||||
pub const DOUBLE_VERTICAL: &str = "║";
|
||||
pub const THICK_VERTICAL: &str = "┃";
|
||||
|
||||
pub const HORIZONTAL: &str = "─";
|
||||
pub const DOUBLE_HORIZONTAL: &str = "═";
|
||||
pub const THICK_HORIZONTAL: &str = "━";
|
||||
|
||||
pub const TOP_RIGHT: &str = "┐";
|
||||
pub const ROUNDED_TOP_RIGHT: &str = "╮";
|
||||
pub const DOUBLE_TOP_RIGHT: &str = "╗";
|
||||
pub const THICK_TOP_RIGHT: &str = "┓";
|
||||
|
||||
pub const TOP_LEFT: &str = "┌";
|
||||
pub const ROUNDED_TOP_LEFT: &str = "╭";
|
||||
pub const DOUBLE_TOP_LEFT: &str = "╔";
|
||||
pub const THICK_TOP_LEFT: &str = "┏";
|
||||
|
||||
pub const BOTTOM_RIGHT: &str = "┘";
|
||||
pub const ROUNDED_BOTTOM_RIGHT: &str = "╯";
|
||||
pub const DOUBLE_BOTTOM_RIGHT: &str = "╝";
|
||||
pub const THICK_BOTTOM_RIGHT: &str = "┛";
|
||||
|
||||
pub const BOTTOM_LEFT: &str = "└";
|
||||
pub const ROUNDED_BOTTOM_LEFT: &str = "╰";
|
||||
pub const DOUBLE_BOTTOM_LEFT: &str = "╚";
|
||||
pub const THICK_BOTTOM_LEFT: &str = "┗";
|
||||
|
||||
pub const VERTICAL_LEFT: &str = "┤";
|
||||
pub const DOUBLE_VERTICAL_LEFT: &str = "╣";
|
||||
pub const THICK_VERTICAL_LEFT: &str = "┫";
|
||||
|
||||
pub const VERTICAL_RIGHT: &str = "├";
|
||||
pub const DOUBLE_VERTICAL_RIGHT: &str = "╠";
|
||||
pub const THICK_VERTICAL_RIGHT: &str = "┣";
|
||||
|
||||
pub const HORIZONTAL_DOWN: &str = "┬";
|
||||
pub const DOUBLE_HORIZONTAL_DOWN: &str = "╦";
|
||||
pub const THICK_HORIZONTAL_DOWN: &str = "┳";
|
||||
|
||||
pub const HORIZONTAL_UP: &str = "┴";
|
||||
pub const DOUBLE_HORIZONTAL_UP: &str = "╩";
|
||||
pub const THICK_HORIZONTAL_UP: &str = "┻";
|
||||
|
||||
pub const CROSS: &str = "┼";
|
||||
pub const DOUBLE_CROSS: &str = "╬";
|
||||
pub const THICK_CROSS: &str = "╋";
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct Set {
|
||||
pub vertical: &'static str,
|
||||
pub horizontal: &'static str,
|
||||
pub top_right: &'static str,
|
||||
pub top_left: &'static str,
|
||||
pub bottom_right: &'static str,
|
||||
pub bottom_left: &'static str,
|
||||
pub vertical_left: &'static str,
|
||||
pub vertical_right: &'static str,
|
||||
pub horizontal_down: &'static str,
|
||||
pub horizontal_up: &'static str,
|
||||
pub cross: &'static str,
|
||||
}
|
||||
|
||||
impl Default for Set {
|
||||
fn default() -> Self {
|
||||
NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
pub const NORMAL: Set = Set {
|
||||
vertical: VERTICAL,
|
||||
horizontal: HORIZONTAL,
|
||||
top_right: TOP_RIGHT,
|
||||
top_left: TOP_LEFT,
|
||||
bottom_right: BOTTOM_RIGHT,
|
||||
bottom_left: BOTTOM_LEFT,
|
||||
vertical_left: VERTICAL_LEFT,
|
||||
vertical_right: VERTICAL_RIGHT,
|
||||
horizontal_down: HORIZONTAL_DOWN,
|
||||
horizontal_up: HORIZONTAL_UP,
|
||||
cross: CROSS,
|
||||
};
|
||||
|
||||
pub const ROUNDED: Set = Set {
|
||||
top_right: ROUNDED_TOP_RIGHT,
|
||||
top_left: ROUNDED_TOP_LEFT,
|
||||
bottom_right: ROUNDED_BOTTOM_RIGHT,
|
||||
bottom_left: ROUNDED_BOTTOM_LEFT,
|
||||
..NORMAL
|
||||
};
|
||||
|
||||
pub const DOUBLE: Set = Set {
|
||||
vertical: DOUBLE_VERTICAL,
|
||||
horizontal: DOUBLE_HORIZONTAL,
|
||||
top_right: DOUBLE_TOP_RIGHT,
|
||||
top_left: DOUBLE_TOP_LEFT,
|
||||
bottom_right: DOUBLE_BOTTOM_RIGHT,
|
||||
bottom_left: DOUBLE_BOTTOM_LEFT,
|
||||
vertical_left: DOUBLE_VERTICAL_LEFT,
|
||||
vertical_right: DOUBLE_VERTICAL_RIGHT,
|
||||
horizontal_down: DOUBLE_HORIZONTAL_DOWN,
|
||||
horizontal_up: DOUBLE_HORIZONTAL_UP,
|
||||
cross: DOUBLE_CROSS,
|
||||
};
|
||||
|
||||
pub const THICK: Set = Set {
|
||||
vertical: THICK_VERTICAL,
|
||||
horizontal: THICK_HORIZONTAL,
|
||||
top_right: THICK_TOP_RIGHT,
|
||||
top_left: THICK_TOP_LEFT,
|
||||
bottom_right: THICK_BOTTOM_RIGHT,
|
||||
bottom_left: THICK_BOTTOM_LEFT,
|
||||
vertical_left: THICK_VERTICAL_LEFT,
|
||||
vertical_right: THICK_VERTICAL_RIGHT,
|
||||
horizontal_down: THICK_HORIZONTAL_DOWN,
|
||||
horizontal_up: THICK_HORIZONTAL_UP,
|
||||
cross: THICK_CROSS,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod border {
|
||||
use super::line;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct Set {
|
||||
pub top_left: &'static str,
|
||||
pub top_right: &'static str,
|
||||
pub bottom_left: &'static str,
|
||||
pub bottom_right: &'static str,
|
||||
pub vertical_left: &'static str,
|
||||
pub vertical_right: &'static str,
|
||||
pub horizontal_top: &'static str,
|
||||
pub horizontal_bottom: &'static str,
|
||||
}
|
||||
|
||||
impl Default for Set {
|
||||
fn default() -> Self {
|
||||
PLAIN
|
||||
}
|
||||
}
|
||||
|
||||
/// Border Set with a single line width
|
||||
///
|
||||
/// ```text
|
||||
/// ┌─────┐
|
||||
/// │xxxxx│
|
||||
/// │xxxxx│
|
||||
/// └─────┘
|
||||
pub const PLAIN: Set = Set {
|
||||
top_left: line::NORMAL.top_left,
|
||||
top_right: line::NORMAL.top_right,
|
||||
bottom_left: line::NORMAL.bottom_left,
|
||||
bottom_right: line::NORMAL.bottom_right,
|
||||
vertical_left: line::NORMAL.vertical,
|
||||
vertical_right: line::NORMAL.vertical,
|
||||
horizontal_top: line::NORMAL.horizontal,
|
||||
horizontal_bottom: line::NORMAL.horizontal,
|
||||
};
|
||||
|
||||
/// Border Set with a single line width and rounded corners
|
||||
///
|
||||
/// ```text
|
||||
/// ╭─────╮
|
||||
/// │xxxxx│
|
||||
/// │xxxxx│
|
||||
/// ╰─────╯
|
||||
pub const ROUNDED: Set = Set {
|
||||
top_left: line::ROUNDED.top_left,
|
||||
top_right: line::ROUNDED.top_right,
|
||||
bottom_left: line::ROUNDED.bottom_left,
|
||||
bottom_right: line::ROUNDED.bottom_right,
|
||||
vertical_left: line::ROUNDED.vertical,
|
||||
vertical_right: line::ROUNDED.vertical,
|
||||
horizontal_top: line::ROUNDED.horizontal,
|
||||
horizontal_bottom: line::ROUNDED.horizontal,
|
||||
};
|
||||
|
||||
/// Border Set with a double line width
|
||||
///
|
||||
/// ```text
|
||||
/// ╔═════╗
|
||||
/// ║xxxxx║
|
||||
/// ║xxxxx║
|
||||
/// ╚═════╝
|
||||
pub const DOUBLE: Set = Set {
|
||||
top_left: line::DOUBLE.top_left,
|
||||
top_right: line::DOUBLE.top_right,
|
||||
bottom_left: line::DOUBLE.bottom_left,
|
||||
bottom_right: line::DOUBLE.bottom_right,
|
||||
vertical_left: line::DOUBLE.vertical,
|
||||
vertical_right: line::DOUBLE.vertical,
|
||||
horizontal_top: line::DOUBLE.horizontal,
|
||||
horizontal_bottom: line::DOUBLE.horizontal,
|
||||
};
|
||||
|
||||
/// Border Set with a thick line width
|
||||
///
|
||||
/// ```text
|
||||
/// ┏━━━━━┓
|
||||
/// ┃xxxxx┃
|
||||
/// ┃xxxxx┃
|
||||
/// ┗━━━━━┛
|
||||
pub const THICK: Set = Set {
|
||||
top_left: line::THICK.top_left,
|
||||
top_right: line::THICK.top_right,
|
||||
bottom_left: line::THICK.bottom_left,
|
||||
bottom_right: line::THICK.bottom_right,
|
||||
vertical_left: line::THICK.vertical,
|
||||
vertical_right: line::THICK.vertical,
|
||||
horizontal_top: line::THICK.horizontal,
|
||||
horizontal_bottom: line::THICK.horizontal,
|
||||
};
|
||||
|
||||
pub const QUADRANT_TOP_LEFT: &str = "▘";
|
||||
pub const QUADRANT_TOP_RIGHT: &str = "▝";
|
||||
pub const QUADRANT_BOTTOM_LEFT: &str = "▖";
|
||||
pub const QUADRANT_BOTTOM_RIGHT: &str = "▗";
|
||||
pub const QUADRANT_TOP_HALF: &str = "▀";
|
||||
pub const QUADRANT_BOTTOM_HALF: &str = "▄";
|
||||
pub const QUADRANT_LEFT_HALF: &str = "▌";
|
||||
pub const QUADRANT_RIGHT_HALF: &str = "▐";
|
||||
pub const QUADRANT_TOP_LEFT_BOTTOM_LEFT_BOTTOM_RIGHT: &str = "▙";
|
||||
pub const QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT: &str = "▛";
|
||||
pub const QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT: &str = "▜";
|
||||
pub const QUADRANT_TOP_RIGHT_BOTTOM_LEFT_BOTTOM_RIGHT: &str = "▟";
|
||||
pub const QUADRANT_TOP_LEFT_BOTTOM_RIGHT: &str = "▚";
|
||||
pub const QUADRANT_TOP_RIGHT_BOTTOM_LEFT: &str = "▞";
|
||||
pub const QUADRANT_BLOCK: &str = "█";
|
||||
|
||||
/// Quadrant used for setting a border outside a block by one half cell "pixel".
|
||||
///
|
||||
/// ```text
|
||||
/// ▛▀▀▀▀▀▜
|
||||
/// ▌xxxxx▐
|
||||
/// ▌xxxxx▐
|
||||
/// ▙▄▄▄▄▄▟
|
||||
/// ```
|
||||
pub const QUADRANT_OUTSIDE: Set = Set {
|
||||
top_left: QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT,
|
||||
top_right: QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT,
|
||||
bottom_left: QUADRANT_TOP_LEFT_BOTTOM_LEFT_BOTTOM_RIGHT,
|
||||
bottom_right: QUADRANT_TOP_RIGHT_BOTTOM_LEFT_BOTTOM_RIGHT,
|
||||
vertical_left: QUADRANT_LEFT_HALF,
|
||||
vertical_right: QUADRANT_RIGHT_HALF,
|
||||
horizontal_top: QUADRANT_TOP_HALF,
|
||||
horizontal_bottom: QUADRANT_BOTTOM_HALF,
|
||||
};
|
||||
|
||||
/// Quadrant used for setting a border inside a block by one half cell "pixel".
|
||||
///
|
||||
/// ```text
|
||||
/// ▗▄▄▄▄▄▖
|
||||
/// ▐xxxxx▌
|
||||
/// ▐xxxxx▌
|
||||
/// ▝▀▀▀▀▀▘
|
||||
/// ```
|
||||
pub const QUADRANT_INSIDE: Set = Set {
|
||||
top_right: QUADRANT_BOTTOM_LEFT,
|
||||
top_left: QUADRANT_BOTTOM_RIGHT,
|
||||
bottom_right: QUADRANT_TOP_LEFT,
|
||||
bottom_left: QUADRANT_TOP_RIGHT,
|
||||
vertical_left: QUADRANT_RIGHT_HALF,
|
||||
vertical_right: QUADRANT_LEFT_HALF,
|
||||
horizontal_top: QUADRANT_BOTTOM_HALF,
|
||||
horizontal_bottom: QUADRANT_TOP_HALF,
|
||||
};
|
||||
|
||||
pub const ONE_EIGHTH_TOP_EIGHT: &str = "▔";
|
||||
pub const ONE_EIGHTH_BOTTOM_EIGHT: &str = "▁";
|
||||
pub const ONE_EIGHTH_LEFT_EIGHT: &str = "▏";
|
||||
pub const ONE_EIGHTH_RIGHT_EIGHT: &str = "▕";
|
||||
|
||||
/// Wide border set based on McGugan box technique
|
||||
///
|
||||
/// ```text
|
||||
/// ▁▁▁▁▁▁▁
|
||||
/// ▏xxxxx▕
|
||||
/// ▏xxxxx▕
|
||||
/// ▔▔▔▔▔▔▔
|
||||
/// ```
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub const ONE_EIGHTH_WIDE: Set = Set {
|
||||
top_right: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
top_left: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
bottom_right: ONE_EIGHTH_TOP_EIGHT,
|
||||
bottom_left: ONE_EIGHTH_TOP_EIGHT,
|
||||
vertical_left: ONE_EIGHTH_LEFT_EIGHT,
|
||||
vertical_right: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
horizontal_top: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
horizontal_bottom: ONE_EIGHTH_TOP_EIGHT,
|
||||
};
|
||||
|
||||
/// Tall border set based on McGugan box technique
|
||||
///
|
||||
/// ```text
|
||||
/// ▕▔▔▏
|
||||
/// ▕xx▏
|
||||
/// ▕xx▏
|
||||
/// ▕▁▁▏
|
||||
/// ```
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub const ONE_EIGHTH_TALL: Set = Set {
|
||||
top_right: ONE_EIGHTH_LEFT_EIGHT,
|
||||
top_left: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
bottom_right: ONE_EIGHTH_LEFT_EIGHT,
|
||||
bottom_left: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
vertical_left: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
vertical_right: ONE_EIGHTH_LEFT_EIGHT,
|
||||
horizontal_top: ONE_EIGHTH_TOP_EIGHT,
|
||||
horizontal_bottom: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
};
|
||||
|
||||
/// Wide proportional (visually equal width and height) border with using set of quadrants.
|
||||
///
|
||||
/// The border is created by using half blocks for top and bottom, and full
|
||||
/// blocks for right and left sides to make horizontal and vertical borders seem equal.
|
||||
///
|
||||
/// ```text
|
||||
/// ▄▄▄▄
|
||||
/// █xx█
|
||||
/// █xx█
|
||||
/// ▀▀▀▀
|
||||
/// ```
|
||||
pub const PROPORTIONAL_WIDE: Set = Set {
|
||||
top_right: QUADRANT_BOTTOM_HALF,
|
||||
top_left: QUADRANT_BOTTOM_HALF,
|
||||
bottom_right: QUADRANT_TOP_HALF,
|
||||
bottom_left: QUADRANT_TOP_HALF,
|
||||
vertical_left: QUADRANT_BLOCK,
|
||||
vertical_right: QUADRANT_BLOCK,
|
||||
horizontal_top: QUADRANT_BOTTOM_HALF,
|
||||
horizontal_bottom: QUADRANT_TOP_HALF,
|
||||
};
|
||||
|
||||
/// Tall proportional (visually equal width and height) border with using set of quadrants.
|
||||
///
|
||||
/// The border is created by using full blocks for all sides, except for the top and bottom,
|
||||
/// which use half blocks to make horizontal and vertical borders seem equal.
|
||||
///
|
||||
/// ```text
|
||||
/// ▕█▀▀█
|
||||
/// ▕█xx█
|
||||
/// ▕█xx█
|
||||
/// ▕█▄▄█
|
||||
/// ```
|
||||
pub const PROPORTIONAL_TALL: Set = Set {
|
||||
top_right: QUADRANT_BLOCK,
|
||||
top_left: QUADRANT_BLOCK,
|
||||
bottom_right: QUADRANT_BLOCK,
|
||||
bottom_left: QUADRANT_BLOCK,
|
||||
vertical_left: QUADRANT_BLOCK,
|
||||
vertical_right: QUADRANT_BLOCK,
|
||||
horizontal_top: QUADRANT_TOP_HALF,
|
||||
horizontal_bottom: QUADRANT_BOTTOM_HALF,
|
||||
};
|
||||
}
|
||||
|
||||
pub const DOT: &str = "•";
|
||||
|
||||
pub mod braille {
|
||||
|
||||
504
src/symbols/border.rs
Normal file
504
src/symbols/border.rs
Normal file
@@ -0,0 +1,504 @@
|
||||
use super::{block, line};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct Set {
|
||||
pub top_left: &'static str,
|
||||
pub top_right: &'static str,
|
||||
pub bottom_left: &'static str,
|
||||
pub bottom_right: &'static str,
|
||||
pub vertical_left: &'static str,
|
||||
pub vertical_right: &'static str,
|
||||
pub horizontal_top: &'static str,
|
||||
pub horizontal_bottom: &'static str,
|
||||
}
|
||||
|
||||
impl Default for Set {
|
||||
fn default() -> Self {
|
||||
PLAIN
|
||||
}
|
||||
}
|
||||
|
||||
/// Border Set with a single line width
|
||||
///
|
||||
/// ```text
|
||||
/// ┌─────┐
|
||||
/// │xxxxx│
|
||||
/// │xxxxx│
|
||||
/// └─────┘
|
||||
pub const PLAIN: Set = Set {
|
||||
top_left: line::NORMAL.top_left,
|
||||
top_right: line::NORMAL.top_right,
|
||||
bottom_left: line::NORMAL.bottom_left,
|
||||
bottom_right: line::NORMAL.bottom_right,
|
||||
vertical_left: line::NORMAL.vertical,
|
||||
vertical_right: line::NORMAL.vertical,
|
||||
horizontal_top: line::NORMAL.horizontal,
|
||||
horizontal_bottom: line::NORMAL.horizontal,
|
||||
};
|
||||
|
||||
/// Border Set with a single line width and rounded corners
|
||||
///
|
||||
/// ```text
|
||||
/// ╭─────╮
|
||||
/// │xxxxx│
|
||||
/// │xxxxx│
|
||||
/// ╰─────╯
|
||||
pub const ROUNDED: Set = Set {
|
||||
top_left: line::ROUNDED.top_left,
|
||||
top_right: line::ROUNDED.top_right,
|
||||
bottom_left: line::ROUNDED.bottom_left,
|
||||
bottom_right: line::ROUNDED.bottom_right,
|
||||
vertical_left: line::ROUNDED.vertical,
|
||||
vertical_right: line::ROUNDED.vertical,
|
||||
horizontal_top: line::ROUNDED.horizontal,
|
||||
horizontal_bottom: line::ROUNDED.horizontal,
|
||||
};
|
||||
|
||||
/// Border Set with a double line width
|
||||
///
|
||||
/// ```text
|
||||
/// ╔═════╗
|
||||
/// ║xxxxx║
|
||||
/// ║xxxxx║
|
||||
/// ╚═════╝
|
||||
pub const DOUBLE: Set = Set {
|
||||
top_left: line::DOUBLE.top_left,
|
||||
top_right: line::DOUBLE.top_right,
|
||||
bottom_left: line::DOUBLE.bottom_left,
|
||||
bottom_right: line::DOUBLE.bottom_right,
|
||||
vertical_left: line::DOUBLE.vertical,
|
||||
vertical_right: line::DOUBLE.vertical,
|
||||
horizontal_top: line::DOUBLE.horizontal,
|
||||
horizontal_bottom: line::DOUBLE.horizontal,
|
||||
};
|
||||
|
||||
/// Border Set with a thick line width
|
||||
///
|
||||
/// ```text
|
||||
/// ┏━━━━━┓
|
||||
/// ┃xxxxx┃
|
||||
/// ┃xxxxx┃
|
||||
/// ┗━━━━━┛
|
||||
pub const THICK: Set = Set {
|
||||
top_left: line::THICK.top_left,
|
||||
top_right: line::THICK.top_right,
|
||||
bottom_left: line::THICK.bottom_left,
|
||||
bottom_right: line::THICK.bottom_right,
|
||||
vertical_left: line::THICK.vertical,
|
||||
vertical_right: line::THICK.vertical,
|
||||
horizontal_top: line::THICK.horizontal,
|
||||
horizontal_bottom: line::THICK.horizontal,
|
||||
};
|
||||
|
||||
pub const QUADRANT_TOP_LEFT: &str = "▘";
|
||||
pub const QUADRANT_TOP_RIGHT: &str = "▝";
|
||||
pub const QUADRANT_BOTTOM_LEFT: &str = "▖";
|
||||
pub const QUADRANT_BOTTOM_RIGHT: &str = "▗";
|
||||
pub const QUADRANT_TOP_HALF: &str = "▀";
|
||||
pub const QUADRANT_BOTTOM_HALF: &str = "▄";
|
||||
pub const QUADRANT_LEFT_HALF: &str = "▌";
|
||||
pub const QUADRANT_RIGHT_HALF: &str = "▐";
|
||||
pub const QUADRANT_TOP_LEFT_BOTTOM_LEFT_BOTTOM_RIGHT: &str = "▙";
|
||||
pub const QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT: &str = "▛";
|
||||
pub const QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT: &str = "▜";
|
||||
pub const QUADRANT_TOP_RIGHT_BOTTOM_LEFT_BOTTOM_RIGHT: &str = "▟";
|
||||
pub const QUADRANT_TOP_LEFT_BOTTOM_RIGHT: &str = "▚";
|
||||
pub const QUADRANT_TOP_RIGHT_BOTTOM_LEFT: &str = "▞";
|
||||
pub const QUADRANT_BLOCK: &str = "█";
|
||||
|
||||
/// Quadrant used for setting a border outside a block by one half cell "pixel".
|
||||
///
|
||||
/// ```text
|
||||
/// ▛▀▀▀▀▀▜
|
||||
/// ▌xxxxx▐
|
||||
/// ▌xxxxx▐
|
||||
/// ▙▄▄▄▄▄▟
|
||||
/// ```
|
||||
pub const QUADRANT_OUTSIDE: Set = Set {
|
||||
top_left: QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT,
|
||||
top_right: QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT,
|
||||
bottom_left: QUADRANT_TOP_LEFT_BOTTOM_LEFT_BOTTOM_RIGHT,
|
||||
bottom_right: QUADRANT_TOP_RIGHT_BOTTOM_LEFT_BOTTOM_RIGHT,
|
||||
vertical_left: QUADRANT_LEFT_HALF,
|
||||
vertical_right: QUADRANT_RIGHT_HALF,
|
||||
horizontal_top: QUADRANT_TOP_HALF,
|
||||
horizontal_bottom: QUADRANT_BOTTOM_HALF,
|
||||
};
|
||||
|
||||
/// Quadrant used for setting a border inside a block by one half cell "pixel".
|
||||
///
|
||||
/// ```text
|
||||
/// ▗▄▄▄▄▄▖
|
||||
/// ▐xxxxx▌
|
||||
/// ▐xxxxx▌
|
||||
/// ▝▀▀▀▀▀▘
|
||||
/// ```
|
||||
pub const QUADRANT_INSIDE: Set = Set {
|
||||
top_right: QUADRANT_BOTTOM_LEFT,
|
||||
top_left: QUADRANT_BOTTOM_RIGHT,
|
||||
bottom_right: QUADRANT_TOP_LEFT,
|
||||
bottom_left: QUADRANT_TOP_RIGHT,
|
||||
vertical_left: QUADRANT_RIGHT_HALF,
|
||||
vertical_right: QUADRANT_LEFT_HALF,
|
||||
horizontal_top: QUADRANT_BOTTOM_HALF,
|
||||
horizontal_bottom: QUADRANT_TOP_HALF,
|
||||
};
|
||||
|
||||
pub const ONE_EIGHTH_TOP_EIGHT: &str = "▔";
|
||||
pub const ONE_EIGHTH_BOTTOM_EIGHT: &str = "▁";
|
||||
pub const ONE_EIGHTH_LEFT_EIGHT: &str = "▏";
|
||||
pub const ONE_EIGHTH_RIGHT_EIGHT: &str = "▕";
|
||||
|
||||
/// Wide border set based on McGugan box technique
|
||||
///
|
||||
/// ```text
|
||||
/// ▁▁▁▁▁▁▁
|
||||
/// ▏xxxxx▕
|
||||
/// ▏xxxxx▕
|
||||
/// ▔▔▔▔▔▔▔
|
||||
/// ```
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub const ONE_EIGHTH_WIDE: Set = Set {
|
||||
top_right: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
top_left: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
bottom_right: ONE_EIGHTH_TOP_EIGHT,
|
||||
bottom_left: ONE_EIGHTH_TOP_EIGHT,
|
||||
vertical_left: ONE_EIGHTH_LEFT_EIGHT,
|
||||
vertical_right: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
horizontal_top: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
horizontal_bottom: ONE_EIGHTH_TOP_EIGHT,
|
||||
};
|
||||
|
||||
/// Tall border set based on McGugan box technique
|
||||
///
|
||||
/// ```text
|
||||
/// ▕▔▔▏
|
||||
/// ▕xx▏
|
||||
/// ▕xx▏
|
||||
/// ▕▁▁▏
|
||||
/// ```
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub const ONE_EIGHTH_TALL: Set = Set {
|
||||
top_right: ONE_EIGHTH_LEFT_EIGHT,
|
||||
top_left: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
bottom_right: ONE_EIGHTH_LEFT_EIGHT,
|
||||
bottom_left: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
vertical_left: ONE_EIGHTH_RIGHT_EIGHT,
|
||||
vertical_right: ONE_EIGHTH_LEFT_EIGHT,
|
||||
horizontal_top: ONE_EIGHTH_TOP_EIGHT,
|
||||
horizontal_bottom: ONE_EIGHTH_BOTTOM_EIGHT,
|
||||
};
|
||||
|
||||
/// Wide proportional (visually equal width and height) border with using set of quadrants.
|
||||
///
|
||||
/// The border is created by using half blocks for top and bottom, and full
|
||||
/// blocks for right and left sides to make horizontal and vertical borders seem equal.
|
||||
///
|
||||
/// ```text
|
||||
/// ▄▄▄▄
|
||||
/// █xx█
|
||||
/// █xx█
|
||||
/// ▀▀▀▀
|
||||
/// ```
|
||||
pub const PROPORTIONAL_WIDE: Set = Set {
|
||||
top_right: QUADRANT_BOTTOM_HALF,
|
||||
top_left: QUADRANT_BOTTOM_HALF,
|
||||
bottom_right: QUADRANT_TOP_HALF,
|
||||
bottom_left: QUADRANT_TOP_HALF,
|
||||
vertical_left: QUADRANT_BLOCK,
|
||||
vertical_right: QUADRANT_BLOCK,
|
||||
horizontal_top: QUADRANT_BOTTOM_HALF,
|
||||
horizontal_bottom: QUADRANT_TOP_HALF,
|
||||
};
|
||||
|
||||
/// Tall proportional (visually equal width and height) border with using set of quadrants.
|
||||
///
|
||||
/// The border is created by using full blocks for all sides, except for the top and bottom,
|
||||
/// which use half blocks to make horizontal and vertical borders seem equal.
|
||||
///
|
||||
/// ```text
|
||||
/// ▕█▀▀█
|
||||
/// ▕█xx█
|
||||
/// ▕█xx█
|
||||
/// ▕█▄▄█
|
||||
/// ```
|
||||
pub const PROPORTIONAL_TALL: Set = Set {
|
||||
top_right: QUADRANT_BLOCK,
|
||||
top_left: QUADRANT_BLOCK,
|
||||
bottom_right: QUADRANT_BLOCK,
|
||||
bottom_left: QUADRANT_BLOCK,
|
||||
vertical_left: QUADRANT_BLOCK,
|
||||
vertical_right: QUADRANT_BLOCK,
|
||||
horizontal_top: QUADRANT_TOP_HALF,
|
||||
horizontal_bottom: QUADRANT_BOTTOM_HALF,
|
||||
};
|
||||
|
||||
/// Solid border set
|
||||
///
|
||||
/// The border is created by using full blocks for all sides.
|
||||
///
|
||||
/// ```text
|
||||
/// ████
|
||||
/// █xx█
|
||||
/// █xx█
|
||||
/// ████
|
||||
pub const FULL: Set = Set {
|
||||
top_left: block::FULL,
|
||||
top_right: block::FULL,
|
||||
bottom_left: block::FULL,
|
||||
bottom_right: block::FULL,
|
||||
vertical_left: block::FULL,
|
||||
vertical_right: block::FULL,
|
||||
horizontal_top: block::FULL,
|
||||
horizontal_bottom: block::FULL,
|
||||
};
|
||||
|
||||
/// Empty border set
|
||||
///
|
||||
/// The border is created by using empty strings for all sides.
|
||||
///
|
||||
/// This is useful for ensuring that the border style is applied to a border on a block with a title
|
||||
/// without actually drawing a border.
|
||||
///
|
||||
/// ░ Example
|
||||
///
|
||||
/// `░` represents the content in the area not covered by the border to make it easier to see the
|
||||
/// blank symbols.
|
||||
///
|
||||
/// ```text
|
||||
/// ░░░░░░░░
|
||||
/// ░░ ░░
|
||||
/// ░░ ░░ ░░
|
||||
/// ░░ ░░ ░░
|
||||
/// ░░ ░░
|
||||
/// ░░░░░░░░
|
||||
/// ```
|
||||
pub const EMPTY: Set = Set {
|
||||
top_left: " ",
|
||||
top_right: " ",
|
||||
bottom_left: " ",
|
||||
bottom_right: " ",
|
||||
vertical_left: " ",
|
||||
vertical_right: " ",
|
||||
horizontal_top: " ",
|
||||
horizontal_bottom: " ",
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use indoc::{formatdoc, indoc};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
assert_eq!(Set::default(), PLAIN);
|
||||
}
|
||||
|
||||
/// A helper function to render a border set to a string.
|
||||
///
|
||||
/// '░' (U+2591 Light Shade) is used as a placeholder for empty space to make it easier to see
|
||||
/// the size of the border symbols.
|
||||
fn render(set: Set) -> String {
|
||||
formatdoc!(
|
||||
"░░░░░░
|
||||
░{}{}{}{}░
|
||||
░{}░░{}░
|
||||
░{}░░{}░
|
||||
░{}{}{}{}░
|
||||
░░░░░░",
|
||||
set.top_left,
|
||||
set.horizontal_top,
|
||||
set.horizontal_top,
|
||||
set.top_right,
|
||||
set.vertical_left,
|
||||
set.vertical_right,
|
||||
set.vertical_left,
|
||||
set.vertical_right,
|
||||
set.bottom_left,
|
||||
set.horizontal_bottom,
|
||||
set.horizontal_bottom,
|
||||
set.bottom_right
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain() {
|
||||
assert_eq!(
|
||||
render(PLAIN),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░┌──┐░
|
||||
░│░░│░
|
||||
░│░░│░
|
||||
░└──┘░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rounded() {
|
||||
assert_eq!(
|
||||
render(ROUNDED),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░╭──╮░
|
||||
░│░░│░
|
||||
░│░░│░
|
||||
░╰──╯░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double() {
|
||||
assert_eq!(
|
||||
render(DOUBLE),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░╔══╗░
|
||||
░║░░║░
|
||||
░║░░║░
|
||||
░╚══╝░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thick() {
|
||||
assert_eq!(
|
||||
render(THICK),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░┏━━┓░
|
||||
░┃░░┃░
|
||||
░┃░░┃░
|
||||
░┗━━┛░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quadrant_outside() {
|
||||
assert_eq!(
|
||||
render(QUADRANT_OUTSIDE),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░▛▀▀▜░
|
||||
░▌░░▐░
|
||||
░▌░░▐░
|
||||
░▙▄▄▟░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quadrant_inside() {
|
||||
assert_eq!(
|
||||
render(QUADRANT_INSIDE),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░▗▄▄▖░
|
||||
░▐░░▌░
|
||||
░▐░░▌░
|
||||
░▝▀▀▘░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_eighth_wide() {
|
||||
assert_eq!(
|
||||
render(ONE_EIGHTH_WIDE),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░▁▁▁▁░
|
||||
░▏░░▕░
|
||||
░▏░░▕░
|
||||
░▔▔▔▔░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_eighth_tall() {
|
||||
assert_eq!(
|
||||
render(ONE_EIGHTH_TALL),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░▕▔▔▏░
|
||||
░▕░░▏░
|
||||
░▕░░▏░
|
||||
░▕▁▁▏░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proportional_wide() {
|
||||
assert_eq!(
|
||||
render(PROPORTIONAL_WIDE),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░▄▄▄▄░
|
||||
░█░░█░
|
||||
░█░░█░
|
||||
░▀▀▀▀░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proportional_tall() {
|
||||
assert_eq!(
|
||||
render(PROPORTIONAL_TALL),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░█▀▀█░
|
||||
░█░░█░
|
||||
░█░░█░
|
||||
░█▄▄█░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full() {
|
||||
assert_eq!(
|
||||
render(FULL),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░████░
|
||||
░█░░█░
|
||||
░█░░█░
|
||||
░████░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!(
|
||||
render(EMPTY),
|
||||
indoc!(
|
||||
"░░░░░░
|
||||
░ ░
|
||||
░ ░░ ░
|
||||
░ ░░ ░
|
||||
░ ░
|
||||
░░░░░░"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
208
src/symbols/line.rs
Normal file
208
src/symbols/line.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
pub const VERTICAL: &str = "│";
|
||||
pub const DOUBLE_VERTICAL: &str = "║";
|
||||
pub const THICK_VERTICAL: &str = "┃";
|
||||
|
||||
pub const HORIZONTAL: &str = "─";
|
||||
pub const DOUBLE_HORIZONTAL: &str = "═";
|
||||
pub const THICK_HORIZONTAL: &str = "━";
|
||||
|
||||
pub const TOP_RIGHT: &str = "┐";
|
||||
pub const ROUNDED_TOP_RIGHT: &str = "╮";
|
||||
pub const DOUBLE_TOP_RIGHT: &str = "╗";
|
||||
pub const THICK_TOP_RIGHT: &str = "┓";
|
||||
|
||||
pub const TOP_LEFT: &str = "┌";
|
||||
pub const ROUNDED_TOP_LEFT: &str = "╭";
|
||||
pub const DOUBLE_TOP_LEFT: &str = "╔";
|
||||
pub const THICK_TOP_LEFT: &str = "┏";
|
||||
|
||||
pub const BOTTOM_RIGHT: &str = "┘";
|
||||
pub const ROUNDED_BOTTOM_RIGHT: &str = "╯";
|
||||
pub const DOUBLE_BOTTOM_RIGHT: &str = "╝";
|
||||
pub const THICK_BOTTOM_RIGHT: &str = "┛";
|
||||
|
||||
pub const BOTTOM_LEFT: &str = "└";
|
||||
pub const ROUNDED_BOTTOM_LEFT: &str = "╰";
|
||||
pub const DOUBLE_BOTTOM_LEFT: &str = "╚";
|
||||
pub const THICK_BOTTOM_LEFT: &str = "┗";
|
||||
|
||||
pub const VERTICAL_LEFT: &str = "┤";
|
||||
pub const DOUBLE_VERTICAL_LEFT: &str = "╣";
|
||||
pub const THICK_VERTICAL_LEFT: &str = "┫";
|
||||
|
||||
pub const VERTICAL_RIGHT: &str = "├";
|
||||
pub const DOUBLE_VERTICAL_RIGHT: &str = "╠";
|
||||
pub const THICK_VERTICAL_RIGHT: &str = "┣";
|
||||
|
||||
pub const HORIZONTAL_DOWN: &str = "┬";
|
||||
pub const DOUBLE_HORIZONTAL_DOWN: &str = "╦";
|
||||
pub const THICK_HORIZONTAL_DOWN: &str = "┳";
|
||||
|
||||
pub const HORIZONTAL_UP: &str = "┴";
|
||||
pub const DOUBLE_HORIZONTAL_UP: &str = "╩";
|
||||
pub const THICK_HORIZONTAL_UP: &str = "┻";
|
||||
|
||||
pub const CROSS: &str = "┼";
|
||||
pub const DOUBLE_CROSS: &str = "╬";
|
||||
pub const THICK_CROSS: &str = "╋";
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct Set {
|
||||
pub vertical: &'static str,
|
||||
pub horizontal: &'static str,
|
||||
pub top_right: &'static str,
|
||||
pub top_left: &'static str,
|
||||
pub bottom_right: &'static str,
|
||||
pub bottom_left: &'static str,
|
||||
pub vertical_left: &'static str,
|
||||
pub vertical_right: &'static str,
|
||||
pub horizontal_down: &'static str,
|
||||
pub horizontal_up: &'static str,
|
||||
pub cross: &'static str,
|
||||
}
|
||||
|
||||
impl Default for Set {
|
||||
fn default() -> Self {
|
||||
NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
pub const NORMAL: Set = Set {
|
||||
vertical: VERTICAL,
|
||||
horizontal: HORIZONTAL,
|
||||
top_right: TOP_RIGHT,
|
||||
top_left: TOP_LEFT,
|
||||
bottom_right: BOTTOM_RIGHT,
|
||||
bottom_left: BOTTOM_LEFT,
|
||||
vertical_left: VERTICAL_LEFT,
|
||||
vertical_right: VERTICAL_RIGHT,
|
||||
horizontal_down: HORIZONTAL_DOWN,
|
||||
horizontal_up: HORIZONTAL_UP,
|
||||
cross: CROSS,
|
||||
};
|
||||
|
||||
pub const ROUNDED: Set = Set {
|
||||
top_right: ROUNDED_TOP_RIGHT,
|
||||
top_left: ROUNDED_TOP_LEFT,
|
||||
bottom_right: ROUNDED_BOTTOM_RIGHT,
|
||||
bottom_left: ROUNDED_BOTTOM_LEFT,
|
||||
..NORMAL
|
||||
};
|
||||
|
||||
pub const DOUBLE: Set = Set {
|
||||
vertical: DOUBLE_VERTICAL,
|
||||
horizontal: DOUBLE_HORIZONTAL,
|
||||
top_right: DOUBLE_TOP_RIGHT,
|
||||
top_left: DOUBLE_TOP_LEFT,
|
||||
bottom_right: DOUBLE_BOTTOM_RIGHT,
|
||||
bottom_left: DOUBLE_BOTTOM_LEFT,
|
||||
vertical_left: DOUBLE_VERTICAL_LEFT,
|
||||
vertical_right: DOUBLE_VERTICAL_RIGHT,
|
||||
horizontal_down: DOUBLE_HORIZONTAL_DOWN,
|
||||
horizontal_up: DOUBLE_HORIZONTAL_UP,
|
||||
cross: DOUBLE_CROSS,
|
||||
};
|
||||
|
||||
pub const THICK: Set = Set {
|
||||
vertical: THICK_VERTICAL,
|
||||
horizontal: THICK_HORIZONTAL,
|
||||
top_right: THICK_TOP_RIGHT,
|
||||
top_left: THICK_TOP_LEFT,
|
||||
bottom_right: THICK_BOTTOM_RIGHT,
|
||||
bottom_left: THICK_BOTTOM_LEFT,
|
||||
vertical_left: THICK_VERTICAL_LEFT,
|
||||
vertical_right: THICK_VERTICAL_RIGHT,
|
||||
horizontal_down: THICK_HORIZONTAL_DOWN,
|
||||
horizontal_up: THICK_HORIZONTAL_UP,
|
||||
cross: THICK_CROSS,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use indoc::{formatdoc, indoc};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
assert_eq!(Set::default(), NORMAL);
|
||||
}
|
||||
|
||||
/// A helper function to render a set of symbols.
|
||||
fn render(set: Set) -> String {
|
||||
formatdoc!(
|
||||
"{}{}{}{}
|
||||
{}{}{}{}
|
||||
{}{}{}{}
|
||||
{}{}{}{}",
|
||||
set.top_left,
|
||||
set.horizontal,
|
||||
set.horizontal_down,
|
||||
set.top_right,
|
||||
set.vertical,
|
||||
" ",
|
||||
set.vertical,
|
||||
set.vertical,
|
||||
set.vertical_right,
|
||||
set.horizontal,
|
||||
set.cross,
|
||||
set.vertical_left,
|
||||
set.bottom_left,
|
||||
set.horizontal,
|
||||
set.horizontal_up,
|
||||
set.bottom_right
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal() {
|
||||
assert_eq!(
|
||||
render(NORMAL),
|
||||
indoc!(
|
||||
"┌─┬┐
|
||||
│ ││
|
||||
├─┼┤
|
||||
└─┴┘"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rounded() {
|
||||
assert_eq!(
|
||||
render(ROUNDED),
|
||||
indoc!(
|
||||
"╭─┬╮
|
||||
│ ││
|
||||
├─┼┤
|
||||
╰─┴╯"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double() {
|
||||
assert_eq!(
|
||||
render(DOUBLE),
|
||||
indoc!(
|
||||
"╔═╦╗
|
||||
║ ║║
|
||||
╠═╬╣
|
||||
╚═╩╝"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thick() {
|
||||
assert_eq!(
|
||||
render(THICK),
|
||||
indoc!(
|
||||
"┏━┳┓
|
||||
┃ ┃┃
|
||||
┣━╋┫
|
||||
┗━┻┛"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ impl Frame<'_> {
|
||||
}
|
||||
|
||||
/// Render a [`StatefulWidgetRef`] to the current buffer using
|
||||
/// [`StatefulWidgetRef::render_stateful_ref`].
|
||||
/// [`StatefulWidgetRef::render_ref`].
|
||||
///
|
||||
/// Usually the area argument is the size of the current frame or a sub-area of the current
|
||||
/// frame (which can be obtained using [`Layout`] to split the total area).
|
||||
@@ -157,7 +157,7 @@ impl Frame<'_> {
|
||||
where
|
||||
W: StatefulWidgetRef,
|
||||
{
|
||||
widget.render_stateful_ref(area, self.buffer, state);
|
||||
widget.render_ref(area, self.buffer, state);
|
||||
}
|
||||
|
||||
/// After drawing this frame, make the cursor visible and put it at the specified (x, y)
|
||||
|
||||
@@ -12,6 +12,9 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
|
||||
/// text. When a [`Line`] is rendered, it is rendered as a single line of text, with each [`Span`]
|
||||
/// being rendered in order (left to right).
|
||||
///
|
||||
/// Any newlines in the content are removed when creating a [`Line`] using the constructor or
|
||||
/// conversion methods.
|
||||
///
|
||||
/// # Constructor Methods
|
||||
///
|
||||
/// - [`Line::default`] creates a line with empty content and the default style.
|
||||
@@ -502,13 +505,13 @@ impl<'a> IntoIterator for &'a mut Line<'a> {
|
||||
|
||||
impl<'a> From<String> for Line<'a> {
|
||||
fn from(s: String) -> Self {
|
||||
Self::from(vec![Span::from(s)])
|
||||
Self::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Line<'a> {
|
||||
fn from(s: &'a str) -> Self {
|
||||
Self::from(vec![Span::from(s)])
|
||||
Self::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,14 +806,22 @@ mod tests {
|
||||
fn from_string() {
|
||||
let s = String::from("Hello, world!");
|
||||
let line = Line::from(s);
|
||||
assert_eq!(vec![Span::from("Hello, world!")], line.spans);
|
||||
assert_eq!(line.spans, vec![Span::from("Hello, world!")]);
|
||||
|
||||
let s = String::from("Hello\nworld!");
|
||||
let line = Line::from(s);
|
||||
assert_eq!(line.spans, vec![Span::from("Hello"), Span::from("world!")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
let s = "Hello, world!";
|
||||
let line = Line::from(s);
|
||||
assert_eq!(vec![Span::from("Hello, world!")], line.spans);
|
||||
assert_eq!(line.spans, vec![Span::from("Hello, world!")]);
|
||||
|
||||
let s = "Hello\nworld!";
|
||||
let line = Line::from(s);
|
||||
assert_eq!(line.spans, vec![Span::from("Hello"), Span::from("world!")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -820,7 +831,7 @@ mod tests {
|
||||
Span::styled(" world!", Style::default().fg(Color::Green)),
|
||||
];
|
||||
let line = Line::from(spans.clone());
|
||||
assert_eq!(spans, line.spans);
|
||||
assert_eq!(line.spans, spans);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -853,7 +864,7 @@ mod tests {
|
||||
fn from_span() {
|
||||
let span = Span::styled("Hello, world!", Style::default().fg(Color::Yellow));
|
||||
let line = Line::from(span.clone());
|
||||
assert_eq!(vec![span], line.spans);
|
||||
assert_eq!(line.spans, vec![span],);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -863,7 +874,7 @@ mod tests {
|
||||
Span::styled(" world!", Style::default().fg(Color::Green)),
|
||||
]);
|
||||
let s: String = line.into();
|
||||
assert_eq!("Hello, world!", s);
|
||||
assert_eq!(s, "Hello, world!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -360,14 +360,14 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
|
||||
///
|
||||
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal stateful
|
||||
/// widgets. Implementors should prefer to implement this over the `StatefulWidget` trait and add an
|
||||
/// implementation of `StatefulWidget` that calls `StatefulWidgetRef::render_stateful_ref` where
|
||||
/// backwards compatibility is required.
|
||||
/// implementation of `StatefulWidget` that calls `StatefulWidgetRef::render_ref` where backwards
|
||||
/// compatibility is required.
|
||||
///
|
||||
/// 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.
|
||||
/// See the documentation for [`WidgetRef`] for more information on boxed widgets.
|
||||
/// See the documentation for [`StatefulWidget`] for more information on stateful widgets.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -379,7 +379,7 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
|
||||
///
|
||||
/// impl StatefulWidgetRef for PersonalGreeting {
|
||||
/// type State = String;
|
||||
/// fn render_stateful_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
/// fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
/// Line::raw(format!("Hello {}", state)).render(area, buf);
|
||||
/// }
|
||||
/// }
|
||||
@@ -387,7 +387,7 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
|
||||
/// impl StatefulWidget for PersonalGreeting {
|
||||
/// type State = String;
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
/// self.render_stateful_ref(area, buf, state);
|
||||
/// (&self).render_ref(area, buf, state);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -406,7 +406,7 @@ pub trait StatefulWidgetRef {
|
||||
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_stateful_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
|
||||
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
|
||||
@@ -420,7 +420,7 @@ pub trait StatefulWidgetRef {
|
||||
// impl<W: StatefulWidgetRef> StatefulWidget for &W {
|
||||
// type State = W::State;
|
||||
// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
// self.render_stateful_ref(area, buf, state);
|
||||
// StatefulWidgetRef::render_ref(self, area, buf, state);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -583,7 +583,7 @@ mod tests {
|
||||
|
||||
impl StatefulWidgetRef for PersonalGreeting {
|
||||
type State = String;
|
||||
fn render_stateful_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
Line::from(format!("Hello {state}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
@@ -591,7 +591,7 @@ mod tests {
|
||||
#[rstest]
|
||||
fn render_ref(mut buf: Buffer, mut state: String) {
|
||||
let widget = PersonalGreeting;
|
||||
widget.render_stateful_ref(buf.area, &mut buf, &mut state);
|
||||
widget.render_ref(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
|
||||
@@ -610,7 +610,7 @@ mod tests {
|
||||
#[rstest]
|
||||
fn box_render_render(mut buf: Buffer, mut state: String) {
|
||||
let widget = Box::new(PersonalGreeting);
|
||||
widget.render_stateful_ref(buf.area, &mut buf, &mut state);
|
||||
widget.render_ref(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -854,7 +854,7 @@ impl Widget for List<'_> {
|
||||
impl WidgetRef for List<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = ListState::default();
|
||||
self.render_stateful_ref(area, buf, &mut state);
|
||||
StatefulWidgetRef::render_ref(self, area, buf, &mut state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,7 +862,7 @@ impl StatefulWidget for List<'_> {
|
||||
type State = ListState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
self.render_stateful_ref(area, buf, state);
|
||||
StatefulWidgetRef::render_ref(&self, area, buf, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,14 +870,14 @@ impl StatefulWidget for List<'_> {
|
||||
impl StatefulWidget for &List<'_> {
|
||||
type State = ListState;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
self.render_stateful_ref(area, buf, state);
|
||||
StatefulWidgetRef::render_ref(self, area, buf, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidgetRef for List<'_> {
|
||||
type State = ListState;
|
||||
|
||||
fn render_stateful_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render_ref(area, buf);
|
||||
let list_area = self.block.inner_if_some(area);
|
||||
|
||||
@@ -20,7 +20,9 @@ use crate::{layout::Flex, prelude::*, style::Styled, widgets::Block};
|
||||
/// [`Table`] implements [`Widget`] and so it can be drawn using [`Frame::render_widget`].
|
||||
///
|
||||
/// [`Table`] is also a [`StatefulWidget`], which means you can use it with [`TableState`] to allow
|
||||
/// the user to scroll through the rows and select one of them.
|
||||
/// the user to scroll through the rows and select one of them. When rendering a [`Table`] with a
|
||||
/// [`TableState`], the selected row will be highlighted. If the selected row is not visible (based
|
||||
/// on the offset), the table will be scrolled to make the selected row visible.
|
||||
///
|
||||
/// Note: if the `widths` field is empty, the table will be rendered with equal widths.
|
||||
///
|
||||
@@ -596,14 +598,14 @@ impl StatefulWidget for Table<'_> {
|
||||
impl StatefulWidget for &Table<'_> {
|
||||
type State = TableState;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
self.render_stateful_ref(area, buf, state);
|
||||
StatefulWidgetRef::render_ref(self, area, buf, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidgetRef for Table<'_> {
|
||||
type State = TableState;
|
||||
|
||||
fn render_stateful_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render_ref(area, buf);
|
||||
let table_area = self.block.inner_if_some(area);
|
||||
@@ -775,7 +777,14 @@ impl Table<'_> {
|
||||
end += 1;
|
||||
}
|
||||
|
||||
let selected = selected.unwrap_or(0).min(self.rows.len() - 1);
|
||||
let Some(selected) = selected else {
|
||||
return (start, end);
|
||||
};
|
||||
|
||||
// clamp the selected row to the last row
|
||||
let selected = selected.min(self.rows.len() - 1);
|
||||
|
||||
// scroll down until the selected row is visible
|
||||
while selected >= end {
|
||||
height = height.saturating_add(self.rows[end].height_with_margin());
|
||||
end += 1;
|
||||
@@ -784,6 +793,8 @@ impl Table<'_> {
|
||||
start += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// scroll up until the selected row is visible
|
||||
while selected < start {
|
||||
start -= 1;
|
||||
height = height.saturating_add(self.rows[start].height_with_margin());
|
||||
@@ -1007,6 +1018,8 @@ mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod render {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -1197,6 +1210,36 @@ mod tests {
|
||||
]);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
/// Note that this includes a regression test for a bug where the table would not render the
|
||||
/// correct rows when there is no selection.
|
||||
/// <https://github.com/ratatui-org/ratatui/issues/1179>
|
||||
#[rstest]
|
||||
#[case::no_selection(None, 50, ["50", "51", "52", "53", "54"])]
|
||||
#[case::selection_before_offset(20, 20, ["20", "21", "22", "23", "24"])]
|
||||
#[case::selection_immediately_before_offset(49, 49, ["49", "50", "51", "52", "53"])]
|
||||
#[case::selection_at_start_of_offset(50, 50, ["50", "51", "52", "53", "54"])]
|
||||
#[case::selection_at_end_of_offset(54, 50, ["50", "51", "52", "53", "54"])]
|
||||
#[case::selection_immediately_after_offset(55, 51, ["51", "52", "53", "54", "55"])]
|
||||
#[case::selection_after_offset(80, 76, ["76", "77", "78", "79", "80"])]
|
||||
fn render_with_selection_and_offset<T: Into<Option<usize>>>(
|
||||
#[case] selected_row: T,
|
||||
#[case] expected_offset: usize,
|
||||
#[case] expected_items: [&str; 5],
|
||||
) {
|
||||
// render 100 rows offset at 50, with a selected row
|
||||
let rows = (0..100).map(|i| Row::new([i.to_string()]));
|
||||
let table = Table::new(rows, [Constraint::Length(2)]);
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 2, 5));
|
||||
let mut state = TableState::new()
|
||||
.with_offset(50)
|
||||
.with_selected(selected_row);
|
||||
|
||||
StatefulWidget::render(table.clone(), Rect::new(0, 0, 5, 5), &mut buf, &mut state);
|
||||
|
||||
assert_eq!(buf, Buffer::with_lines(expected_items));
|
||||
assert_eq!(state.offset, expected_offset);
|
||||
}
|
||||
}
|
||||
|
||||
// test how constraints interact with table column width allocation
|
||||
|
||||
Reference in New Issue
Block a user