Compare commits
3 Commits
663/master
...
feat-reflo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72a912820a | ||
|
|
ec411bb1cd | ||
|
|
47f8c815ce |
@@ -33,7 +33,6 @@ serde = ["dep:serde", "bitflags/serde"]
|
||||
all-features = true
|
||||
# see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html
|
||||
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.3"
|
||||
|
||||
91
bacon.toml
91
bacon.toml
@@ -1,91 +0,0 @@
|
||||
# This is a configuration file for the bacon tool
|
||||
#
|
||||
# Bacon repository: https://github.com/Canop/bacon
|
||||
# Complete help on configuration: https://dystroy.org/bacon/config/
|
||||
# You can also check bacon's own bacon.toml file
|
||||
# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml
|
||||
|
||||
default_job = "check"
|
||||
|
||||
[jobs.check]
|
||||
command = ["cargo", "check", "--all-features", "--color", "always"]
|
||||
need_stdout = false
|
||||
|
||||
[jobs.check-all]
|
||||
command = ["cargo", "check", "--all-targets", "--all-features", "--color", "always"]
|
||||
need_stdout = false
|
||||
|
||||
[jobs.clippy]
|
||||
command = [
|
||||
"cargo", "clippy",
|
||||
"--all-targets",
|
||||
"--color", "always",
|
||||
]
|
||||
need_stdout = false
|
||||
|
||||
[jobs.test]
|
||||
command = [
|
||||
"cargo", "test",
|
||||
"--all-features",
|
||||
"--color", "always",
|
||||
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
|
||||
]
|
||||
need_stdout = true
|
||||
|
||||
[jobs.doc]
|
||||
command = [
|
||||
"cargo", "+nightly", "doc",
|
||||
"-Zunstable-options", "-Zrustdoc-scrape-examples",
|
||||
"--all-features",
|
||||
"--color", "always",
|
||||
"--no-deps",
|
||||
]
|
||||
env.RUSTDOCFLAGS = "--cfg docsrs"
|
||||
need_stdout = false
|
||||
|
||||
# If the doc compiles, then it opens in your browser and bacon switches
|
||||
# to the previous job
|
||||
[jobs.doc-open]
|
||||
command = [
|
||||
"cargo", "+nightly", "doc",
|
||||
"-Zunstable-options", "-Zrustdoc-scrape-examples",
|
||||
"--all-features",
|
||||
"--color", "always",
|
||||
"--no-deps",
|
||||
"--open",
|
||||
]
|
||||
env.RUSTDOCFLAGS = "--cfg docsrs"
|
||||
need_stdout = false
|
||||
on_success = "job:doc" # so that we don't open the browser at each change
|
||||
|
||||
# You can run your application and have the result displayed in bacon,
|
||||
# *if* it makes sense for this crate. You can run an example the same
|
||||
# way. Don't forget the `--color always` part or the errors won't be
|
||||
# properly parsed.
|
||||
[jobs.run]
|
||||
command = [
|
||||
"cargo", "run",
|
||||
"--color", "always",
|
||||
# put launch parameters for your program behind a `--` separator
|
||||
]
|
||||
need_stdout = true
|
||||
allow_warnings = true
|
||||
|
||||
[jobs.check-crossterm]
|
||||
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "crossterm"]
|
||||
|
||||
[jobs.check-termion]
|
||||
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "termion"]
|
||||
|
||||
[jobs.check-termwiz]
|
||||
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "termwiz"]
|
||||
|
||||
# You may define here keybindings that would be specific to
|
||||
# a project, for example a shortcut to launch a specific job.
|
||||
# Shortcuts to internal functions (scrolling, toggling, etc.)
|
||||
# should go in your personal global prefs.toml file instead.
|
||||
[keybindings]
|
||||
# alt-m = "job:my-job"
|
||||
ctrl-c = "job:check-crossterm"
|
||||
ctrl-t = "job:check-termion"
|
||||
ctrl-w = "job:check-termwiz"
|
||||
@@ -1,7 +1,3 @@
|
||||
use std::{error::Error, time::Duration};
|
||||
|
||||
use argh::FromArgs;
|
||||
|
||||
mod app;
|
||||
#[cfg(feature = "crossterm")]
|
||||
mod crossterm;
|
||||
@@ -12,6 +8,17 @@ mod termwiz;
|
||||
|
||||
mod ui;
|
||||
|
||||
use std::{error::Error, time::Duration};
|
||||
|
||||
use argh::FromArgs;
|
||||
|
||||
#[cfg(feature = "crossterm")]
|
||||
use crate::crossterm::run;
|
||||
#[cfg(feature = "termion")]
|
||||
use crate::termion::run;
|
||||
#[cfg(feature = "termwiz")]
|
||||
use crate::termwiz::run;
|
||||
|
||||
/// Demo
|
||||
#[derive(Debug, FromArgs)]
|
||||
struct Cli {
|
||||
@@ -26,11 +33,6 @@ struct Cli {
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli: Cli = argh::from_env();
|
||||
let tick_rate = Duration::from_millis(cli.tick_rate);
|
||||
#[cfg(feature = "crossterm")]
|
||||
crate::crossterm::run(tick_rate, cli.enhanced_graphics)?;
|
||||
#[cfg(feature = "termion")]
|
||||
crate::termion::run(tick_rate, cli.enhanced_graphics)?;
|
||||
#[cfg(feature = "termwiz")]
|
||||
crate::termwiz::run(tick_rate, cli.enhanced_graphics)?;
|
||||
run(tick_rate, cli.enhanced_graphics)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Style},
|
||||
widgets::{Block, Borders, Histogram},
|
||||
Frame, Terminal,
|
||||
};
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use rand::{rngs::ThreadRng, thread_rng, Rng};
|
||||
|
||||
struct App {
|
||||
data: Vec<u64>,
|
||||
size: usize,
|
||||
rng: ThreadRng,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new(size: usize) -> App {
|
||||
let data = vec![0; size];
|
||||
App {
|
||||
data,
|
||||
rng: thread_rng(),
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_tick(&mut self) {
|
||||
for i in 0..self.size {
|
||||
self.data[i] = self.rng.gen_range(0..100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
let app = App::new(100);
|
||||
let res = run_app(&mut terminal, app, tick_rate);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> io::Result<()> {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &app))?;
|
||||
|
||||
let timeout = tick_rate
|
||||
.checked_sub(last_tick.elapsed())
|
||||
.unwrap_or_else(|| Duration::from_secs(0));
|
||||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
app.on_tick();
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
.constraints([Constraint::Percentage(100)].as_ref())
|
||||
.split(f.size());
|
||||
let histogram = Histogram::default()
|
||||
.block(Block::default().title("Data1").borders(Borders::ALL))
|
||||
.data(&app.data, 10)
|
||||
.bar_style(Style::default().fg(Color::Yellow))
|
||||
.value_style(Style::default().fg(Color::Black).bg(Color::Yellow));
|
||||
f.render_widget(histogram, chunks[0]);
|
||||
}
|
||||
@@ -174,9 +174,6 @@
|
||||
//! you might need a blank space somewhere, try to pass an additional constraint and don't use the
|
||||
//! corresponding area.
|
||||
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
pub mod backend;
|
||||
pub mod buffer;
|
||||
pub mod layout;
|
||||
|
||||
470
src/text.rs
470
src/text.rs
@@ -1,470 +0,0 @@
|
||||
//! 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 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`].
|
||||
//!
|
||||
//! 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):
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use ratatui::widgets::Block;
|
||||
//! # use ratatui::text::{Span, Line};
|
||||
//! # use ratatui::style::{Color, Style};
|
||||
//! // A simple string with no styling.
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My title"), style: Style { .. } }
|
||||
//! // ])
|
||||
//! let block = Block::default().title("My title");
|
||||
//!
|
||||
//! // A simple string with a unique style.
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My title"), style: Style { fg: Some(Color::Yellow), .. }
|
||||
//! // ])
|
||||
//! let block = Block::default().title(
|
||||
//! Span::styled("My title", Style::default().fg(Color::Yellow))
|
||||
//! );
|
||||
//!
|
||||
//! // A string with multiple styles.
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My"), style: Style { fg: Some(Color::Yellow), .. } },
|
||||
//! // Span { content: Cow::Borrowed(" title"), .. }
|
||||
//! // ])
|
||||
//! let block = Block::default().title(vec![
|
||||
//! Span::styled("My", Style::default().fg(Color::Yellow)),
|
||||
//! Span::raw(" title"),
|
||||
//! ]);
|
||||
//! ```
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::style::{Style, Styled};
|
||||
|
||||
mod line;
|
||||
mod masked;
|
||||
mod spans;
|
||||
#[allow(deprecated)]
|
||||
pub use {line::Line, masked::Masked, spans::Spans};
|
||||
|
||||
/// A grapheme associated to a style.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StyledGrapheme<'a> {
|
||||
pub symbol: &'a str,
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
/// A string where all graphemes have the same style.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Span<'a> {
|
||||
pub content: Cow<'a, str>,
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
impl<'a> Span<'a> {
|
||||
/// Create a span with no style.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Span;
|
||||
/// Span::raw("My text");
|
||||
/// Span::raw(String::from("My text"));
|
||||
/// ```
|
||||
pub fn raw<T>(content: T) -> Span<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
Span {
|
||||
content: content.into(),
|
||||
style: Style::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a span with a style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Span;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// Span::styled("My text", style);
|
||||
/// Span::styled(String::from("My text"), style);
|
||||
/// ```
|
||||
pub fn styled<T>(content: T, style: Style) -> Span<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
Span {
|
||||
content: content.into(),
|
||||
style,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the width of the content held by this span.
|
||||
pub fn width(&self) -> usize {
|
||||
self.content.width()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the graphemes held by this span.
|
||||
///
|
||||
/// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
|
||||
/// the resulting [`Style`].
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::{Span, StyledGrapheme};
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// # use std::iter::Iterator;
|
||||
/// let style = Style::default().fg(Color::Yellow);
|
||||
/// let span = Span::styled("Text", style);
|
||||
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
|
||||
/// let styled_graphemes = span.styled_graphemes(style);
|
||||
/// assert_eq!(
|
||||
/// vec![
|
||||
/// StyledGrapheme {
|
||||
/// symbol: "T",
|
||||
/// style: Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Black),
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
/// },
|
||||
/// StyledGrapheme {
|
||||
/// symbol: "e",
|
||||
/// style: Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Black),
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
/// },
|
||||
/// StyledGrapheme {
|
||||
/// symbol: "x",
|
||||
/// style: Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Black),
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
/// },
|
||||
/// StyledGrapheme {
|
||||
/// symbol: "t",
|
||||
/// style: Style {
|
||||
/// fg: Some(Color::Yellow),
|
||||
/// bg: Some(Color::Black),
|
||||
/// add_modifier: Modifier::empty(),
|
||||
/// sub_modifier: Modifier::empty(),
|
||||
/// },
|
||||
/// },
|
||||
/// ],
|
||||
/// styled_graphemes.collect::<Vec<StyledGrapheme>>()
|
||||
/// );
|
||||
/// ```
|
||||
pub fn styled_graphemes(
|
||||
&'a self,
|
||||
base_style: Style,
|
||||
) -> impl Iterator<Item = StyledGrapheme<'a>> {
|
||||
UnicodeSegmentation::graphemes(self.content.as_ref(), true)
|
||||
.map(move |g| StyledGrapheme {
|
||||
symbol: g,
|
||||
style: base_style.patch(self.style),
|
||||
})
|
||||
.filter(|s| s.symbol != "\n")
|
||||
}
|
||||
|
||||
/// Patches the style an existing Span, adding modifiers from the given style.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Span;
|
||||
/// # use ratatui::style::{Color, Style, Modifier};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// let mut raw_span = Span::raw("My text");
|
||||
/// let mut styled_span = Span::styled("My text", style);
|
||||
///
|
||||
/// assert_ne!(raw_span, styled_span);
|
||||
///
|
||||
/// raw_span.patch_style(style);
|
||||
/// assert_eq!(raw_span, styled_span);
|
||||
/// ```
|
||||
pub fn patch_style(&mut self, style: Style) {
|
||||
self.style = self.style.patch(style);
|
||||
}
|
||||
|
||||
/// Resets the style of the Span.
|
||||
/// Equivalent to calling `patch_style(Style::reset())`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Span;
|
||||
/// # use ratatui::style::{Color, Style, Modifier};
|
||||
/// let mut span = Span::styled("My text", Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC));
|
||||
///
|
||||
/// span.reset_style();
|
||||
/// assert_eq!(Style::reset(), span.style);
|
||||
/// ```
|
||||
pub fn reset_style(&mut self) {
|
||||
self.patch_style(Style::reset());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Span<'a> {
|
||||
fn from(s: String) -> Span<'a> {
|
||||
Span::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Span<'a> {
|
||||
fn from(s: &'a str) -> Span<'a> {
|
||||
Span::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Styled for Span<'a> {
|
||||
type Item = Span<'a>;
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
}
|
||||
|
||||
fn set_style(self, style: Style) -> Self {
|
||||
Self::styled(self.content, style)
|
||||
}
|
||||
}
|
||||
|
||||
/// A string split over multiple lines where each line is composed of several clusters, each with
|
||||
/// their own style.
|
||||
///
|
||||
/// A [`Text`], like a [`Span`], can be constructed using one of the many `From` implementations
|
||||
/// or via the [`Text::raw`] and [`Text::styled`] methods. Helpfully, [`Text`] also implements
|
||||
/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Text;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
///
|
||||
/// // An initial two lines of `Text` built from a `&str`
|
||||
/// let mut text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(2, text.height());
|
||||
///
|
||||
/// // Adding two more unstyled lines
|
||||
/// text.extend(Text::raw("These are two\nmore lines!"));
|
||||
/// assert_eq!(4, text.height());
|
||||
///
|
||||
/// // Adding a final two styled lines
|
||||
/// text.extend(Text::styled("Some more lines\nnow with more style!", style));
|
||||
/// assert_eq!(6, text.height());
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Default, Eq)]
|
||||
pub struct Text<'a> {
|
||||
pub lines: Vec<Line<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Text<'a> {
|
||||
/// Create some text (potentially multiple lines) with no style.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Text;
|
||||
/// Text::raw("The first line\nThe second line");
|
||||
/// Text::raw(String::from("The first line\nThe second line"));
|
||||
/// ```
|
||||
pub fn raw<T>(content: T) -> Text<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
let lines: Vec<_> = match content.into() {
|
||||
Cow::Borrowed("") => vec![Line::from("")],
|
||||
Cow::Borrowed(s) => s.lines().map(Line::from).collect(),
|
||||
Cow::Owned(s) if s.is_empty() => vec![Line::from("")],
|
||||
Cow::Owned(s) => s.lines().map(|l| Line::from(l.to_owned())).collect(),
|
||||
};
|
||||
|
||||
Text::from(lines)
|
||||
}
|
||||
|
||||
/// Create some text (potentially multiple lines) with a style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Text;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// Text::styled("The first line\nThe second line", style);
|
||||
/// Text::styled(String::from("The first line\nThe second line"), style);
|
||||
/// ```
|
||||
pub fn styled<T>(content: T, style: Style) -> Text<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
let mut text = Text::raw(content);
|
||||
text.patch_style(style);
|
||||
text
|
||||
}
|
||||
|
||||
/// Returns the max width of all the lines.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(15, text.width());
|
||||
/// ```
|
||||
pub fn width(&self) -> usize {
|
||||
self.lines.iter().map(Line::width).max().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns the height.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(2, text.height());
|
||||
/// ```
|
||||
pub fn height(&self) -> usize {
|
||||
self.lines.len()
|
||||
}
|
||||
|
||||
/// Patches the style of each line in an existing Text, adding modifiers from the given style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Text;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// let mut raw_text = Text::raw("The first line\nThe second line");
|
||||
/// let styled_text = Text::styled(String::from("The first line\nThe second line"), style);
|
||||
/// assert_ne!(raw_text, styled_text);
|
||||
///
|
||||
/// raw_text.patch_style(style);
|
||||
/// assert_eq!(raw_text, styled_text);
|
||||
/// ```
|
||||
pub fn patch_style(&mut self, style: Style) {
|
||||
for line in &mut self.lines {
|
||||
line.patch_style(style);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the style of the Text.
|
||||
/// Equivalent to calling `patch_style(Style::reset())`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::{Span, Line, Text};
|
||||
/// # use ratatui::style::{Color, Style, Modifier};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// let mut text = Text::styled("The first line\nThe second line", style);
|
||||
///
|
||||
/// text.reset_style();
|
||||
/// for line in &text.lines {
|
||||
/// for span in &line.spans {
|
||||
/// assert_eq!(Style::reset(), span.style);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn reset_style(&mut self) {
|
||||
for line in &mut self.lines {
|
||||
line.reset_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Text<'a> {
|
||||
fn from(s: String) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Text<'a> {
|
||||
fn from(s: &'a str) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for Text<'a> {
|
||||
fn from(s: Cow<'a, str>) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Span<'a>> for Text<'a> {
|
||||
fn from(span: Span<'a>) -> Text<'a> {
|
||||
Text {
|
||||
lines: vec![Line::from(span)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<'a> From<Spans<'a>> for Text<'a> {
|
||||
fn from(spans: Spans<'a>) -> Text<'a> {
|
||||
Text {
|
||||
lines: vec![spans.into()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Line<'a>> for Text<'a> {
|
||||
fn from(line: Line<'a>) -> Text<'a> {
|
||||
Text { lines: vec![line] }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<'a> From<Vec<Spans<'a>>> for Text<'a> {
|
||||
fn from(lines: Vec<Spans<'a>>) -> Text<'a> {
|
||||
Text {
|
||||
lines: lines.into_iter().map(|l| l.0.into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<Line<'a>>> for Text<'a> {
|
||||
fn from(lines: Vec<Line<'a>>) -> Text<'a> {
|
||||
Text { lines }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for Text<'a> {
|
||||
type Item = Line<'a>;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.lines.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Extend<T> for Text<'a>
|
||||
where
|
||||
T: Into<Line<'a>>,
|
||||
{
|
||||
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
|
||||
let lines = iter.into_iter().map(Into::into);
|
||||
self.lines.extend(lines);
|
||||
}
|
||||
}
|
||||
31
src/text/grapheme.rs
Normal file
31
src/text/grapheme.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use crate::style::{Style, Styled};
|
||||
|
||||
/// A grapheme associated to a style.
|
||||
/// Note that, although `StyledGrapheme` is the smallest divisible unit of text,
|
||||
/// it actually is not a member of the text type hierarchy (`Text` -> `Line` -> `Span`).
|
||||
/// It is a separate type used mostly for rendering purposes. A `Span` consists of components that
|
||||
/// can be split into `StyledGrapheme`s, but it does not contain a collection of `StyledGrapheme`s.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StyledGrapheme<'a> {
|
||||
pub symbol: &'a str,
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
impl<'a> StyledGrapheme<'a> {
|
||||
pub fn new(symbol: &'a str, style: Style) -> StyledGrapheme<'a> {
|
||||
StyledGrapheme { symbol, style }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Styled for StyledGrapheme<'a> {
|
||||
type Item = StyledGrapheme<'a>;
|
||||
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
}
|
||||
|
||||
fn set_style(mut self, style: Style) -> Self::Item {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#![allow(deprecated)]
|
||||
use super::{Span, Spans, Style};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::{Span, Spans, Style, StyledGrapheme};
|
||||
use crate::layout::Alignment;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Eq)]
|
||||
@@ -9,6 +11,24 @@ pub struct Line<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Line<'a> {
|
||||
/// Create a line with a style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Line;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// Line::styled("My text", style);
|
||||
/// Line::styled(String::from("My text"), style);
|
||||
/// ```
|
||||
pub fn styled<T>(content: T, style: Style) -> Line<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
Line::from(Span::styled(content, style))
|
||||
}
|
||||
|
||||
/// Returns the width of the underlying string.
|
||||
///
|
||||
/// ## Examples
|
||||
@@ -26,6 +46,38 @@ impl<'a> Line<'a> {
|
||||
self.spans.iter().map(Span::width).sum()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the graphemes held by this line.
|
||||
///
|
||||
/// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
|
||||
/// the resulting [`Style`].
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::{Line, StyledGrapheme};
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// # use std::iter::Iterator;
|
||||
/// let line = Line::styled("Text", Style::default().fg(Color::Yellow));
|
||||
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
|
||||
/// assert_eq!(
|
||||
/// line.styled_graphemes(style).collect::<Vec<StyledGrapheme>>(),
|
||||
/// vec![
|
||||
/// StyledGrapheme::new("T", Style::default().fg(Color::Yellow).bg(Color::Black)),
|
||||
/// StyledGrapheme::new("e", Style::default().fg(Color::Yellow).bg(Color::Black)),
|
||||
/// StyledGrapheme::new("x", Style::default().fg(Color::Yellow).bg(Color::Black)),
|
||||
/// StyledGrapheme::new("t", Style::default().fg(Color::Yellow).bg(Color::Black)),
|
||||
/// ]
|
||||
/// );
|
||||
/// ```
|
||||
pub fn styled_graphemes(
|
||||
&'a self,
|
||||
base_style: Style,
|
||||
) -> impl Iterator<Item = StyledGrapheme<'a>> {
|
||||
self.spans
|
||||
.iter()
|
||||
.flat_map(move |span| span.styled_graphemes(base_style))
|
||||
}
|
||||
|
||||
/// Patches the style of each Span in an existing Line, adding modifiers from the given style.
|
||||
///
|
||||
/// ## Examples
|
||||
@@ -146,7 +198,7 @@ mod tests {
|
||||
use crate::{
|
||||
layout::Alignment,
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span, Spans},
|
||||
text::{Line, Span, Spans, StyledGrapheme},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -248,4 +300,34 @@ mod tests {
|
||||
let line = Line::from("This is default");
|
||||
assert_eq!(None, line.alignment);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn styled_graphemes() {
|
||||
const RED: Style = Style::new().fg(Color::Red);
|
||||
const GREEN: Style = Style::new().fg(Color::Green);
|
||||
const BLUE: Style = Style::new().fg(Color::Blue);
|
||||
const RED_ON_WHITE: Style = Style::new().fg(Color::Red).bg(Color::White);
|
||||
const GREEN_ON_WHITE: Style = Style::new().fg(Color::Green).bg(Color::White);
|
||||
const BLUE_ON_WHITE: Style = Style::new().fg(Color::Blue).bg(Color::White);
|
||||
|
||||
let line = Line::from(vec![
|
||||
Span::styled("He", RED),
|
||||
Span::styled("ll", GREEN),
|
||||
Span::styled("o!", BLUE),
|
||||
]);
|
||||
let styled_graphemes = line
|
||||
.styled_graphemes(Style::new().bg(Color::White))
|
||||
.collect::<Vec<StyledGrapheme>>();
|
||||
assert_eq!(
|
||||
styled_graphemes,
|
||||
vec![
|
||||
StyledGrapheme::new("H", RED_ON_WHITE),
|
||||
StyledGrapheme::new("e", RED_ON_WHITE),
|
||||
StyledGrapheme::new("l", GREEN_ON_WHITE),
|
||||
StyledGrapheme::new("l", GREEN_ON_WHITE),
|
||||
StyledGrapheme::new("o", BLUE_ON_WHITE),
|
||||
StyledGrapheme::new("!", BLUE_ON_WHITE),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
71
src/text/mod.rs
Normal file
71
src/text/mod.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! 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 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`].
|
||||
//!
|
||||
//! 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):
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use ratatui::widgets::Block;
|
||||
//! # use ratatui::text::{Span, Line};
|
||||
//! # use ratatui::style::{Color, Style};
|
||||
//! // A simple string with no styling.
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My title"), style: Style { .. } }
|
||||
//! // ])
|
||||
//! let block = Block::default().title("My title");
|
||||
//!
|
||||
//! // A simple string with a unique style.
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My title"), style: Style { fg: Some(Color::Yellow), .. }
|
||||
//! // ])
|
||||
//! let block = Block::default().title(
|
||||
//! Span::styled("My title", Style::default().fg(Color::Yellow))
|
||||
//! );
|
||||
//!
|
||||
//! // A string with multiple styles.
|
||||
//! // Converted to Line(vec![
|
||||
//! // Span { content: Cow::Borrowed("My"), style: Style { fg: Some(Color::Yellow), .. } },
|
||||
//! // Span { content: Cow::Borrowed(" title"), .. }
|
||||
//! // ])
|
||||
//! let block = Block::default().title(vec![
|
||||
//! Span::styled("My", Style::default().fg(Color::Yellow)),
|
||||
//! Span::raw(" title"),
|
||||
//! ]);
|
||||
//! ```
|
||||
|
||||
use crate::style::Style;
|
||||
|
||||
mod grapheme;
|
||||
pub use grapheme::StyledGrapheme;
|
||||
|
||||
mod line;
|
||||
pub use line::Line;
|
||||
|
||||
mod masked;
|
||||
pub use masked::Masked;
|
||||
|
||||
mod span;
|
||||
pub use span::Span;
|
||||
|
||||
/// We keep this for backward compatibility.
|
||||
mod spans;
|
||||
#[allow(deprecated)]
|
||||
pub use spans::Spans;
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod text;
|
||||
pub use text::Text;
|
||||
156
src/text/span.rs
Normal file
156
src/text/span.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::StyledGrapheme;
|
||||
use crate::style::{Style, Styled};
|
||||
|
||||
/// A string where all graphemes have the same style.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Span<'a> {
|
||||
pub content: Cow<'a, str>,
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
impl<'a> Span<'a> {
|
||||
/// Create a span with no style.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Span;
|
||||
/// Span::raw("My text");
|
||||
/// Span::raw(String::from("My text"));
|
||||
/// ```
|
||||
pub fn raw<T>(content: T) -> Span<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
Span {
|
||||
content: content.into(),
|
||||
style: Style::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a span with a style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Span;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// Span::styled("My text", style);
|
||||
/// Span::styled(String::from("My text"), style);
|
||||
/// ```
|
||||
pub fn styled<T>(content: T, style: Style) -> Span<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
Span {
|
||||
content: content.into(),
|
||||
style,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the width of the content held by this span.
|
||||
pub fn width(&self) -> usize {
|
||||
self.content.width()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the graphemes held by this span.
|
||||
///
|
||||
/// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
|
||||
/// the resulting [`Style`].
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::{Span, StyledGrapheme};
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// # use std::iter::Iterator;
|
||||
/// let span = Span::styled("Text", Style::default().fg(Color::Yellow));
|
||||
/// let style = Style::default().fg(Color::Green).bg(Color::Black);
|
||||
/// assert_eq!(
|
||||
/// span.styled_graphemes(style).collect::<Vec<StyledGrapheme>>(),
|
||||
/// vec![
|
||||
/// StyledGrapheme::new("T", Style::default().fg(Color::Yellow).bg(Color::Black)),
|
||||
/// StyledGrapheme::new("e", Style::default().fg(Color::Yellow).bg(Color::Black)),
|
||||
/// StyledGrapheme::new("x", Style::default().fg(Color::Yellow).bg(Color::Black)),
|
||||
/// StyledGrapheme::new("t", Style::default().fg(Color::Yellow).bg(Color::Black)),
|
||||
/// ],
|
||||
/// );
|
||||
/// ```
|
||||
pub fn styled_graphemes(
|
||||
&'a self,
|
||||
base_style: Style,
|
||||
) -> impl Iterator<Item = StyledGrapheme<'a>> {
|
||||
UnicodeSegmentation::graphemes(self.content.as_ref(), true)
|
||||
.map(move |g| StyledGrapheme {
|
||||
symbol: g,
|
||||
style: base_style.patch(self.style),
|
||||
})
|
||||
.filter(|s| s.symbol != "\n")
|
||||
}
|
||||
|
||||
/// Patches the style an existing Span, adding modifiers from the given style.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Span;
|
||||
/// # use ratatui::style::{Color, Style, Modifier};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// let mut raw_span = Span::raw("My text");
|
||||
/// let mut styled_span = Span::styled("My text", style);
|
||||
///
|
||||
/// assert_ne!(raw_span, styled_span);
|
||||
///
|
||||
/// raw_span.patch_style(style);
|
||||
/// assert_eq!(raw_span, styled_span);
|
||||
/// ```
|
||||
pub fn patch_style(&mut self, style: Style) {
|
||||
self.style = self.style.patch(style);
|
||||
}
|
||||
|
||||
/// Resets the style of the Span.
|
||||
/// Equivalent to calling `patch_style(Style::reset())`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Span;
|
||||
/// # use ratatui::style::{Color, Style, Modifier};
|
||||
/// let mut span = Span::styled("My text", Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC));
|
||||
///
|
||||
/// span.reset_style();
|
||||
/// assert_eq!(Style::reset(), span.style);
|
||||
/// ```
|
||||
pub fn reset_style(&mut self) {
|
||||
self.patch_style(Style::reset());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Span<'a> {
|
||||
fn from(s: String) -> Span<'a> {
|
||||
Span::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Span<'a> {
|
||||
fn from(s: &'a str) -> Span<'a> {
|
||||
Span::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Styled for Span<'a> {
|
||||
type Item = Span<'a>;
|
||||
fn style(&self) -> Style {
|
||||
self.style
|
||||
}
|
||||
|
||||
fn set_style(self, style: Style) -> Self {
|
||||
Self::styled(self.content, style)
|
||||
}
|
||||
}
|
||||
225
src/text/text.rs
Normal file
225
src/text/text.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[allow(deprecated)]
|
||||
use super::{Line, Span, Spans};
|
||||
use crate::style::Style;
|
||||
|
||||
/// A string split over multiple lines where each line is composed of several clusters, each with
|
||||
/// their own style.
|
||||
///
|
||||
/// A [`Text`], like a [`Span`], can be constructed using one of the many `From` implementations
|
||||
/// or via the [`Text::raw`] and [`Text::styled`] methods. Helpfully, [`Text`] also implements
|
||||
/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Text;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
///
|
||||
/// // An initial two lines of `Text` built from a `&str`
|
||||
/// let mut text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(2, text.height());
|
||||
///
|
||||
/// // Adding two more unstyled lines
|
||||
/// text.extend(Text::raw("These are two\nmore lines!"));
|
||||
/// assert_eq!(4, text.height());
|
||||
///
|
||||
/// // Adding a final two styled lines
|
||||
/// text.extend(Text::styled("Some more lines\nnow with more style!", style));
|
||||
/// assert_eq!(6, text.height());
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Default, Eq)]
|
||||
pub struct Text<'a> {
|
||||
pub lines: Vec<Line<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Text<'a> {
|
||||
/// Create some text (potentially multiple lines) with no style.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Text;
|
||||
/// Text::raw("The first line\nThe second line");
|
||||
/// Text::raw(String::from("The first line\nThe second line"));
|
||||
/// ```
|
||||
pub fn raw<T>(content: T) -> Text<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
let lines: Vec<_> = match content.into() {
|
||||
Cow::Borrowed("") => vec![Line::from("")],
|
||||
Cow::Borrowed(s) => s.lines().map(Line::from).collect(),
|
||||
Cow::Owned(s) if s.is_empty() => vec![Line::from("")],
|
||||
Cow::Owned(s) => s.lines().map(|l| Line::from(l.to_owned())).collect(),
|
||||
};
|
||||
|
||||
Text::from(lines)
|
||||
}
|
||||
|
||||
/// Create some text (potentially multiple lines) with a style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Text;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// Text::styled("The first line\nThe second line", style);
|
||||
/// Text::styled(String::from("The first line\nThe second line"), style);
|
||||
/// ```
|
||||
pub fn styled<T>(content: T, style: Style) -> Text<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
{
|
||||
let mut text = Text::raw(content);
|
||||
text.patch_style(style);
|
||||
text
|
||||
}
|
||||
|
||||
/// Returns the max width of all the lines.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(15, text.width());
|
||||
/// ```
|
||||
pub fn width(&self) -> usize {
|
||||
self.lines.iter().map(Line::width).max().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns the height.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui::text::Text;
|
||||
/// let text = Text::from("The first line\nThe second line");
|
||||
/// assert_eq!(2, text.height());
|
||||
/// ```
|
||||
pub fn height(&self) -> usize {
|
||||
self.lines.len()
|
||||
}
|
||||
|
||||
/// Patches the style of each line in an existing Text, adding modifiers from the given style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::Text;
|
||||
/// # use ratatui::style::{Color, Modifier, Style};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// let mut raw_text = Text::raw("The first line\nThe second line");
|
||||
/// let styled_text = Text::styled(String::from("The first line\nThe second line"), style);
|
||||
/// assert_ne!(raw_text, styled_text);
|
||||
///
|
||||
/// raw_text.patch_style(style);
|
||||
/// assert_eq!(raw_text, styled_text);
|
||||
/// ```
|
||||
pub fn patch_style(&mut self, style: Style) {
|
||||
for line in &mut self.lines {
|
||||
line.patch_style(style);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the style of the Text.
|
||||
/// Equivalent to calling `patch_style(Style::reset())`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::text::{Span, Line, Text};
|
||||
/// # use ratatui::style::{Color, Style, Modifier};
|
||||
/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
|
||||
/// let mut text = Text::styled("The first line\nThe second line", style);
|
||||
///
|
||||
/// text.reset_style();
|
||||
/// for line in &text.lines {
|
||||
/// for span in &line.spans {
|
||||
/// assert_eq!(Style::reset(), span.style);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn reset_style(&mut self) {
|
||||
for line in &mut self.lines {
|
||||
line.reset_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Text<'a> {
|
||||
fn from(s: String) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Text<'a> {
|
||||
fn from(s: &'a str) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for Text<'a> {
|
||||
fn from(s: Cow<'a, str>) -> Text<'a> {
|
||||
Text::raw(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Span<'a>> for Text<'a> {
|
||||
fn from(span: Span<'a>) -> Text<'a> {
|
||||
Text {
|
||||
lines: vec![Line::from(span)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<'a> From<Spans<'a>> for Text<'a> {
|
||||
fn from(spans: Spans<'a>) -> Text<'a> {
|
||||
Text {
|
||||
lines: vec![spans.into()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Line<'a>> for Text<'a> {
|
||||
fn from(line: Line<'a>) -> Text<'a> {
|
||||
Text { lines: vec![line] }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<'a> From<Vec<Spans<'a>>> for Text<'a> {
|
||||
fn from(lines: Vec<Spans<'a>>) -> Text<'a> {
|
||||
Text {
|
||||
lines: lines.into_iter().map(|l| l.0.into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<Line<'a>>> for Text<'a> {
|
||||
fn from(lines: Vec<Line<'a>>) -> Text<'a> {
|
||||
Text { lines }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for Text<'a> {
|
||||
type Item = Line<'a>;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.lines.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Extend<T> for Text<'a>
|
||||
where
|
||||
T: Into<Line<'a>>,
|
||||
{
|
||||
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
|
||||
let lines = iter.into_iter().map(Into::into);
|
||||
self.lines.extend(lines);
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::Style,
|
||||
symbols,
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
/// A bar chart specialized for showing histograms
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tui::widgets::{Block, Borders, Histogram};
|
||||
/// # use tui::style::{Style, Color, Modifier};
|
||||
/// Histogram::default()
|
||||
/// .block(Block::default().title("Histogram").borders(Borders::ALL))
|
||||
/// .bar_width(3)
|
||||
/// .bar_gap(1)
|
||||
/// .bar_style(Style::default().fg(Color::Yellow).bg(Color::Red))
|
||||
/// .value_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
|
||||
/// .label_style(Style::default().fg(Color::White))
|
||||
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
|
||||
/// .max(4);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Histogram<'a> {
|
||||
/// Block to wrap the widget in
|
||||
block: Option<Block<'a>>,
|
||||
/// The gap between each bar
|
||||
bar_gap: u16,
|
||||
/// Set of symbols used to display the data
|
||||
bar_set: symbols::bar::Set,
|
||||
/// Style of the bars
|
||||
bar_style: Style,
|
||||
/// Style of the values printed at the bottom of each bar
|
||||
value_style: Style,
|
||||
/// Style of the labels printed under each bar
|
||||
label_style: Style,
|
||||
/// Style for the widget
|
||||
style: Style,
|
||||
/// Slice of values to plot on the chart
|
||||
data: &'a [u64],
|
||||
/// each bucket keeps a count of the data points that fall into it
|
||||
/// buckets[0] counts items where 0 <= x < bucket_size
|
||||
/// buckets[1] counts items where bucket_size <= x < 2*bucket_size
|
||||
/// etc.
|
||||
buckets: Vec<u64>,
|
||||
/// Value necessary for a bar to reach the maximum height (if no value is specified,
|
||||
/// the maximum value in the data is taken as reference)
|
||||
max: Option<u64>,
|
||||
/// Values to display on the bar (computed when the data is passed to the widget)
|
||||
values: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> Default for Histogram<'a> {
|
||||
fn default() -> Histogram<'a> {
|
||||
Histogram {
|
||||
block: None,
|
||||
max: None,
|
||||
data: &[],
|
||||
values: Vec::new(),
|
||||
bar_style: Style::default(),
|
||||
bar_gap: 1,
|
||||
bar_set: symbols::bar::NINE_LEVELS,
|
||||
buckets: Vec::new(),
|
||||
value_style: Default::default(),
|
||||
label_style: Default::default(),
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Histogram<'a> {
|
||||
pub fn data(mut self, data: &'a [u64], n_buckets: u64) -> Histogram<'a> {
|
||||
self.data = data;
|
||||
|
||||
let min = *self.data.iter().min().unwrap();
|
||||
let max = *self.data.iter().max().unwrap() + 1;
|
||||
let bucket_size: u64 = ((max - min) as f64 / n_buckets as f64).ceil() as u64;
|
||||
self.buckets = vec![0; n_buckets as usize];
|
||||
|
||||
// initialize buckets
|
||||
self.values = Vec::with_capacity(n_buckets as usize);
|
||||
for v in 0..n_buckets {
|
||||
self.values.push(format!("{}", v * bucket_size));
|
||||
}
|
||||
|
||||
// bucketize data
|
||||
for &x in self.data.iter() {
|
||||
let idx: usize = ((x - min) / bucket_size) as usize;
|
||||
self.buckets[idx] += 1;
|
||||
}
|
||||
|
||||
self.max = Some(*self.buckets.iter().max().unwrap());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block(mut self, block: Block<'a>) -> Histogram<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max(mut self, max: u64) -> Histogram<'a> {
|
||||
self.max = Some(max);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_style(mut self, style: Style) -> Histogram<'a> {
|
||||
self.bar_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_gap(mut self, gap: u16) -> Histogram<'a> {
|
||||
self.bar_gap = gap;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> Histogram<'a> {
|
||||
self.bar_set = bar_set;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn value_style(mut self, style: Style) -> Histogram<'a> {
|
||||
self.value_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_style(mut self, style: Style) -> Histogram<'a> {
|
||||
self.label_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: Style) -> Histogram<'a> {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Histogram<'a> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
|
||||
let chart_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
|
||||
if chart_area.height < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let n_bars = self.buckets.len() as u16;
|
||||
let bar_width: u16 = (chart_area.width - (n_bars + 1) * self.bar_gap) / n_bars;
|
||||
|
||||
let max = self
|
||||
.max
|
||||
.unwrap_or_else(|| self.buckets.iter().copied().max().unwrap_or_default());
|
||||
|
||||
let mut data = self
|
||||
.buckets
|
||||
.iter()
|
||||
.take(n_bars as usize)
|
||||
.map(|&v| v * u64::from(chart_area.height - 1) * 8 / std::cmp::max(max, 1))
|
||||
.collect::<Vec<u64>>();
|
||||
for j in (0..chart_area.height - 1).rev() {
|
||||
for (i, d) in data.iter_mut().enumerate() {
|
||||
let symbol = match d {
|
||||
0 => self.bar_set.empty,
|
||||
1 => self.bar_set.one_eighth,
|
||||
2 => self.bar_set.one_quarter,
|
||||
3 => self.bar_set.three_eighths,
|
||||
4 => self.bar_set.half,
|
||||
5 => self.bar_set.five_eighths,
|
||||
6 => self.bar_set.three_quarters,
|
||||
7 => self.bar_set.seven_eighths,
|
||||
_ => self.bar_set.full,
|
||||
};
|
||||
|
||||
for x in 0..bar_width {
|
||||
buf.get_mut(
|
||||
chart_area.left() + i as u16 * (bar_width + self.bar_gap) + x,
|
||||
chart_area.top() + j,
|
||||
)
|
||||
.set_symbol(symbol)
|
||||
.set_style(self.bar_style);
|
||||
}
|
||||
|
||||
if *d > 8 {
|
||||
*d -= 8;
|
||||
} else {
|
||||
*d = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &value) in self.buckets.iter().enumerate() {
|
||||
let label = &self.values[i];
|
||||
if value != 0 {
|
||||
let value_label = format!("{}", &self.buckets[i]);
|
||||
let width = value_label.width() as u16;
|
||||
if width < bar_width {
|
||||
buf.set_string(
|
||||
chart_area.left()
|
||||
+ i as u16 * (bar_width + self.bar_gap)
|
||||
+ (bar_width - width) / 2,
|
||||
chart_area.bottom() - 2,
|
||||
value_label,
|
||||
self.value_style,
|
||||
);
|
||||
}
|
||||
}
|
||||
buf.set_stringn(
|
||||
chart_area.left() + i as u16 * (bar_width + self.bar_gap),
|
||||
chart_area.bottom() - 1,
|
||||
label,
|
||||
bar_width as usize,
|
||||
self.label_style,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ pub mod canvas;
|
||||
mod chart;
|
||||
mod clear;
|
||||
mod gauge;
|
||||
mod histogram;
|
||||
mod list;
|
||||
mod paragraph;
|
||||
mod reflow;
|
||||
@@ -43,7 +42,6 @@ pub use self::{
|
||||
chart::{Axis, Chart, Dataset, GraphType},
|
||||
clear::Clear,
|
||||
gauge::{Gauge, LineGauge},
|
||||
histogram::Histogram,
|
||||
list::{List, ListItem, ListState},
|
||||
paragraph::{Paragraph, Wrap},
|
||||
scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::Style,
|
||||
symbols,
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
use std::cmp::min;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
/// A series for a stacked bar chart
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BarSeries<'a> {
|
||||
/// Name of the series
|
||||
name: Cow<'a, str>,
|
||||
/// The color to display for this series
|
||||
bar_style: Style,
|
||||
/// A reference to the data for this series
|
||||
data: &'a [u64]
|
||||
}
|
||||
|
||||
/// Display multiple bars in a single widgets
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tui::widgets::{Block, Borders, BarChart};
|
||||
/// # use tui::style::{Style, Color, Modifier};
|
||||
/// BarChart::default()
|
||||
/// .block(Block::default().title("BarChart").borders(Borders::ALL))
|
||||
/// .bar_width(3)
|
||||
/// .bar_gap(1)
|
||||
/// .bar_style(Style::default().fg(Color::Yellow).bg(Color::Red))
|
||||
/// .value_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
|
||||
/// .label_style(Style::default().fg(Color::White))
|
||||
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
|
||||
/// .max(4);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OverlappingBarChart<'a> {
|
||||
/// Block to wrap the widget in
|
||||
block: Option<Block<'a>>,
|
||||
/// The width of each bar
|
||||
bar_width: u16,
|
||||
/// The gap between each bar
|
||||
bar_gap: u16,
|
||||
/// Set of symbols used to display the data
|
||||
bar_set: symbols::bar::Set,
|
||||
/// Style of the bars
|
||||
bar_style: Style,
|
||||
/// Style of the values printed at the bottom of each bar
|
||||
value_style: Style,
|
||||
/// Style of the labels printed under each bar
|
||||
label_style: Style,
|
||||
/// Style for the widget
|
||||
style: Style,
|
||||
/// Vec of slices of (label, value) pair to plot on the chart
|
||||
data: Vec<&'a [(&'a str, u64)]>,
|
||||
/// Value necessary for a bar to reach the maximum height (if no value is specified,
|
||||
/// the maximum value in the data is taken as reference)
|
||||
max: Option<u64>,
|
||||
/// Values to display on the bar (computed when the data is passed to the widget)
|
||||
values: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> Default for BarChart<'a> {
|
||||
fn default() -> BarChart<'a> {
|
||||
BarChart {
|
||||
block: None,
|
||||
max: None,
|
||||
data: Vec::new(),
|
||||
values: Vec::new(),
|
||||
bar_style: Style::default(),
|
||||
bar_width: 1,
|
||||
bar_gap: 1,
|
||||
bar_set: symbols::bar::NINE_LEVELS,
|
||||
value_style: Default::default(),
|
||||
label_style: Default::default(),
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BarChart<'a> {
|
||||
pub fn data(mut self, data: &'a [(&'a str, u64)]) -> BarChart<'a> {
|
||||
self.data = data;
|
||||
self.values = Vec::with_capacity(self.data.len());
|
||||
for &(_, v) in self.data {
|
||||
self.values.push(format!("{}", v));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block(mut self, block: Block<'a>) -> BarChart<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max(mut self, max: u64) -> BarChart<'a> {
|
||||
self.max = Some(max);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_style(mut self, style: Style) -> BarChart<'a> {
|
||||
self.bar_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_width(mut self, width: u16) -> BarChart<'a> {
|
||||
self.bar_width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_gap(mut self, gap: u16) -> BarChart<'a> {
|
||||
self.bar_gap = gap;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> BarChart<'a> {
|
||||
self.bar_set = bar_set;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn value_style(mut self, style: Style) -> BarChart<'a> {
|
||||
self.value_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_style(mut self, style: Style) -> BarChart<'a> {
|
||||
self.label_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: Style) -> BarChart<'a> {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for BarChart<'a> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
|
||||
let chart_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
|
||||
if chart_area.height < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let max = self
|
||||
.max
|
||||
.unwrap_or_else(|| self.data.iter().map(|t| t.1).max().unwrap_or_default());
|
||||
let max_index = min(
|
||||
(chart_area.width / (self.bar_width + self.bar_gap)) as usize,
|
||||
self.data.len(),
|
||||
);
|
||||
let mut data = self
|
||||
.data
|
||||
.iter()
|
||||
.take(max_index)
|
||||
.map(|&(l, v)| {
|
||||
(
|
||||
l,
|
||||
v * u64::from(chart_area.height - 1) * 8 / std::cmp::max(max, 1),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(&str, u64)>>();
|
||||
for j in (0..chart_area.height - 1).rev() {
|
||||
for (i, d) in data.iter_mut().enumerate() {
|
||||
let symbol = match d.1 {
|
||||
0 => self.bar_set.empty,
|
||||
1 => self.bar_set.one_eighth,
|
||||
2 => self.bar_set.one_quarter,
|
||||
3 => self.bar_set.three_eighths,
|
||||
4 => self.bar_set.half,
|
||||
5 => self.bar_set.five_eighths,
|
||||
6 => self.bar_set.three_quarters,
|
||||
7 => self.bar_set.seven_eighths,
|
||||
_ => self.bar_set.full,
|
||||
};
|
||||
|
||||
for x in 0..self.bar_width {
|
||||
buf.get_mut(
|
||||
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x,
|
||||
chart_area.top() + j,
|
||||
)
|
||||
.set_symbol(symbol)
|
||||
.set_style(self.bar_style);
|
||||
}
|
||||
|
||||
if d.1 > 8 {
|
||||
d.1 -= 8;
|
||||
} else {
|
||||
d.1 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &(label, value)) in self.data.iter().take(max_index).enumerate() {
|
||||
if value != 0 {
|
||||
let value_label = &self.values[i];
|
||||
let width = value_label.width() as u16;
|
||||
if width < self.bar_width {
|
||||
buf.set_string(
|
||||
chart_area.left()
|
||||
+ i as u16 * (self.bar_width + self.bar_gap)
|
||||
+ (self.bar_width - width) / 2,
|
||||
chart_area.bottom() - 2,
|
||||
value_label,
|
||||
self.value_style,
|
||||
);
|
||||
}
|
||||
}
|
||||
buf.set_stringn(
|
||||
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
|
||||
chart_area.bottom() - 1,
|
||||
label,
|
||||
self.bar_width as usize,
|
||||
self.label_style,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user