2024-01-24 19:50:18 +00:00
|
|
|
//! # [Ratatui] Gauge example
|
|
|
|
//!
|
|
|
|
//! The latest version of this example is available in the [examples] folder in the repository.
|
|
|
|
//!
|
|
|
|
//! Please note that the examples are designed to be run against the `main` branch of the Github
|
|
|
|
//! repository. This means that you may not be able to compile with the latest release version on
|
|
|
|
//! crates.io, or the one that you have installed locally.
|
|
|
|
//!
|
|
|
|
//! See the [examples readme] for more information on finding examples that match the version of the
|
|
|
|
//! library you are using.
|
|
|
|
//!
|
2024-08-21 18:35:08 +00:00
|
|
|
//! [Ratatui]: https://github.com/ratatui/ratatui
|
|
|
|
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
|
|
|
|
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
|
2024-01-24 19:50:18 +00:00
|
|
|
|
feat(terminal): Add ratatui::init() and restore() methods (#1289)
These are simple opinionated methods for creating a terminal that is
useful to use in most apps. The new init method creates a crossterm
backend writing to stdout, enables raw mode, enters the alternate
screen, and sets a panic handler that restores the terminal on panic.
A minimal hello world now looks a bit like:
```rust
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("Failed to draw");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
```
A type alias `DefaultTerminal` is added to represent this terminal
type and to simplify any cases where applications need to pass this
terminal around. It is equivalent to:
`Terminal<CrosstermBackend<Stdout>>`
We also added `ratatui::try_init()` and `try_restore()`, for situations
where you might want to handle initialization errors yourself instead
of letting the panic handler fire and cleanup. Simple Apps should
prefer the `init` and `restore` functions over these functions.
Corresponding functions to allow passing a `TerminalOptions` with
a `Viewport` (e.g. inline, fixed) are also available
(`init_with_options`,
and `try_init_with_options`).
The existing code to create a backend and terminal will remain and
is not deprecated by this approach. This just provides a simple one
line initialization using the common options.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
|
|
|
use std::time::Duration;
|
2023-06-12 05:07:15 +00:00
|
|
|
|
feat(terminal): Add ratatui::init() and restore() methods (#1289)
These are simple opinionated methods for creating a terminal that is
useful to use in most apps. The new init method creates a crossterm
backend writing to stdout, enables raw mode, enters the alternate
screen, and sets a panic handler that restores the terminal on panic.
A minimal hello world now looks a bit like:
```rust
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("Failed to draw");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
```
A type alias `DefaultTerminal` is added to represent this terminal
type and to simplify any cases where applications need to pass this
terminal around. It is equivalent to:
`Terminal<CrosstermBackend<Stdout>>`
We also added `ratatui::try_init()` and `try_restore()`, for situations
where you might want to handle initialization errors yourself instead
of letting the panic handler fire and cleanup. Simple Apps should
prefer the `init` and `restore` functions over these functions.
Corresponding functions to allow passing a `TerminalOptions` with
a `Viewport` (e.g. inline, fixed) are also available
(`init_with_options`,
and `try_init_with_options`).
The existing code to create a backend and terminal will remain and
is not deprecated by this approach. This just provides a simple one
line initialization using the common options.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
|
|
|
use color_eyre::Result;
|
2023-10-19 11:29:53 +00:00
|
|
|
use ratatui::{
|
2024-05-29 11:42:29 +00:00
|
|
|
buffer::Buffer,
|
feat(terminal): Add ratatui::init() and restore() methods (#1289)
These are simple opinionated methods for creating a terminal that is
useful to use in most apps. The new init method creates a crossterm
backend writing to stdout, enables raw mode, enters the alternate
screen, and sets a panic handler that restores the terminal on panic.
A minimal hello world now looks a bit like:
```rust
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("Failed to draw");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
```
A type alias `DefaultTerminal` is added to represent this terminal
type and to simplify any cases where applications need to pass this
terminal around. It is equivalent to:
`Terminal<CrosstermBackend<Stdout>>`
We also added `ratatui::try_init()` and `try_restore()`, for situations
where you might want to handle initialization errors yourself instead
of letting the panic handler fire and cleanup. Simple Apps should
prefer the `init` and `restore` functions over these functions.
Corresponding functions to allow passing a `TerminalOptions` with
a `Viewport` (e.g. inline, fixed) are also available
(`init_with_options`,
and `try_init_with_options`).
The existing code to create a backend and terminal will remain and
is not deprecated by this approach. This just provides a simple one
line initialization using the common options.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
|
|
|
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
2024-05-29 11:42:29 +00:00
|
|
|
layout::{Alignment, Constraint, Layout, Rect},
|
|
|
|
style::{palette::tailwind, Color, Style, Stylize},
|
|
|
|
text::Span,
|
|
|
|
widgets::{block::Title, Block, Borders, Gauge, Padding, Paragraph, Widget},
|
feat(terminal): Add ratatui::init() and restore() methods (#1289)
These are simple opinionated methods for creating a terminal that is
useful to use in most apps. The new init method creates a crossterm
backend writing to stdout, enables raw mode, enters the alternate
screen, and sets a panic handler that restores the terminal on panic.
A minimal hello world now looks a bit like:
```rust
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("Failed to draw");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
```
A type alias `DefaultTerminal` is added to represent this terminal
type and to simplify any cases where applications need to pass this
terminal around. It is equivalent to:
`Terminal<CrosstermBackend<Stdout>>`
We also added `ratatui::try_init()` and `try_restore()`, for situations
where you might want to handle initialization errors yourself instead
of letting the panic handler fire and cleanup. Simple Apps should
prefer the `init` and `restore` functions over these functions.
Corresponding functions to allow passing a `TerminalOptions` with
a `Viewport` (e.g. inline, fixed) are also available
(`init_with_options`,
and `try_init_with_options`).
The existing code to create a backend and terminal will remain and
is not deprecated by this approach. This just provides a simple one
line initialization using the common options.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
|
|
|
DefaultTerminal,
|
2023-10-19 11:29:53 +00:00
|
|
|
};
|
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
const GAUGE1_COLOR: Color = tailwind::RED.c800;
|
|
|
|
const GAUGE2_COLOR: Color = tailwind::GREEN.c800;
|
|
|
|
const GAUGE3_COLOR: Color = tailwind::BLUE.c800;
|
|
|
|
const GAUGE4_COLOR: Color = tailwind::ORANGE.c800;
|
|
|
|
const CUSTOM_LABEL_COLOR: Color = tailwind::SLATE.c200;
|
2018-09-23 18:59:51 +00:00
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
#[derive(Debug, Default, Clone, Copy)]
|
2016-11-07 23:35:46 +00:00
|
|
|
struct App {
|
2023-10-19 11:29:53 +00:00
|
|
|
state: AppState,
|
2024-01-27 09:47:17 +00:00
|
|
|
progress_columns: u16,
|
2016-11-07 23:35:46 +00:00
|
|
|
progress1: u16,
|
2024-01-27 09:47:17 +00:00
|
|
|
progress2: f64,
|
2018-11-27 01:02:37 +00:00
|
|
|
progress3: f64,
|
2024-01-27 09:47:17 +00:00
|
|
|
progress4: f64,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
|
|
enum AppState {
|
|
|
|
#[default]
|
|
|
|
Running,
|
|
|
|
Started,
|
|
|
|
Quitting,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> Result<()> {
|
feat(terminal): Add ratatui::init() and restore() methods (#1289)
These are simple opinionated methods for creating a terminal that is
useful to use in most apps. The new init method creates a crossterm
backend writing to stdout, enables raw mode, enters the alternate
screen, and sets a panic handler that restores the terminal on panic.
A minimal hello world now looks a bit like:
```rust
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("Failed to draw");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
```
A type alias `DefaultTerminal` is added to represent this terminal
type and to simplify any cases where applications need to pass this
terminal around. It is equivalent to:
`Terminal<CrosstermBackend<Stdout>>`
We also added `ratatui::try_init()` and `try_restore()`, for situations
where you might want to handle initialization errors yourself instead
of letting the panic handler fire and cleanup. Simple Apps should
prefer the `init` and `restore` functions over these functions.
Corresponding functions to allow passing a `TerminalOptions` with
a `Viewport` (e.g. inline, fixed) are also available
(`init_with_options`,
and `try_init_with_options`).
The existing code to create a backend and terminal will remain and
is not deprecated by this approach. This just provides a simple one
line initialization using the common options.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
|
|
|
color_eyre::install()?;
|
|
|
|
let terminal = ratatui::init();
|
|
|
|
let app_result = App::default().run(terminal);
|
|
|
|
ratatui::restore();
|
|
|
|
app_result
|
2016-11-07 23:35:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl App {
|
feat(terminal): Add ratatui::init() and restore() methods (#1289)
These are simple opinionated methods for creating a terminal that is
useful to use in most apps. The new init method creates a crossterm
backend writing to stdout, enables raw mode, enters the alternate
screen, and sets a panic handler that restores the terminal on panic.
A minimal hello world now looks a bit like:
```rust
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("Failed to draw");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
```
A type alias `DefaultTerminal` is added to represent this terminal
type and to simplify any cases where applications need to pass this
terminal around. It is equivalent to:
`Terminal<CrosstermBackend<Stdout>>`
We also added `ratatui::try_init()` and `try_restore()`, for situations
where you might want to handle initialization errors yourself instead
of letting the panic handler fire and cleanup. Simple Apps should
prefer the `init` and `restore` functions over these functions.
Corresponding functions to allow passing a `TerminalOptions` with
a `Viewport` (e.g. inline, fixed) are also available
(`init_with_options`,
and `try_init_with_options`).
The existing code to create a backend and terminal will remain and
is not deprecated by this approach. This just provides a simple one
line initialization using the common options.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
|
|
|
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
2024-01-27 09:47:17 +00:00
|
|
|
while self.state != AppState::Quitting {
|
feat(terminal): Add ratatui::init() and restore() methods (#1289)
These are simple opinionated methods for creating a terminal that is
useful to use in most apps. The new init method creates a crossterm
backend writing to stdout, enables raw mode, enters the alternate
screen, and sets a panic handler that restores the terminal on panic.
A minimal hello world now looks a bit like:
```rust
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("Failed to draw");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
```
A type alias `DefaultTerminal` is added to represent this terminal
type and to simplify any cases where applications need to pass this
terminal around. It is equivalent to:
`Terminal<CrosstermBackend<Stdout>>`
We also added `ratatui::try_init()` and `try_restore()`, for situations
where you might want to handle initialization errors yourself instead
of letting the panic handler fire and cleanup. Simple Apps should
prefer the `init` and `restore` functions over these functions.
Corresponding functions to allow passing a `TerminalOptions` with
a `Viewport` (e.g. inline, fixed) are also available
(`init_with_options`,
and `try_init_with_options`).
The existing code to create a backend and terminal will remain and
is not deprecated by this approach. This just provides a simple one
line initialization using the common options.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
2024-08-22 12:16:35 +00:00
|
|
|
terminal.draw(|frame| frame.render_widget(&self, frame.area()))?;
|
2024-01-27 09:47:17 +00:00
|
|
|
self.handle_events()?;
|
|
|
|
self.update(terminal.size()?.width);
|
2016-11-07 23:35:46 +00:00
|
|
|
}
|
2023-10-19 11:29:53 +00:00
|
|
|
Ok(())
|
2016-11-07 23:35:46 +00:00
|
|
|
}
|
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
fn update(&mut self, terminal_width: u16) {
|
|
|
|
if self.state != AppState::Started {
|
|
|
|
return;
|
|
|
|
}
|
2023-10-19 11:29:53 +00:00
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
// progress1 and progress2 help show the difference between ratio and percentage measuring
|
|
|
|
// the same thing, but converting to either a u16 or f64. Effectively, we're showing the
|
|
|
|
// difference between how a continuous gauge acts for floor and rounded values.
|
|
|
|
self.progress_columns = (self.progress_columns + 1).clamp(0, terminal_width);
|
|
|
|
self.progress1 = self.progress_columns * 100 / terminal_width;
|
2024-03-02 09:06:53 +00:00
|
|
|
self.progress2 = f64::from(self.progress_columns) * 100.0 / f64::from(terminal_width);
|
2024-01-27 09:47:17 +00:00
|
|
|
|
|
|
|
// progress3 and progress4 similarly show the difference between unicode and non-unicode
|
|
|
|
// gauges measuring the same thing.
|
|
|
|
self.progress3 = (self.progress3 + 0.1).clamp(40.0, 100.0);
|
|
|
|
self.progress4 = (self.progress4 + 0.1).clamp(40.0, 100.0);
|
2023-10-19 11:29:53 +00:00
|
|
|
}
|
2019-12-15 20:38:18 +00:00
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
fn handle_events(&mut self) -> Result<()> {
|
|
|
|
let timeout = Duration::from_secs_f32(1.0 / 20.0);
|
2023-10-19 11:29:53 +00:00
|
|
|
if event::poll(timeout)? {
|
2021-11-01 21:27:46 +00:00
|
|
|
if let Event::Key(key) = event::read()? {
|
2023-10-19 11:29:53 +00:00
|
|
|
if key.kind == KeyEventKind::Press {
|
2024-01-27 09:47:17 +00:00
|
|
|
match key.code {
|
2024-05-29 11:42:29 +00:00
|
|
|
KeyCode::Char(' ') | KeyCode::Enter => self.start(),
|
|
|
|
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
|
2024-01-27 09:47:17 +00:00
|
|
|
_ => {}
|
2023-10-19 11:29:53 +00:00
|
|
|
}
|
2018-12-07 15:17:33 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-01 21:27:46 +00:00
|
|
|
}
|
2023-10-19 11:29:53 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
fn start(&mut self) {
|
|
|
|
self.state = AppState::Started;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn quit(&mut self) {
|
|
|
|
self.state = AppState::Quitting;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Widget for &App {
|
2024-03-02 09:06:53 +00:00
|
|
|
#[allow(clippy::similar_names)]
|
2024-01-27 09:47:17 +00:00
|
|
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
2024-05-29 11:42:29 +00:00
|
|
|
use Constraint::{Length, Min, Ratio};
|
2024-01-27 09:47:17 +00:00
|
|
|
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
|
2024-02-02 04:26:35 +00:00
|
|
|
let [header_area, gauge_area, footer_area] = layout.areas(area);
|
2024-01-27 09:47:17 +00:00
|
|
|
|
|
|
|
let layout = Layout::vertical([Ratio(1, 4); 4]);
|
2024-02-02 04:26:35 +00:00
|
|
|
let [gauge1_area, gauge2_area, gauge3_area, gauge4_area] = layout.areas(gauge_area);
|
2024-01-27 09:47:17 +00:00
|
|
|
|
2024-03-02 09:06:53 +00:00
|
|
|
render_header(header_area, buf);
|
|
|
|
render_footer(footer_area, buf);
|
2024-01-27 09:47:17 +00:00
|
|
|
|
|
|
|
self.render_gauge1(gauge1_area, buf);
|
|
|
|
self.render_gauge2(gauge2_area, buf);
|
|
|
|
self.render_gauge3(gauge3_area, buf);
|
|
|
|
self.render_gauge4(gauge4_area, buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-02 09:06:53 +00:00
|
|
|
fn render_header(area: Rect, buf: &mut Buffer) {
|
|
|
|
Paragraph::new("Ratatui Gauge Example")
|
|
|
|
.bold()
|
|
|
|
.alignment(Alignment::Center)
|
|
|
|
.fg(CUSTOM_LABEL_COLOR)
|
|
|
|
.render(area, buf);
|
|
|
|
}
|
2024-01-27 09:47:17 +00:00
|
|
|
|
2024-03-02 09:06:53 +00:00
|
|
|
fn render_footer(area: Rect, buf: &mut Buffer) {
|
|
|
|
Paragraph::new("Press ENTER to start")
|
|
|
|
.alignment(Alignment::Center)
|
|
|
|
.fg(CUSTOM_LABEL_COLOR)
|
|
|
|
.bold()
|
|
|
|
.render(area, buf);
|
|
|
|
}
|
2023-10-19 11:29:53 +00:00
|
|
|
|
2024-03-02 09:06:53 +00:00
|
|
|
impl App {
|
2024-01-27 09:47:17 +00:00
|
|
|
fn render_gauge1(&self, area: Rect, buf: &mut Buffer) {
|
|
|
|
let title = title_block("Gauge with percentage");
|
|
|
|
Gauge::default()
|
2023-10-19 11:29:53 +00:00
|
|
|
.block(title)
|
2024-01-27 09:47:17 +00:00
|
|
|
.gauge_style(GAUGE1_COLOR)
|
|
|
|
.percent(self.progress1)
|
|
|
|
.render(area, buf);
|
2023-10-19 11:29:53 +00:00
|
|
|
}
|
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
fn render_gauge2(&self, area: Rect, buf: &mut Buffer) {
|
|
|
|
let title = title_block("Gauge with ratio and custom label");
|
2023-10-19 11:29:53 +00:00
|
|
|
let label = Span::styled(
|
2024-01-27 09:47:17 +00:00
|
|
|
format!("{:.1}/100", self.progress2),
|
|
|
|
Style::new().italic().bold().fg(CUSTOM_LABEL_COLOR),
|
2023-10-19 11:29:53 +00:00
|
|
|
);
|
2024-01-27 09:47:17 +00:00
|
|
|
Gauge::default()
|
2023-10-19 11:29:53 +00:00
|
|
|
.block(title)
|
2024-01-27 09:47:17 +00:00
|
|
|
.gauge_style(GAUGE2_COLOR)
|
|
|
|
.ratio(self.progress2 / 100.0)
|
2023-10-19 11:29:53 +00:00
|
|
|
.label(label)
|
2024-01-27 09:47:17 +00:00
|
|
|
.render(area, buf);
|
2023-10-19 11:29:53 +00:00
|
|
|
}
|
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
fn render_gauge3(&self, area: Rect, buf: &mut Buffer) {
|
|
|
|
let title = title_block("Gauge with ratio (no unicode)");
|
|
|
|
let label = format!("{:.1}%", self.progress3);
|
|
|
|
Gauge::default()
|
2023-10-19 11:29:53 +00:00
|
|
|
.block(title)
|
2024-01-27 09:47:17 +00:00
|
|
|
.gauge_style(GAUGE3_COLOR)
|
|
|
|
.ratio(self.progress3 / 100.0)
|
|
|
|
.label(label)
|
|
|
|
.render(area, buf);
|
2023-10-19 11:29:53 +00:00
|
|
|
}
|
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
fn render_gauge4(&self, area: Rect, buf: &mut Buffer) {
|
|
|
|
let title = title_block("Gauge with ratio (unicode)");
|
|
|
|
let label = format!("{:.1}%", self.progress3);
|
|
|
|
Gauge::default()
|
|
|
|
.block(title)
|
|
|
|
.gauge_style(GAUGE4_COLOR)
|
|
|
|
.ratio(self.progress4 / 100.0)
|
|
|
|
.label(label)
|
|
|
|
.use_unicode(true)
|
|
|
|
.render(area, buf);
|
2023-10-19 11:29:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-27 09:47:17 +00:00
|
|
|
fn title_block(title: &str) -> Block {
|
|
|
|
let title = Title::from(title).alignment(Alignment::Center);
|
2024-05-02 10:09:48 +00:00
|
|
|
Block::new()
|
2024-01-27 09:47:17 +00:00
|
|
|
.borders(Borders::NONE)
|
|
|
|
.padding(Padding::vertical(1))
|
2024-05-02 10:09:48 +00:00
|
|
|
.title(title)
|
|
|
|
.fg(CUSTOM_LABEL_COLOR)
|
2021-11-01 21:27:46 +00:00
|
|
|
}
|