Compare commits
11 Commits
v0.26.3-al
...
jm/ansi-wi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48924e9cb9 | ||
|
|
ee85bcfb43 | ||
|
|
9f9a3047c4 | ||
|
|
0d54ca06f8 | ||
|
|
97ee102f17 | ||
|
|
c442dfd1ad | ||
|
|
0a164965ea | ||
|
|
bf0923473c | ||
|
|
81b96338ea | ||
|
|
2e71c1874e | ||
|
|
c75aa1990f |
14
Cargo.toml
14
Cargo.toml
@@ -64,7 +64,9 @@ serde_json = "1.0.109"
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
|
||||
[lints.clippy]
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
cast_possible_truncation = "allow"
|
||||
cast_possible_wrap = "allow"
|
||||
@@ -146,6 +148,9 @@ unstable-rendered-line-info = []
|
||||
## the future.
|
||||
unstable-widget-ref = []
|
||||
|
||||
## Enables the `WidgetExt` trait which is experimental and may change in the future.
|
||||
unstable-widget-ext = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html
|
||||
@@ -177,8 +182,8 @@ harness = false
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "barchart"
|
||||
required-features = ["crossterm"]
|
||||
name = "ansi_buffer"
|
||||
required-features = ["unstable-widget-ext", "unstable-widget-ref"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
@@ -323,6 +328,11 @@ name = "inline"
|
||||
required-features = ["crossterm"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "widget_ext"
|
||||
required-features = ["unstable-widget-ext"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[test]]
|
||||
name = "state_serde"
|
||||
required-features = ["serde"]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Bencher, BenchmarkId, Criterion};
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
prelude::Alignment,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
block::{Position, Title},
|
||||
Block, Borders, Padding, Widget,
|
||||
@@ -13,23 +12,23 @@ use ratatui::{
|
||||
fn block(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("block");
|
||||
|
||||
for buffer_size in [
|
||||
Rect::new(0, 0, 100, 50), // vertically split screen
|
||||
Rect::new(0, 0, 200, 50), // 1080p fullscreen with medium font
|
||||
Rect::new(0, 0, 256, 256), // Max sized area
|
||||
for (width, height) in [
|
||||
(100, 50), // vertically split screen
|
||||
(200, 50), // 1080p fullscreen with medium font
|
||||
(256, 256), // Max sized area
|
||||
] {
|
||||
let buffer_area = buffer_size.area();
|
||||
let buffer_size = Rect::new(0, 0, width, height);
|
||||
|
||||
// Render an empty block
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("render_empty", buffer_area),
|
||||
format!("render_empty/{width}x{height}"),
|
||||
&Block::new(),
|
||||
|b, block| render(b, block, buffer_size),
|
||||
);
|
||||
|
||||
// Render with all features
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("render_all_feature", buffer_area),
|
||||
format!("render_all_feature/{width}x{height}"),
|
||||
&Block::new()
|
||||
.borders(Borders::ALL)
|
||||
.title("test title")
|
||||
|
||||
4
clippy.toml
Normal file
4
clippy.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
# https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_crate_versions
|
||||
# ratatui -> bitflags v2.3
|
||||
# termwiz -> wezterm-blob-leases -> mac_address -> nix -> bitflags v1.3.2
|
||||
allowed-duplicate-crates = ["bitflags"]
|
||||
8
examples/ansi_buffer.rs
Normal file
8
examples/ansi_buffer.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use ratatui::{prelude::*, widgets::ansi_string_buffer::AnsiStringBuffer};
|
||||
|
||||
fn main() {
|
||||
let mut buf = AnsiStringBuffer::new(5, 2);
|
||||
buf.render_ref(&Line::styled("Hello", Color::Blue), Rect::new(0, 0, 5, 1));
|
||||
buf.render_ref(&Line::styled("World", Color::Green), Rect::new(0, 1, 5, 1));
|
||||
println!("{buf}");
|
||||
}
|
||||
12
examples/vhs/widget_ext.tape
Normal file
12
examples/vhs/widget_ext.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/hello_world.tape`
|
||||
Output "target/widget_ext.gif"
|
||||
Set Theme "Aardvark Blue"
|
||||
Set Width 1200
|
||||
Set Height 300
|
||||
Set TypingSpeed 10ms
|
||||
Type "cargo run --example=widget_ext --features=unstable-widget-ext"
|
||||
Enter
|
||||
Sleep 2s
|
||||
Screenshot "target/widget_ext.png"
|
||||
Sleep 1s
|
||||
9
examples/widget_ext.rs
Normal file
9
examples/widget_ext.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use ratatui::{prelude::*, widgets::widget_ext::WidgetExt};
|
||||
|
||||
fn main() {
|
||||
let greeting = Text::from(vec![
|
||||
Line::styled("Hello", Color::Blue),
|
||||
Line::styled("World ", Color::Green),
|
||||
]);
|
||||
println!("{}", greeting.to_ansi_string(5, 2));
|
||||
}
|
||||
@@ -55,22 +55,21 @@ pub struct Buffer {
|
||||
|
||||
impl Buffer {
|
||||
/// Returns a Buffer with all cells set to the default one
|
||||
#[must_use]
|
||||
pub fn empty(area: Rect) -> Self {
|
||||
let cell = Cell::default();
|
||||
Self::filled(area, &cell)
|
||||
Self::filled(area, &Cell::default())
|
||||
}
|
||||
|
||||
/// Returns a Buffer with all cells initialized with the attributes of the given Cell
|
||||
#[must_use]
|
||||
pub fn filled(area: Rect, cell: &Cell) -> Self {
|
||||
let size = area.area() as usize;
|
||||
let mut content = Vec::with_capacity(size);
|
||||
for _ in 0..size {
|
||||
content.push(cell.clone());
|
||||
}
|
||||
let content = vec![cell.clone(); size];
|
||||
Self { area, content }
|
||||
}
|
||||
|
||||
/// Returns a Buffer containing the given lines
|
||||
#[must_use]
|
||||
pub fn with_lines<'a, Iter>(lines: Iter) -> Self
|
||||
where
|
||||
Iter: IntoIterator,
|
||||
@@ -97,12 +96,14 @@ impl Buffer {
|
||||
}
|
||||
|
||||
/// Returns a reference to Cell at the given coordinates
|
||||
#[track_caller]
|
||||
pub fn get(&self, x: u16, y: u16) -> &Cell {
|
||||
let i = self.index_of(x, y);
|
||||
&self.content[i]
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to Cell at the given coordinates
|
||||
#[track_caller]
|
||||
pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
|
||||
let i = self.index_of(x, y);
|
||||
&mut self.content[i]
|
||||
@@ -134,6 +135,7 @@ impl Buffer {
|
||||
/// // starts at (200, 100).
|
||||
/// buffer.index_of(0, 0); // Panics
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn index_of(&self, x: u16, y: u16) -> usize {
|
||||
debug_assert!(
|
||||
x >= self.area.left()
|
||||
|
||||
@@ -66,7 +66,6 @@ use std::{
|
||||
///
|
||||
/// [ANSI color table]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub enum Color {
|
||||
/// Resets the foreground or background color
|
||||
#[default]
|
||||
@@ -141,14 +140,102 @@ impl Color {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for Color {
|
||||
/// This utilises the [`Display`] implementation for serialization.
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for Color {
|
||||
/// This is used to deserialize a value into Color via serde.
|
||||
///
|
||||
/// This implementation uses the `FromStr` trait to deserialize strings, so named colours, RGB,
|
||||
/// and indexed values are able to be deserialized. In addition, values that were produced by
|
||||
/// the the older serialization implementation of Color are also able to be deserialized.
|
||||
///
|
||||
/// Prior to v0.26.0, Ratatui would be serialized using a map for indexed and RGB values, for
|
||||
/// examples in json `{"Indexed": 10}` and `{"Rgb": [255, 0, 255]}` respectively. Now they are
|
||||
/// serialized using the string representation of the index and the RGB hex value, for example
|
||||
/// in json it would now be `"10"` and `"#FF00FF"` respectively.
|
||||
///
|
||||
/// See the [`Color`] documentation for more information on color names.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::prelude::*;
|
||||
///
|
||||
/// #[derive(Debug, serde::Deserialize)]
|
||||
/// struct Theme {
|
||||
/// color: Color,
|
||||
/// }
|
||||
///
|
||||
/// # fn get_theme() -> Result<(), serde_json::Error> {
|
||||
/// let theme: Theme = serde_json::from_str(r#"{"color": "bright-white"}"#)?;
|
||||
/// assert_eq!(theme.color, Color::White);
|
||||
///
|
||||
/// let theme: Theme = serde_json::from_str(r##"{"color": "#00FF00"}"##)?;
|
||||
/// assert_eq!(theme.color, Color::Rgb(0, 255, 0));
|
||||
///
|
||||
/// let theme: Theme = serde_json::from_str(r#"{"color": "42"}"#)?;
|
||||
/// assert_eq!(theme.color, Color::Indexed(42));
|
||||
///
|
||||
/// let err = serde_json::from_str::<Theme>(r#"{"color": "invalid"}"#).unwrap_err();
|
||||
/// assert!(err.is_data());
|
||||
/// assert_eq!(
|
||||
/// err.to_string(),
|
||||
/// "Failed to parse Colors at line 1 column 20"
|
||||
/// );
|
||||
///
|
||||
/// // Deserializing from the previous serialization implementation
|
||||
/// let theme: Theme = serde_json::from_str(r#"{"color": {"Rgb":[255,0,255]}}"#)?;
|
||||
/// assert_eq!(theme.color, Color::Rgb(255, 0, 255));
|
||||
///
|
||||
/// let theme: Theme = serde_json::from_str(r#"{"color": {"Indexed":10}}"#)?;
|
||||
/// assert_eq!(theme.color, Color::Indexed(10));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
FromStr::from_str(&s).map_err(serde::de::Error::custom)
|
||||
/// Colors are currently serialized with the `Display` implementation, so
|
||||
/// RGB values are serialized via hex, for example "#FFFFFF".
|
||||
///
|
||||
/// Previously they were serialized using serde derive, which encoded
|
||||
/// RGB values as a map, for example { "rgb": [255, 255, 255] }.
|
||||
///
|
||||
/// The deserialization implementation utilises a `Helper` struct
|
||||
/// to be able to support both formats for backwards compatibility.
|
||||
#[derive(serde::Deserialize)]
|
||||
enum ColorWrapper {
|
||||
Rgb(u8, u8, u8),
|
||||
Indexed(u8),
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum ColorFormat {
|
||||
V2(String),
|
||||
V1(ColorWrapper),
|
||||
}
|
||||
|
||||
let multi_type = ColorFormat::deserialize(deserializer)
|
||||
.map_err(|err| serde::de::Error::custom(format!("Failed to parse Colors: {err}")))?;
|
||||
match multi_type {
|
||||
ColorFormat::V2(s) => FromStr::from_str(&s).map_err(serde::de::Error::custom),
|
||||
ColorFormat::V1(color_wrapper) => match color_wrapper {
|
||||
ColorWrapper::Rgb(red, green, blue) => Ok(Self::Rgb(red, green, blue)),
|
||||
ColorWrapper::Indexed(index) => Ok(Self::Indexed(index)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,4 +666,42 @@ mod tests {
|
||||
Color::deserialize("#00000000".into_deserializer());
|
||||
assert!(color.is_err());
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn serialize_then_deserialize() -> Result<(), serde_json::Error> {
|
||||
let json_rgb = serde_json::to_string(&Color::Rgb(255, 0, 255))?;
|
||||
assert_eq!(json_rgb, r##""#FF00FF""##);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Color>(&json_rgb)?,
|
||||
Color::Rgb(255, 0, 255)
|
||||
);
|
||||
|
||||
let json_white = serde_json::to_string(&Color::White)?;
|
||||
assert_eq!(json_white, r#""White""#);
|
||||
|
||||
let json_indexed = serde_json::to_string(&Color::Indexed(10))?;
|
||||
assert_eq!(json_indexed, r#""10""#);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Color>(&json_indexed)?,
|
||||
Color::Indexed(10)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn deserialize_with_previous_format() -> Result<(), serde_json::Error> {
|
||||
assert_eq!(Color::White, serde_json::from_str::<Color>("\"White\"")?);
|
||||
assert_eq!(
|
||||
Color::Rgb(255, 0, 255),
|
||||
serde_json::from_str::<Color>(r#"{"Rgb":[255,0,255]}"#)?
|
||||
);
|
||||
assert_eq!(
|
||||
Color::Indexed(10),
|
||||
serde_json::from_str::<Color>(r#"{"Indexed":10}"#)?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
//! - [`Tabs`]: displays a tab bar and allows selection.
|
||||
//!
|
||||
//! [`Canvas`]: crate::widgets::canvas::Canvas
|
||||
pub mod ansi_string_buffer;
|
||||
mod barchart;
|
||||
pub mod block;
|
||||
mod borders;
|
||||
@@ -37,6 +38,7 @@ mod scrollbar;
|
||||
mod sparkline;
|
||||
mod table;
|
||||
mod tabs;
|
||||
pub mod widget_ext;
|
||||
|
||||
pub use self::{
|
||||
barchart::{Bar, BarChart, BarGroup},
|
||||
|
||||
169
src/widgets/ansi_string_buffer.rs
Normal file
169
src/widgets/ansi_string_buffer.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
//! A buffer that allows widgets to easily be rendered to a string with ANSI escape sequences.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A buffer that allows widgets to easily be rendered to a string with ANSI escape sequences.
|
||||
#[stability::unstable(
|
||||
feature = "widget-ext",
|
||||
issue = "https://github.com/ratatui-org/ratatui/issues/1045"
|
||||
)]
|
||||
pub struct AnsiStringBuffer {
|
||||
/// The area of the buffer.
|
||||
pub area: Rect,
|
||||
buf: Buffer,
|
||||
}
|
||||
|
||||
impl AnsiStringBuffer {
|
||||
/// Creates a new `AnsiStringBuffer` with the given width and height.
|
||||
pub fn new(width: u16, height: u16) -> Self {
|
||||
Self {
|
||||
area: Rect::new(0, 0, width, height),
|
||||
buf: Buffer::empty(Rect::new(0, 0, width, height)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the given widget to the buffer.
|
||||
pub fn render_ref(&mut self, widget: &impl WidgetRef, area: Rect) {
|
||||
widget.render_ref(area, &mut self.buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AnsiStringBuffer {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let mut last_style = None;
|
||||
for y in 0..self.area.height {
|
||||
if y > 0 {
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
for x in 0..self.area.width {
|
||||
let cell = self.buf.get(x, y);
|
||||
let style = (cell.fg, cell.bg, cell.modifier);
|
||||
if last_style.is_none() || last_style != Some(style) {
|
||||
write_cell_style(f, cell)?;
|
||||
last_style = Some(style);
|
||||
}
|
||||
f.write_str(cell.symbol())?;
|
||||
}
|
||||
}
|
||||
f.write_str("\u{1b}[0m")
|
||||
}
|
||||
}
|
||||
|
||||
fn write_cell_style(f: &mut Formatter, cell: &buffer::Cell) -> fmt::Result {
|
||||
f.write_str("\u{1b}[")?;
|
||||
write_modifier(f, cell.modifier)?;
|
||||
write_fg(f, cell.fg)?;
|
||||
write_bg(f, cell.bg)?;
|
||||
f.write_str("m")
|
||||
}
|
||||
|
||||
fn write_modifier(f: &mut Formatter, modifier: Modifier) -> fmt::Result {
|
||||
if modifier.contains(Modifier::BOLD) {
|
||||
f.write_str("1;")?;
|
||||
}
|
||||
if modifier.contains(Modifier::DIM) {
|
||||
f.write_str("2;")?;
|
||||
}
|
||||
if modifier.contains(Modifier::ITALIC) {
|
||||
f.write_str("3;")?;
|
||||
}
|
||||
if modifier.contains(Modifier::UNDERLINED) {
|
||||
f.write_str("4;")?;
|
||||
}
|
||||
if modifier.contains(Modifier::SLOW_BLINK) {
|
||||
f.write_str("5;")?;
|
||||
}
|
||||
if modifier.contains(Modifier::RAPID_BLINK) {
|
||||
f.write_str("6;")?;
|
||||
}
|
||||
if modifier.contains(Modifier::REVERSED) {
|
||||
f.write_str("7;")?;
|
||||
}
|
||||
if modifier.contains(Modifier::HIDDEN) {
|
||||
f.write_str("8;")?;
|
||||
}
|
||||
if modifier.contains(Modifier::CROSSED_OUT) {
|
||||
f.write_str("9;")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_fg(f: &mut Formatter, color: Color) -> fmt::Result {
|
||||
f.write_str(match color {
|
||||
Color::Reset => "39",
|
||||
Color::Black => "30",
|
||||
Color::Red => "31",
|
||||
Color::Green => "32",
|
||||
Color::Yellow => "33",
|
||||
Color::Blue => "34",
|
||||
Color::Magenta => "35",
|
||||
Color::Cyan => "36",
|
||||
Color::Gray => "37",
|
||||
Color::DarkGray => "90",
|
||||
Color::LightRed => "91",
|
||||
Color::LightGreen => "92",
|
||||
Color::LightYellow => "93",
|
||||
Color::LightBlue => "94",
|
||||
Color::LightMagenta => "95",
|
||||
Color::LightCyan => "96",
|
||||
Color::White => "97",
|
||||
_ => "",
|
||||
})?;
|
||||
if let Color::Rgb(red, green, blue) = color {
|
||||
f.write_fmt(format_args!("38;2;{red};{green};{blue}"))?;
|
||||
}
|
||||
if let Color::Indexed(i) = color {
|
||||
f.write_fmt(format_args!("38;5;{i}"))?;
|
||||
}
|
||||
f.write_str(";")
|
||||
}
|
||||
|
||||
fn write_bg(f: &mut Formatter, color: Color) -> fmt::Result {
|
||||
f.write_str(match color {
|
||||
Color::Reset => "49",
|
||||
Color::Black => "40",
|
||||
Color::Red => "41",
|
||||
Color::Green => "42",
|
||||
Color::Yellow => "43",
|
||||
Color::Blue => "44",
|
||||
Color::Magenta => "45",
|
||||
Color::Cyan => "46",
|
||||
Color::Gray => "47",
|
||||
Color::DarkGray => "100",
|
||||
Color::LightRed => "101",
|
||||
Color::LightGreen => "102",
|
||||
Color::LightYellow => "103",
|
||||
Color::LightBlue => "104",
|
||||
Color::LightMagenta => "105",
|
||||
Color::LightCyan => "106",
|
||||
Color::White => "107",
|
||||
_ => "",
|
||||
})?;
|
||||
if let Color::Rgb(red, green, blue) = color {
|
||||
f.write_fmt(format_args!("48;2;{red};{green};{blue}"))?;
|
||||
}
|
||||
if let Color::Indexed(i) = color {
|
||||
f.write_fmt(format_args!("48;5;{i}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
let mut buf = AnsiStringBuffer::new(5, 2);
|
||||
buf.render_ref(&Line::styled("Hello", Color::Blue), Rect::new(0, 0, 5, 1));
|
||||
buf.render_ref(&Line::styled("World", Color::Green), Rect::new(0, 1, 5, 1));
|
||||
let ansi_string = buf.to_string();
|
||||
// println!("{ansi_string}"); // uncomment to visually inspect the output
|
||||
assert_eq!(
|
||||
ansi_string,
|
||||
"\u{1b}[34;49mHello\n\u{1b}[32;49mWorld\u{1b}[0m"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,14 @@ pub struct Monthly<'a, DS: DateStyler> {
|
||||
|
||||
impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
/// Construct a calendar for the `display_date` and highlight the `events`
|
||||
pub fn new(display_date: Date, events: DS) -> Self {
|
||||
pub const fn new(display_date: Date, events: DS) -> Self {
|
||||
Self {
|
||||
display_date,
|
||||
events,
|
||||
show_surrounding: None,
|
||||
show_weekday: None,
|
||||
show_month: None,
|
||||
default_style: Style::default(),
|
||||
default_style: Style::new(),
|
||||
block: None,
|
||||
}
|
||||
}
|
||||
@@ -90,10 +90,10 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||
}
|
||||
|
||||
/// Return a style with only the background from the default style
|
||||
fn default_bg(&self) -> Style {
|
||||
const fn default_bg(&self) -> Style {
|
||||
match self.default_style.bg {
|
||||
None => Style::default(),
|
||||
Some(c) => Style::default().bg(c),
|
||||
None => Style::new(),
|
||||
Some(c) => Style::new().bg(c),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ impl<DS: DateStyler> Monthly<'_, DS> {
|
||||
|
||||
let mut y = days_area.y;
|
||||
// go through all the weeks containing a day in the target month.
|
||||
while curr_day.month() as u8 != self.display_date.month().next() as u8 {
|
||||
while curr_day.month() != self.display_date.month().next() {
|
||||
let mut spans = Vec::with_capacity(14);
|
||||
for i in 0..7 {
|
||||
// Draw the gutter. Do it here so we can avoid worrying about
|
||||
|
||||
@@ -27,7 +27,7 @@ pub enum MapResolution {
|
||||
}
|
||||
|
||||
impl MapResolution {
|
||||
fn data(self) -> &'static [(f64, f64)] {
|
||||
const fn data(self) -> &'static [(f64, f64)] {
|
||||
match self {
|
||||
Self::Low => &WORLD_LOW_RESOLUTION,
|
||||
Self::High => &WORLD_HIGH_RESOLUTION,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/// [Source data](http://www.gnuplotting.org/plotting-the-world-revisited)
|
||||
pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
|
||||
pub const WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
|
||||
(-163.7128, -78.5956),
|
||||
(-163.1058, -78.2233),
|
||||
(-161.2451, -78.3801),
|
||||
@@ -5127,7 +5127,7 @@ pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
|
||||
(180.0, -84.71338),
|
||||
];
|
||||
|
||||
pub static WORLD_LOW_RESOLUTION: [(f64, f64); 1166] = [
|
||||
pub const WORLD_LOW_RESOLUTION: [(f64, f64); 1166] = [
|
||||
(-92.32, 48.24),
|
||||
(-88.13, 48.92),
|
||||
(-83.11, 46.27),
|
||||
|
||||
@@ -60,8 +60,11 @@ impl TableState {
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let state = TableState::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
offset: 0,
|
||||
selected: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the index of the first row to be displayed
|
||||
|
||||
50
src/widgets/widget_ext.rs
Normal file
50
src/widgets/widget_ext.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
//! A module that provides an extension trait for widgets that provides methods that are useful for
|
||||
//! debugging.
|
||||
|
||||
use super::ansi_string_buffer::AnsiStringBuffer;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// An extension trait for widgets that provides methods that are useful for debugging.
|
||||
#[stability::unstable(
|
||||
feature = "widget-ext",
|
||||
issue = "https://github.com/ratatui-org/ratatui/issues/1045"
|
||||
)]
|
||||
pub trait WidgetExt {
|
||||
/// Returns a string representation of the widget with ANSI escape sequences for the terminal.
|
||||
fn to_ansi_string(&self, width: u16, height: u16) -> String;
|
||||
}
|
||||
|
||||
impl<W: WidgetRef> WidgetExt for W {
|
||||
fn to_ansi_string(&self, width: u16, height: u16) -> String {
|
||||
let mut buf = AnsiStringBuffer::new(width, height);
|
||||
buf.render_ref(self, buf.area);
|
||||
buf.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod widget_ext_tests {
|
||||
use super::*;
|
||||
|
||||
struct Greeting;
|
||||
|
||||
impl WidgetRef for Greeting {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let text = Text::from(vec![
|
||||
Line::styled("Hello", Color::Blue),
|
||||
Line::styled("World ", Color::Green),
|
||||
]);
|
||||
text.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widget_ext_to_ansi_string() {
|
||||
let ansi_string = Greeting.to_ansi_string(5, 2);
|
||||
// println!("{ansi_string}"); // uncomment to visually inspect the output
|
||||
assert_eq!(
|
||||
ansi_string,
|
||||
"\u{1b}[34;49mHello\n\u{1b}[32;49mWorld\u{1b}[0m"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user