mirror of
https://github.com/ratatui-org/ratatui
synced 2025-02-18 06:58:51 +00:00
Support several modifiers and indexed colors at once
This commit is contained in:
parent
d360cd3434
commit
b7664a4108
17 changed files with 265 additions and 197 deletions
|
@ -97,7 +97,7 @@ fn main() -> Result<(), failure::Error> {
|
||||||
.bar_width(5)
|
.bar_width(5)
|
||||||
.bar_gap(3)
|
.bar_gap(3)
|
||||||
.style(Style::default().fg(Color::Green))
|
.style(Style::default().fg(Color::Green))
|
||||||
.value_style(Style::default().bg(Color::Green).modifier(Modifier::Bold))
|
.value_style(Style::default().bg(Color::Green).modifier(Modifier::BOLD))
|
||||||
.render(&mut f, chunks[0]);
|
.render(&mut f, chunks[0]);
|
||||||
BarChart::default()
|
BarChart::default()
|
||||||
.block(Block::default().title("Data3").borders(Borders::ALL))
|
.block(Block::default().title("Data3").borders(Borders::ALL))
|
||||||
|
@ -106,7 +106,7 @@ fn main() -> Result<(), failure::Error> {
|
||||||
.bar_width(7)
|
.bar_width(7)
|
||||||
.bar_gap(0)
|
.bar_gap(0)
|
||||||
.value_style(Style::default().bg(Color::Red))
|
.value_style(Style::default().bg(Color::Red))
|
||||||
.label_style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic))
|
.label_style(Style::default().fg(Color::Cyan).modifier(Modifier::ITALIC))
|
||||||
.render(&mut f, chunks[1]);
|
.render(&mut f, chunks[1]);
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -54,7 +54,7 @@ fn main() -> Result<(), failure::Error> {
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::White)
|
.fg(Color::White)
|
||||||
.bg(Color::Red)
|
.bg(Color::Red)
|
||||||
.modifier(Modifier::Bold),
|
.modifier(Modifier::BOLD),
|
||||||
)
|
)
|
||||||
.render(&mut f, chunks[1]);
|
.render(&mut f, chunks[1]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,14 +73,14 @@ fn main() -> Result<(), failure::Error> {
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title("Chart")
|
.title("Chart")
|
||||||
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold))
|
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::BOLD))
|
||||||
.borders(Borders::ALL),
|
.borders(Borders::ALL),
|
||||||
)
|
)
|
||||||
.x_axis(
|
.x_axis(
|
||||||
Axis::default()
|
Axis::default()
|
||||||
.title("X Axis")
|
.title("X Axis")
|
||||||
.style(Style::default().fg(Color::Gray))
|
.style(Style::default().fg(Color::White))
|
||||||
.labels_style(Style::default().modifier(Modifier::Italic))
|
.labels_style(Style::default().modifier(Modifier::ITALIC))
|
||||||
.bounds(app.window)
|
.bounds(app.window)
|
||||||
.labels(&[
|
.labels(&[
|
||||||
&format!("{}", app.window[0]),
|
&format!("{}", app.window[0]),
|
||||||
|
@ -91,8 +91,8 @@ fn main() -> Result<(), failure::Error> {
|
||||||
.y_axis(
|
.y_axis(
|
||||||
Axis::default()
|
Axis::default()
|
||||||
.title("Y Axis")
|
.title("Y Axis")
|
||||||
.style(Style::default().fg(Color::Gray))
|
.style(Style::default().fg(Color::White))
|
||||||
.labels_style(Style::default().modifier(Modifier::Italic))
|
.labels_style(Style::default().modifier(Modifier::ITALIC))
|
||||||
.bounds([-20.0, 20.0])
|
.bounds([-20.0, 20.0])
|
||||||
.labels(&["-20", "0", "20"]),
|
.labels(&["-20", "0", "20"]),
|
||||||
)
|
)
|
||||||
|
|
|
@ -69,7 +69,7 @@ where
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Magenta)
|
.fg(Color::Magenta)
|
||||||
.bg(Color::Black)
|
.bg(Color::Black)
|
||||||
.modifier(Modifier::Italic),
|
.modifier(Modifier::ITALIC),
|
||||||
)
|
)
|
||||||
.label(&format!("{} / 100", app.progress))
|
.label(&format!("{} / 100", app.progress))
|
||||||
.percent(app.progress)
|
.percent(app.progress)
|
||||||
|
@ -107,7 +107,7 @@ where
|
||||||
.block(Block::default().borders(Borders::ALL).title("List"))
|
.block(Block::default().borders(Borders::ALL).title("List"))
|
||||||
.items(&app.tasks.items)
|
.items(&app.tasks.items)
|
||||||
.select(Some(app.tasks.selected))
|
.select(Some(app.tasks.selected))
|
||||||
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
|
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::BOLD))
|
||||||
.highlight_symbol(">")
|
.highlight_symbol(">")
|
||||||
.render(f, chunks[0]);
|
.render(f, chunks[0]);
|
||||||
let info_style = Style::default().fg(Color::White);
|
let info_style = Style::default().fg(Color::White);
|
||||||
|
@ -138,7 +138,7 @@ where
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Black)
|
.fg(Color::Black)
|
||||||
.bg(Color::Green)
|
.bg(Color::Green)
|
||||||
.modifier(Modifier::Italic),
|
.modifier(Modifier::ITALIC),
|
||||||
)
|
)
|
||||||
.label_style(Style::default().fg(Color::Yellow))
|
.label_style(Style::default().fg(Color::Yellow))
|
||||||
.style(Style::default().fg(Color::Green))
|
.style(Style::default().fg(Color::Green))
|
||||||
|
@ -149,14 +149,14 @@ where
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title("Chart")
|
.title("Chart")
|
||||||
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold))
|
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::BOLD))
|
||||||
.borders(Borders::ALL),
|
.borders(Borders::ALL),
|
||||||
)
|
)
|
||||||
.x_axis(
|
.x_axis(
|
||||||
Axis::default()
|
Axis::default()
|
||||||
.title("X Axis")
|
.title("X Axis")
|
||||||
.style(Style::default().fg(Color::Gray))
|
.style(Style::default().fg(Color::Gray))
|
||||||
.labels_style(Style::default().modifier(Modifier::Italic))
|
.labels_style(Style::default().modifier(Modifier::ITALIC))
|
||||||
.bounds(app.signals.window)
|
.bounds(app.signals.window)
|
||||||
.labels(&[
|
.labels(&[
|
||||||
&format!("{}", app.signals.window[0]),
|
&format!("{}", app.signals.window[0]),
|
||||||
|
@ -168,7 +168,7 @@ where
|
||||||
Axis::default()
|
Axis::default()
|
||||||
.title("Y Axis")
|
.title("Y Axis")
|
||||||
.style(Style::default().fg(Color::Gray))
|
.style(Style::default().fg(Color::Gray))
|
||||||
.labels_style(Style::default().modifier(Modifier::Italic))
|
.labels_style(Style::default().modifier(Modifier::ITALIC))
|
||||||
.bounds([-20.0, 20.0])
|
.bounds([-20.0, 20.0])
|
||||||
.labels(&["-20", "0", "20"]),
|
.labels(&["-20", "0", "20"]),
|
||||||
)
|
)
|
||||||
|
@ -200,13 +200,13 @@ where
|
||||||
Text::raw(" "),
|
Text::raw(" "),
|
||||||
Text::styled("rainbow", Style::default().fg(Color::Blue)),
|
Text::styled("rainbow", Style::default().fg(Color::Blue)),
|
||||||
Text::raw(".\nOh and if you didn't "),
|
Text::raw(".\nOh and if you didn't "),
|
||||||
Text::styled("notice", Style::default().modifier(Modifier::Italic)),
|
Text::styled("notice", Style::default().modifier(Modifier::ITALIC)),
|
||||||
Text::raw(" you can "),
|
Text::raw(" you can "),
|
||||||
Text::styled("automatically", Style::default().modifier(Modifier::Bold)),
|
Text::styled("automatically", Style::default().modifier(Modifier::BOLD)),
|
||||||
Text::raw(" "),
|
Text::raw(" "),
|
||||||
Text::styled("wrap", Style::default().modifier(Modifier::Invert)),
|
Text::styled("wrap", Style::default().modifier(Modifier::REVERSED)),
|
||||||
Text::raw(" your "),
|
Text::raw(" your "),
|
||||||
Text::styled("text", Style::default().modifier(Modifier::Underline)),
|
Text::styled("text", Style::default().modifier(Modifier::UNDERLINED)),
|
||||||
Text::raw(".\nOne more thing is that it should display unicode characters: 10€")
|
Text::raw(".\nOne more thing is that it should display unicode characters: 10€")
|
||||||
];
|
];
|
||||||
Paragraph::new(text.iter())
|
Paragraph::new(text.iter())
|
||||||
|
@ -214,7 +214,7 @@ where
|
||||||
Block::default()
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.title("Footer")
|
.title("Footer")
|
||||||
.title_style(Style::default().fg(Color::Magenta).modifier(Modifier::Bold)),
|
.title_style(Style::default().fg(Color::Magenta).modifier(Modifier::BOLD)),
|
||||||
)
|
)
|
||||||
.wrap(true)
|
.wrap(true)
|
||||||
.render(f, area);
|
.render(f, area);
|
||||||
|
|
|
@ -99,7 +99,7 @@ fn main() -> Result<(), failure::Error> {
|
||||||
.render(&mut f, chunks[2]);
|
.render(&mut f, chunks[2]);
|
||||||
Gauge::default()
|
Gauge::default()
|
||||||
.block(Block::default().title("Gauge4").borders(Borders::ALL))
|
.block(Block::default().title("Gauge4").borders(Borders::ALL))
|
||||||
.style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic))
|
.style(Style::default().fg(Color::Cyan).modifier(Modifier::ITALIC))
|
||||||
.percent(app.progress4)
|
.percent(app.progress4)
|
||||||
.label(&format!("{}/100", app.progress2))
|
.label(&format!("{}/100", app.progress2))
|
||||||
.render(&mut f, chunks[3]);
|
.render(&mut f, chunks[3]);
|
||||||
|
|
|
@ -102,7 +102,7 @@ fn main() -> Result<(), failure::Error> {
|
||||||
.items(&app.items)
|
.items(&app.items)
|
||||||
.select(app.selected)
|
.select(app.selected)
|
||||||
.style(style)
|
.style(style)
|
||||||
.highlight_style(style.fg(Color::LightGreen).modifier(Modifier::Bold))
|
.highlight_style(style.fg(Color::LightGreen).modifier(Modifier::BOLD))
|
||||||
.highlight_symbol(">")
|
.highlight_symbol(">")
|
||||||
.render(&mut f, chunks[0]);
|
.render(&mut f, chunks[0]);
|
||||||
{
|
{
|
||||||
|
|
|
@ -60,18 +60,18 @@ fn main() -> Result<(), failure::Error> {
|
||||||
Text::styled("This is a line\n", Style::default().bg(Color::Blue)),
|
Text::styled("This is a line\n", Style::default().bg(Color::Blue)),
|
||||||
Text::styled(
|
Text::styled(
|
||||||
"This is a longer line\n",
|
"This is a longer line\n",
|
||||||
Style::default().modifier(Modifier::CrossedOut),
|
Style::default().modifier(Modifier::CROSSED_OUT),
|
||||||
),
|
),
|
||||||
Text::styled(&long_line, Style::default().bg(Color::Green)),
|
Text::styled(&long_line, Style::default().bg(Color::Green)),
|
||||||
Text::styled(
|
Text::styled(
|
||||||
"This is a line\n",
|
"This is a line\n",
|
||||||
Style::default().fg(Color::Green).modifier(Modifier::Italic),
|
Style::default().fg(Color::Green).modifier(Modifier::ITALIC),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.title_style(Style::default().modifier(Modifier::Bold));
|
.title_style(Style::default().modifier(Modifier::BOLD));
|
||||||
Paragraph::new(text.iter())
|
Paragraph::new(text.iter())
|
||||||
.block(block.clone().title("Left, no wrap"))
|
.block(block.clone().title("Left, no wrap"))
|
||||||
.alignment(Alignment::Left)
|
.alignment(Alignment::Left)
|
||||||
|
|
|
@ -53,7 +53,7 @@ fn main() -> Result<(), failure::Error> {
|
||||||
// Input
|
// Input
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|mut f| {
|
terminal.draw(|mut f| {
|
||||||
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold);
|
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::BOLD);
|
||||||
let normal_style = Style::default().fg(Color::White);
|
let normal_style = Style::default().fg(Color::White);
|
||||||
let header = ["Header1", "Header2", "Header3"];
|
let header = ["Header1", "Header2", "Header3"];
|
||||||
let rows = app.items.iter().enumerate().map(|(i, item)| {
|
let rows = app.items.iter().enumerate().map(|(i, item)| {
|
||||||
|
|
|
@ -149,9 +149,8 @@ impl Backend for CrosstermBackend {
|
||||||
if let Some(color) = cell.style.bg.into() {
|
if let Some(color) = cell.style.bg.into() {
|
||||||
s = s.on(color)
|
s = s.on(color)
|
||||||
}
|
}
|
||||||
if let Some(attr) = cell.style.modifier.into() {
|
s.object_style.attrs = cell.style.modifier.into();
|
||||||
s = s.attr(attr)
|
|
||||||
}
|
|
||||||
self.crossterm.paint(s).map_err(convert_error)?;
|
self.crossterm.paint(s).map_err(convert_error)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -178,32 +177,59 @@ impl From<Color> for Option<crossterm::Color> {
|
||||||
Color::LightMagenta => Some(crossterm::Color::Magenta),
|
Color::LightMagenta => Some(crossterm::Color::Magenta),
|
||||||
Color::LightCyan => Some(crossterm::Color::Cyan),
|
Color::LightCyan => Some(crossterm::Color::Cyan),
|
||||||
Color::White => Some(crossterm::Color::White),
|
Color::White => Some(crossterm::Color::White),
|
||||||
|
Color::Indexed(i) => Some(crossterm::Color::AnsiValue(i)),
|
||||||
Color::Rgb(r, g, b) => Some(crossterm::Color::Rgb { r, g, b }),
|
Color::Rgb(r, g, b) => Some(crossterm::Color::Rgb { r, g, b }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Modifier> for Option<crossterm::Attribute> {
|
impl From<Modifier> for Vec<crossterm::Attribute> {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn from(modifier: Modifier) -> Option<crossterm::Attribute> {
|
fn from(modifier: Modifier) -> Vec<crossterm::Attribute> {
|
||||||
match modifier {
|
let mut result = Vec::new();
|
||||||
Modifier::Blink => Some(crossterm::Attribute::SlowBlink),
|
|
||||||
Modifier::Bold => Some(crossterm::Attribute::Bold),
|
if modifier.contains(Modifier::BOLD) {
|
||||||
Modifier::CrossedOut => Some(crossterm::Attribute::CrossedOut),
|
result.push(crossterm::Attribute::Bold)
|
||||||
Modifier::Faint => Some(crossterm::Attribute::Dim),
|
|
||||||
Modifier::Invert => Some(crossterm::Attribute::Reverse),
|
|
||||||
Modifier::Italic => Some(crossterm::Attribute::Italic),
|
|
||||||
Modifier::Underline => Some(crossterm::Attribute::Underlined),
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
|
if modifier.contains(Modifier::DIM) {
|
||||||
|
result.push(crossterm::Attribute::Dim)
|
||||||
|
}
|
||||||
|
if modifier.contains(Modifier::ITALIC) {
|
||||||
|
result.push(crossterm::Attribute::Italic)
|
||||||
|
}
|
||||||
|
if modifier.contains(Modifier::UNDERLINED) {
|
||||||
|
result.push(crossterm::Attribute::Underlined)
|
||||||
|
}
|
||||||
|
if modifier.contains(Modifier::SLOW_BLINK) {
|
||||||
|
result.push(crossterm::Attribute::SlowBlink)
|
||||||
|
}
|
||||||
|
if modifier.contains(Modifier::RAPID_BLINK) {
|
||||||
|
result.push(crossterm::Attribute::RapidBlink)
|
||||||
|
}
|
||||||
|
if modifier.contains(Modifier::REVERSED) {
|
||||||
|
result.push(crossterm::Attribute::Reverse)
|
||||||
|
}
|
||||||
|
if modifier.contains(Modifier::HIDDEN) {
|
||||||
|
result.push(crossterm::Attribute::Hidden)
|
||||||
|
}
|
||||||
|
if modifier.contains(Modifier::CROSSED_OUT) {
|
||||||
|
result.push(crossterm::Attribute::CrossedOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn from(modifier: Modifier) -> Option<crossterm::Attribute> {
|
fn from(modifier: Modifier) -> Vec<crossterm::Attribute> {
|
||||||
match modifier {
|
let mut result = Vec::new();
|
||||||
Modifier::Bold => Some(crossterm::Attribute::Bold),
|
|
||||||
Modifier::Underline => Some(crossterm::Attribute::Underlined),
|
if modifier.contains(Modifier::BOLD) {
|
||||||
_ => None,
|
result.push(crossterm::Attribute::Bold)
|
||||||
}
|
}
|
||||||
|
if modifier.contains(Modifier::UNDERLINED) {
|
||||||
|
result.push(crossterm::Attribute::Underlined)
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ impl Into<rustbox::Color> for Color {
|
||||||
Color::Cyan | Color::LightCyan => rustbox::Color::Cyan,
|
Color::Cyan | Color::LightCyan => rustbox::Color::Cyan,
|
||||||
Color::White => rustbox::Color::White,
|
Color::White => rustbox::Color::White,
|
||||||
Color::Blue | Color::LightBlue => rustbox::Color::Blue,
|
Color::Blue | Color::LightBlue => rustbox::Color::Blue,
|
||||||
|
Color::Indexed(i) => rustbox::Color::Byte(i as u16),
|
||||||
Color::Rgb(r, g, b) => rustbox::Color::Byte(rgb_to_byte(r, g, b)),
|
Color::Rgb(r, g, b) => rustbox::Color::Byte(rgb_to_byte(r, g, b)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,11 +111,16 @@ impl Into<rustbox::Color> for Color {
|
||||||
|
|
||||||
impl Into<rustbox::Style> for Modifier {
|
impl Into<rustbox::Style> for Modifier {
|
||||||
fn into(self) -> rustbox::Style {
|
fn into(self) -> rustbox::Style {
|
||||||
match self {
|
let mut result = rustbox::Style::empty();
|
||||||
Modifier::Bold => rustbox::RB_BOLD,
|
if self.contains(Modifier::BOLD) {
|
||||||
Modifier::Underline => rustbox::RB_UNDERLINE,
|
result.insert(rustbox::RB_BOLD);
|
||||||
Modifier::Invert => rustbox::RB_REVERSE,
|
|
||||||
_ => rustbox::RB_NORMAL,
|
|
||||||
}
|
}
|
||||||
|
if self.contains(Modifier::UNDERLINED) {
|
||||||
|
result.insert(rustbox::RB_UNDERLINE);
|
||||||
|
}
|
||||||
|
if self.contains(Modifier::REVERSED) {
|
||||||
|
result.insert(rustbox::RB_REVERSE);
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use super::Backend;
|
use super::Backend;
|
||||||
use crate::buffer::Cell;
|
use crate::buffer::Cell;
|
||||||
use crate::layout::Rect;
|
use crate::layout::Rect;
|
||||||
use crate::style::{Color, Modifier, Style};
|
use crate::style;
|
||||||
|
|
||||||
pub struct TermionBackend<W>
|
pub struct TermionBackend<W>
|
||||||
where
|
where
|
||||||
|
@ -74,34 +75,40 @@ where
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||||
{
|
{
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
let mut string = String::with_capacity(content.size_hint().0 * 3);
|
let mut string = String::with_capacity(content.size_hint().0 * 3);
|
||||||
let mut style = Style::default();
|
let mut style = style::Style::default();
|
||||||
let mut last_y = 0;
|
let mut last_y = 0;
|
||||||
let mut last_x = 0;
|
let mut last_x = 0;
|
||||||
let mut inst = 0;
|
let mut inst = 0;
|
||||||
for (x, y, cell) in content {
|
for (x, y, cell) in content {
|
||||||
if y != last_y || x != last_x + 1 || inst == 0 {
|
if y != last_y || x != last_x + 1 || inst == 0 {
|
||||||
string.push_str(&format!("{}", termion::cursor::Goto(x + 1, y + 1)));
|
write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap();
|
||||||
inst += 1;
|
inst += 1;
|
||||||
}
|
}
|
||||||
last_x = x;
|
last_x = x;
|
||||||
last_y = y;
|
last_y = y;
|
||||||
if cell.style.modifier != style.modifier {
|
if cell.style.modifier != style.modifier {
|
||||||
string.push_str(&cell.style.modifier.termion_modifier());
|
write!(
|
||||||
|
string,
|
||||||
|
"{}",
|
||||||
|
ModifierDiff {
|
||||||
|
from: style.modifier,
|
||||||
|
to: cell.style.modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
style.modifier = cell.style.modifier;
|
style.modifier = cell.style.modifier;
|
||||||
if style.modifier == Modifier::Reset {
|
|
||||||
style.bg = Color::Reset;
|
|
||||||
style.fg = Color::Reset;
|
|
||||||
}
|
|
||||||
inst += 1;
|
inst += 1;
|
||||||
}
|
}
|
||||||
if cell.style.fg != style.fg {
|
if cell.style.fg != style.fg {
|
||||||
string.push_str(&cell.style.fg.termion_fg());
|
write!(string, "{}", Fg(cell.style.fg)).unwrap();
|
||||||
style.fg = cell.style.fg;
|
style.fg = cell.style.fg;
|
||||||
inst += 1;
|
inst += 1;
|
||||||
}
|
}
|
||||||
if cell.style.bg != style.bg {
|
if cell.style.bg != style.bg {
|
||||||
string.push_str(&cell.style.bg.termion_bg());
|
write!(string, "{}", Bg(cell.style.bg)).unwrap();
|
||||||
style.bg = cell.style.bg;
|
style.bg = cell.style.bg;
|
||||||
inst += 1;
|
inst += 1;
|
||||||
}
|
}
|
||||||
|
@ -113,9 +120,9 @@ where
|
||||||
self.stdout,
|
self.stdout,
|
||||||
"{}{}{}{}",
|
"{}{}{}{}",
|
||||||
string,
|
string,
|
||||||
Color::Reset.termion_fg(),
|
Fg(style::Color::Reset),
|
||||||
Color::Reset.termion_bg(),
|
Bg(style::Color::Reset),
|
||||||
Modifier::Reset.termion_modifier()
|
termion::style::Reset,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,102 +137,118 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! termion_fg {
|
struct Fg(style::Color);
|
||||||
($color:ident) => {
|
|
||||||
format!("{}", termion::color::Fg(termion::color::$color))
|
struct Bg(style::Color);
|
||||||
};
|
|
||||||
|
struct ModifierDiff {
|
||||||
|
from: style::Modifier,
|
||||||
|
to: style::Modifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! termion_fg_rgb {
|
impl fmt::Display for Fg {
|
||||||
($r:expr, $g:expr, $b:expr) => {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
format!("{}", termion::color::Fg(termion::color::Rgb($r, $g, $b)))
|
use termion::color::Color;
|
||||||
};
|
match self.0 {
|
||||||
}
|
style::Color::Reset => termion::color::Reset.write_fg(f),
|
||||||
|
style::Color::Black => termion::color::Black.write_fg(f),
|
||||||
macro_rules! termion_bg {
|
style::Color::Red => termion::color::Red.write_fg(f),
|
||||||
($color:ident) => {
|
style::Color::Green => termion::color::Green.write_fg(f),
|
||||||
format!("{}", termion::color::Bg(termion::color::$color))
|
style::Color::Yellow => termion::color::Yellow.write_fg(f),
|
||||||
};
|
style::Color::Blue => termion::color::Blue.write_fg(f),
|
||||||
}
|
style::Color::Magenta => termion::color::Magenta.write_fg(f),
|
||||||
|
style::Color::Cyan => termion::color::Cyan.write_fg(f),
|
||||||
macro_rules! termion_bg_rgb {
|
style::Color::Gray => termion::color::White.write_fg(f),
|
||||||
($r:expr, $g:expr, $b:expr) => {
|
style::Color::DarkGray => termion::color::LightBlack.write_fg(f),
|
||||||
format!("{}", termion::color::Bg(termion::color::Rgb($r, $g, $b)))
|
style::Color::LightRed => termion::color::LightRed.write_fg(f),
|
||||||
};
|
style::Color::LightGreen => termion::color::LightGreen.write_fg(f),
|
||||||
}
|
style::Color::LightBlue => termion::color::LightBlue.write_fg(f),
|
||||||
|
style::Color::LightYellow => termion::color::LightYellow.write_fg(f),
|
||||||
macro_rules! termion_modifier {
|
style::Color::LightMagenta => termion::color::LightMagenta.write_fg(f),
|
||||||
($style:ident) => {
|
style::Color::LightCyan => termion::color::LightCyan.write_fg(f),
|
||||||
format!("{}", termion::style::$style)
|
style::Color::White => termion::color::LightWhite.write_fg(f),
|
||||||
};
|
style::Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f),
|
||||||
}
|
style::Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f),
|
||||||
|
|
||||||
impl Color {
|
|
||||||
pub fn termion_fg(self) -> String {
|
|
||||||
match self {
|
|
||||||
Color::Reset => termion_fg!(Reset),
|
|
||||||
Color::Black => termion_fg!(Black),
|
|
||||||
Color::Red => termion_fg!(Red),
|
|
||||||
Color::Green => termion_fg!(Green),
|
|
||||||
Color::Yellow => termion_fg!(Yellow),
|
|
||||||
Color::Blue => termion_fg!(Blue),
|
|
||||||
Color::Magenta => termion_fg!(Magenta),
|
|
||||||
Color::Cyan => termion_fg!(Cyan),
|
|
||||||
Color::Gray => termion_fg!(White),
|
|
||||||
Color::DarkGray => termion_fg!(LightBlack),
|
|
||||||
Color::LightRed => termion_fg!(LightRed),
|
|
||||||
Color::LightGreen => termion_fg!(LightGreen),
|
|
||||||
Color::LightBlue => termion_fg!(LightBlue),
|
|
||||||
Color::LightYellow => termion_fg!(LightYellow),
|
|
||||||
Color::LightMagenta => termion_fg!(LightMagenta),
|
|
||||||
Color::LightCyan => termion_fg!(LightCyan),
|
|
||||||
Color::White => termion_fg!(LightWhite),
|
|
||||||
Color::Rgb(r, g, b) => termion_fg_rgb!(r, g, b),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn termion_bg(self) -> String {
|
}
|
||||||
match self {
|
impl fmt::Display for Bg {
|
||||||
Color::Reset => termion_bg!(Reset),
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
Color::Black => termion_bg!(Black),
|
use termion::color::Color;
|
||||||
Color::Red => termion_bg!(Red),
|
match self.0 {
|
||||||
Color::Green => termion_bg!(Green),
|
style::Color::Reset => termion::color::Reset.write_bg(f),
|
||||||
Color::Yellow => termion_bg!(Yellow),
|
style::Color::Black => termion::color::Black.write_bg(f),
|
||||||
Color::Blue => termion_bg!(Blue),
|
style::Color::Red => termion::color::Red.write_bg(f),
|
||||||
Color::Magenta => termion_bg!(Magenta),
|
style::Color::Green => termion::color::Green.write_bg(f),
|
||||||
Color::Cyan => termion_bg!(Cyan),
|
style::Color::Yellow => termion::color::Yellow.write_bg(f),
|
||||||
Color::Gray => termion_bg!(White),
|
style::Color::Blue => termion::color::Blue.write_bg(f),
|
||||||
Color::DarkGray => termion_bg!(LightBlack),
|
style::Color::Magenta => termion::color::Magenta.write_bg(f),
|
||||||
Color::LightRed => termion_bg!(LightRed),
|
style::Color::Cyan => termion::color::Cyan.write_bg(f),
|
||||||
Color::LightGreen => termion_bg!(LightGreen),
|
style::Color::Gray => termion::color::White.write_bg(f),
|
||||||
Color::LightBlue => termion_bg!(LightBlue),
|
style::Color::DarkGray => termion::color::LightBlack.write_bg(f),
|
||||||
Color::LightYellow => termion_bg!(LightYellow),
|
style::Color::LightRed => termion::color::LightRed.write_bg(f),
|
||||||
Color::LightMagenta => termion_bg!(LightMagenta),
|
style::Color::LightGreen => termion::color::LightGreen.write_bg(f),
|
||||||
Color::LightCyan => termion_bg!(LightCyan),
|
style::Color::LightBlue => termion::color::LightBlue.write_bg(f),
|
||||||
Color::White => termion_bg!(LightWhite),
|
style::Color::LightYellow => termion::color::LightYellow.write_bg(f),
|
||||||
Color::Rgb(r, g, b) => termion_bg_rgb!(r, g, b),
|
style::Color::LightMagenta => termion::color::LightMagenta.write_bg(f),
|
||||||
|
style::Color::LightCyan => termion::color::LightCyan.write_bg(f),
|
||||||
|
style::Color::White => termion::color::LightWhite.write_bg(f),
|
||||||
|
style::Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f),
|
||||||
|
style::Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modifier {
|
impl fmt::Display for ModifierDiff {
|
||||||
pub fn termion_modifier(self) -> String {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
let remove = self.from - self.to;
|
||||||
Modifier::Blink => termion_modifier!(Blink),
|
if remove.contains(style::Modifier::REVERSED) {
|
||||||
Modifier::Bold => termion_modifier!(Bold),
|
write!(f, "{}", termion::style::NoInvert)?;
|
||||||
Modifier::CrossedOut => termion_modifier!(CrossedOut),
|
|
||||||
Modifier::Faint => termion_modifier!(Faint),
|
|
||||||
Modifier::Framed => termion_modifier!(Framed),
|
|
||||||
Modifier::Invert => termion_modifier!(Invert),
|
|
||||||
Modifier::Italic => termion_modifier!(Italic),
|
|
||||||
Modifier::NoBlink => termion_modifier!(NoBlink),
|
|
||||||
Modifier::NoBold => termion_modifier!(NoBold),
|
|
||||||
Modifier::NoCrossedOut => termion_modifier!(NoCrossedOut),
|
|
||||||
Modifier::NoFaint => termion_modifier!(NoFaint),
|
|
||||||
Modifier::NoInvert => termion_modifier!(NoInvert),
|
|
||||||
Modifier::NoItalic => termion_modifier!(NoItalic),
|
|
||||||
Modifier::NoUnderline => termion_modifier!(NoUnderline),
|
|
||||||
Modifier::Reset => termion_modifier!(Reset),
|
|
||||||
Modifier::Underline => termion_modifier!(Underline),
|
|
||||||
}
|
}
|
||||||
|
if remove.contains(style::Modifier::BOLD) {
|
||||||
|
write!(f, "{}", termion::style::NoBold)?;
|
||||||
|
}
|
||||||
|
if remove.contains(style::Modifier::ITALIC) {
|
||||||
|
write!(f, "{}", termion::style::NoItalic)?;
|
||||||
|
}
|
||||||
|
if remove.contains(style::Modifier::UNDERLINED) {
|
||||||
|
write!(f, "{}", termion::style::NoUnderline)?;
|
||||||
|
}
|
||||||
|
if remove.contains(style::Modifier::DIM) {
|
||||||
|
write!(f, "{}", termion::style::NoFaint)?;
|
||||||
|
}
|
||||||
|
if remove.contains(style::Modifier::CROSSED_OUT) {
|
||||||
|
write!(f, "{}", termion::style::NoCrossedOut)?;
|
||||||
|
}
|
||||||
|
if remove.contains(style::Modifier::SLOW_BLINK)
|
||||||
|
|| remove.contains(style::Modifier::RAPID_BLINK)
|
||||||
|
{
|
||||||
|
write!(f, "{}", termion::style::NoBlink)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let add = self.to - self.from;
|
||||||
|
if add.contains(style::Modifier::REVERSED) {
|
||||||
|
write!(f, "{}", termion::style::Invert)?;
|
||||||
|
}
|
||||||
|
if add.contains(style::Modifier::BOLD) {
|
||||||
|
write!(f, "{}", termion::style::Bold)?;
|
||||||
|
}
|
||||||
|
if add.contains(style::Modifier::ITALIC) {
|
||||||
|
write!(f, "{}", termion::style::Italic)?;
|
||||||
|
}
|
||||||
|
if add.contains(style::Modifier::UNDERLINED) {
|
||||||
|
write!(f, "{}", termion::style::Underline)?;
|
||||||
|
}
|
||||||
|
if add.contains(style::Modifier::DIM) {
|
||||||
|
write!(f, "{}", termion::style::Faint)?;
|
||||||
|
}
|
||||||
|
if add.contains(style::Modifier::CROSSED_OUT) {
|
||||||
|
write!(f, "{}", termion::style::CrossedOut)?;
|
||||||
|
}
|
||||||
|
if add.contains(style::Modifier::SLOW_BLINK) || add.contains(style::Modifier::RAPID_BLINK) {
|
||||||
|
write!(f, "{}", termion::style::Blink)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ impl Default for Cell {
|
||||||
/// style: Style {
|
/// style: Style {
|
||||||
/// fg: Color::Red,
|
/// fg: Color::Red,
|
||||||
/// bg: Color::White,
|
/// bg: Color::White,
|
||||||
/// modifier: Modifier::Reset
|
/// modifier: Modifier::empty()
|
||||||
/// }});
|
/// }});
|
||||||
/// buf.get_mut(5, 0).set_char('x');
|
/// buf.get_mut(5, 0).set_char('x');
|
||||||
/// assert_eq!(buf.get(5, 0).symbol, "x");
|
/// assert_eq!(buf.get(5, 0).symbol, "x");
|
||||||
|
|
95
src/style.rs
95
src/style.rs
|
@ -1,3 +1,5 @@
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
Reset,
|
Reset,
|
||||||
|
@ -18,6 +20,7 @@ pub enum Color {
|
||||||
LightCyan,
|
LightCyan,
|
||||||
White,
|
White,
|
||||||
Rgb(u8, u8, u8),
|
Rgb(u8, u8, u8),
|
||||||
|
Indexed(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
|
@ -33,62 +36,72 @@ impl Color {
|
||||||
Color::Blue => "b",
|
Color::Blue => "b",
|
||||||
Color::Magenta => "m",
|
Color::Magenta => "m",
|
||||||
Color::Cyan => "c",
|
Color::Cyan => "c",
|
||||||
Color::Gray => "g",
|
Color::Gray => "w",
|
||||||
Color::DarkGray => "G",
|
Color::DarkGray => "B",
|
||||||
Color::LightRed => "R",
|
Color::LightRed => "R",
|
||||||
Color::LightGreen => "G",
|
Color::LightGreen => "G",
|
||||||
Color::LightYellow => "Y",
|
Color::LightYellow => "Y",
|
||||||
Color::LightBlue => "B",
|
Color::LightBlue => "B",
|
||||||
Color::LightMagenta => "M",
|
Color::LightMagenta => "M",
|
||||||
Color::LightCyan => "C",
|
Color::LightCyan => "C",
|
||||||
Color::White => "w",
|
Color::White => "W",
|
||||||
|
Color::Indexed(_) => "i",
|
||||||
Color::Rgb(_, _, _) => "o",
|
Color::Rgb(_, _, _) => "o",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
bitflags! {
|
||||||
pub enum Modifier {
|
pub struct Modifier: u16 {
|
||||||
Blink,
|
const BOLD = 0b0000_0000_0001;
|
||||||
Bold,
|
const DIM = 0b0000_0000_0010;
|
||||||
CrossedOut,
|
const ITALIC = 0b0000_0000_0100;
|
||||||
Faint,
|
const UNDERLINED = 0b0000_0000_1000;
|
||||||
Framed,
|
const SLOW_BLINK = 0b0000_0001_0000;
|
||||||
Invert,
|
const RAPID_BLINK = 0b0000_0010_0000;
|
||||||
Italic,
|
const REVERSED = 0b0000_0100_0000;
|
||||||
NoBlink,
|
const HIDDEN = 0b0000_1000_0000;
|
||||||
NoBold,
|
const CROSSED_OUT = 0b0001_0000_0000;
|
||||||
NoCrossedOut,
|
}
|
||||||
NoFaint,
|
|
||||||
NoInvert,
|
|
||||||
NoItalic,
|
|
||||||
NoUnderline,
|
|
||||||
Reset,
|
|
||||||
Underline,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modifier {
|
impl Modifier {
|
||||||
/// Returns a short code associated with the color, used for debug purpose
|
/// Returns a short code associated with the color, used for debug purpose
|
||||||
/// only
|
/// only
|
||||||
pub(crate) fn code(&self) -> &str {
|
pub(crate) fn code(&self) -> String {
|
||||||
match self {
|
use std::fmt::Write;
|
||||||
Modifier::Blink => "bl",
|
|
||||||
Modifier::Bold => "bo",
|
let mut result = String::new();
|
||||||
Modifier::CrossedOut => "cr",
|
|
||||||
Modifier::Faint => "fa",
|
if self.contains(Modifier::BOLD) {
|
||||||
Modifier::Framed => "fr",
|
write!(result, "BO").unwrap();
|
||||||
Modifier::Invert => "in",
|
|
||||||
Modifier::Italic => "it",
|
|
||||||
Modifier::NoBlink => "BL",
|
|
||||||
Modifier::NoBold => "BO",
|
|
||||||
Modifier::NoCrossedOut => "CR",
|
|
||||||
Modifier::NoFaint => "FA",
|
|
||||||
Modifier::NoInvert => "IN",
|
|
||||||
Modifier::NoItalic => "IT",
|
|
||||||
Modifier::NoUnderline => "UN",
|
|
||||||
Modifier::Reset => "re",
|
|
||||||
Modifier::Underline => "un",
|
|
||||||
}
|
}
|
||||||
|
if self.contains(Modifier::DIM) {
|
||||||
|
write!(result, "DI").unwrap();
|
||||||
|
}
|
||||||
|
if self.contains(Modifier::ITALIC) {
|
||||||
|
write!(result, "IT").unwrap();
|
||||||
|
}
|
||||||
|
if self.contains(Modifier::UNDERLINED) {
|
||||||
|
write!(result, "UN").unwrap();
|
||||||
|
}
|
||||||
|
if self.contains(Modifier::SLOW_BLINK) {
|
||||||
|
write!(result, "SL").unwrap();
|
||||||
|
}
|
||||||
|
if self.contains(Modifier::RAPID_BLINK) {
|
||||||
|
write!(result, "RA").unwrap();
|
||||||
|
}
|
||||||
|
if self.contains(Modifier::REVERSED) {
|
||||||
|
write!(result, "RE").unwrap();
|
||||||
|
}
|
||||||
|
if self.contains(Modifier::HIDDEN) {
|
||||||
|
write!(result, "HI").unwrap();
|
||||||
|
}
|
||||||
|
if self.contains(Modifier::CROSSED_OUT) {
|
||||||
|
write!(result, "CR").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +117,7 @@ impl Default for Style {
|
||||||
Style {
|
Style {
|
||||||
fg: Color::Reset,
|
fg: Color::Reset,
|
||||||
bg: Color::Reset,
|
bg: Color::Reset,
|
||||||
modifier: Modifier::Reset,
|
modifier: Modifier::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +126,7 @@ impl Style {
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.fg = Color::Reset;
|
self.fg = Color::Reset;
|
||||||
self.bg = Color::Reset;
|
self.bg = Color::Reset;
|
||||||
self.modifier = Modifier::Reset;
|
self.modifier = Modifier::empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fg(mut self, color: Color) -> Style {
|
pub fn fg(mut self, color: Color) -> Style {
|
||||||
|
|
|
@ -21,7 +21,7 @@ use crate::widgets::{Block, Widget};
|
||||||
/// .bar_width(3)
|
/// .bar_width(3)
|
||||||
/// .bar_gap(1)
|
/// .bar_gap(1)
|
||||||
/// .style(Style::default().fg(Color::Yellow).bg(Color::Red))
|
/// .style(Style::default().fg(Color::Yellow).bg(Color::Red))
|
||||||
/// .value_style(Style::default().fg(Color::Red).modifier(Modifier::Bold))
|
/// .value_style(Style::default().fg(Color::Red).modifier(Modifier::BOLD))
|
||||||
/// .label_style(Style::default().fg(Color::White))
|
/// .label_style(Style::default().fg(Color::White))
|
||||||
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
|
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
|
||||||
/// .max(4);
|
/// .max(4);
|
||||||
|
|
|
@ -174,13 +174,13 @@ impl Default for ChartLayout {
|
||||||
/// .x_axis(Axis::default()
|
/// .x_axis(Axis::default()
|
||||||
/// .title("X Axis")
|
/// .title("X Axis")
|
||||||
/// .title_style(Style::default().fg(Color::Red))
|
/// .title_style(Style::default().fg(Color::Red))
|
||||||
/// .style(Style::default().fg(Color::Gray))
|
/// .style(Style::default().fg(Color::White))
|
||||||
/// .bounds([0.0, 10.0])
|
/// .bounds([0.0, 10.0])
|
||||||
/// .labels(&["0.0", "5.0", "10.0"]))
|
/// .labels(&["0.0", "5.0", "10.0"]))
|
||||||
/// .y_axis(Axis::default()
|
/// .y_axis(Axis::default()
|
||||||
/// .title("Y Axis")
|
/// .title("Y Axis")
|
||||||
/// .title_style(Style::default().fg(Color::Red))
|
/// .title_style(Style::default().fg(Color::Red))
|
||||||
/// .style(Style::default().fg(Color::Gray))
|
/// .style(Style::default().fg(Color::White))
|
||||||
/// .bounds([0.0, 10.0])
|
/// .bounds([0.0, 10.0])
|
||||||
/// .labels(&["0.0", "5.0", "10.0"]))
|
/// .labels(&["0.0", "5.0", "10.0"]))
|
||||||
/// .datasets(&[Dataset::default()
|
/// .datasets(&[Dataset::default()
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::widgets::{Block, Widget};
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// Gauge::default()
|
/// Gauge::default()
|
||||||
/// .block(Block::default().borders(Borders::ALL).title("Progress"))
|
/// .block(Block::default().borders(Borders::ALL).title("Progress"))
|
||||||
/// .style(Style::default().fg(Color::White).bg(Color::Black).modifier(Modifier::Italic))
|
/// .style(Style::default().fg(Color::White).bg(Color::Black).modifier(Modifier::ITALIC))
|
||||||
/// .percent(20);
|
/// .percent(20);
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -125,7 +125,7 @@ where
|
||||||
/// .items(&["Item 1", "Item 2", "Item 3"])
|
/// .items(&["Item 1", "Item 2", "Item 3"])
|
||||||
/// .select(Some(1))
|
/// .select(Some(1))
|
||||||
/// .style(Style::default().fg(Color::White))
|
/// .style(Style::default().fg(Color::White))
|
||||||
/// .highlight_style(Style::default().modifier(Modifier::Italic))
|
/// .highlight_style(Style::default().modifier(Modifier::ITALIC))
|
||||||
/// .highlight_symbol(">>");
|
/// .highlight_symbol(">>");
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
Loading…
Add table
Reference in a new issue