mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-22 04:33:13 +00:00
refactor(non-src): apply pedantic lints (#976)
Fixes many not yet enabled lints (mostly pedantic) on everything that is not the lib (examples, benchs, tests). Therefore, this is not containing anything that can be a breaking change. Lints are not enabled as that should be the job of #974. I created this as a separate PR as its mostly independent and would only clutter up the diff of #974 even more. Also see https://github.com/ratatui-org/ratatui/pull/974#discussion_r1506458743 --------- Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
This commit is contained in:
parent
94f4547dcf
commit
c12bcfefa2
52 changed files with 637 additions and 531 deletions
|
@ -8,7 +8,7 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Benchmark for rendering a barchart.
|
/// Benchmark for rendering a barchart.
|
||||||
pub fn barchart(c: &mut Criterion) {
|
fn barchart(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("barchart");
|
let mut group = c.benchmark_group("barchart");
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ fn render(bencher: &mut Bencher, barchart: &BarChart) {
|
||||||
bench_barchart.render(buffer.area, &mut buffer);
|
bench_barchart.render(buffer.area, &mut buffer);
|
||||||
},
|
},
|
||||||
criterion::BatchSize::LargeInput,
|
criterion::BatchSize::LargeInput,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, barchart);
|
criterion_group!(benches, barchart);
|
||||||
|
|
|
@ -10,10 +10,10 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Benchmark for rendering a block.
|
/// Benchmark for rendering a block.
|
||||||
pub fn block(c: &mut Criterion) {
|
fn block(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("block");
|
let mut group = c.benchmark_group("block");
|
||||||
|
|
||||||
for buffer_size in &[
|
for buffer_size in [
|
||||||
Rect::new(0, 0, 100, 50), // vertically split screen
|
Rect::new(0, 0, 100, 50), // vertically split screen
|
||||||
Rect::new(0, 0, 200, 50), // 1080p fullscreen with medium font
|
Rect::new(0, 0, 200, 50), // 1080p fullscreen with medium font
|
||||||
Rect::new(0, 0, 256, 256), // Max sized area
|
Rect::new(0, 0, 256, 256), // Max sized area
|
||||||
|
@ -47,8 +47,8 @@ pub fn block(c: &mut Criterion) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// render the block into a buffer of the given `size`
|
/// render the block into a buffer of the given `size`
|
||||||
fn render(bencher: &mut Bencher, block: &Block, size: &Rect) {
|
fn render(bencher: &mut Bencher, block: &Block, size: Rect) {
|
||||||
let mut buffer = Buffer::empty(*size);
|
let mut buffer = Buffer::empty(size);
|
||||||
// We use `iter_batched` to clone the value in the setup function.
|
// We use `iter_batched` to clone the value in the setup function.
|
||||||
// See https://github.com/ratatui-org/ratatui/pull/377.
|
// See https://github.com/ratatui-org/ratatui/pull/377.
|
||||||
bencher.iter_batched(
|
bencher.iter_batched(
|
||||||
|
@ -57,7 +57,7 @@ fn render(bencher: &mut Bencher, block: &Block, size: &Rect) {
|
||||||
bench_block.render(buffer.area, &mut buffer);
|
bench_block.render(buffer.area, &mut buffer);
|
||||||
},
|
},
|
||||||
BatchSize::SmallInput,
|
BatchSize::SmallInput,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, block);
|
criterion_group!(benches, block);
|
||||||
|
|
|
@ -7,7 +7,7 @@ use ratatui::{
|
||||||
|
|
||||||
/// Benchmark for rendering a list.
|
/// Benchmark for rendering a list.
|
||||||
/// It only benchmarks the render with a different amount of items.
|
/// It only benchmarks the render with a different amount of items.
|
||||||
pub fn list(c: &mut Criterion) {
|
fn list(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("list");
|
let mut group = c.benchmark_group("list");
|
||||||
|
|
||||||
for line_count in [64, 2048, 16384] {
|
for line_count in [64, 2048, 16384] {
|
||||||
|
@ -33,7 +33,7 @@ pub fn list(c: &mut Criterion) {
|
||||||
ListState::default()
|
ListState::default()
|
||||||
.with_offset(line_count / 2)
|
.with_offset(line_count / 2)
|
||||||
.with_selected(Some(line_count / 2)),
|
.with_selected(Some(line_count / 2)),
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ fn render(bencher: &mut Bencher, list: &List) {
|
||||||
Widget::render(bench_list, buffer.area, &mut buffer);
|
Widget::render(bench_list, buffer.area, &mut buffer);
|
||||||
},
|
},
|
||||||
BatchSize::LargeInput,
|
BatchSize::LargeInput,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// render the list into a common size buffer with a state
|
/// render the list into a common size buffer with a state
|
||||||
|
@ -66,7 +66,7 @@ fn render_stateful(bencher: &mut Bencher, list: &List, mut state: ListState) {
|
||||||
StatefulWidget::render(bench_list, buffer.area, &mut buffer, &mut state);
|
StatefulWidget::render(bench_list, buffer.area, &mut buffer, &mut state);
|
||||||
},
|
},
|
||||||
BatchSize::LargeInput,
|
BatchSize::LargeInput,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, list);
|
criterion_group!(benches, list);
|
||||||
|
|
|
@ -17,15 +17,15 @@ const WRAP_WIDTH: u16 = 100;
|
||||||
/// Benchmark for rendering a paragraph with a given number of lines. The design of this benchmark
|
/// Benchmark for rendering a paragraph with a given number of lines. The design of this benchmark
|
||||||
/// allows comparison of the performance of rendering a paragraph with different numbers of lines.
|
/// allows comparison of the performance of rendering a paragraph with different numbers of lines.
|
||||||
/// as well as comparing with the various settings on the scroll and wrap features.
|
/// as well as comparing with the various settings on the scroll and wrap features.
|
||||||
pub fn paragraph(c: &mut Criterion) {
|
fn paragraph(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("paragraph");
|
let mut group = c.benchmark_group("paragraph");
|
||||||
for &line_count in [64, 2048, MAX_SCROLL_OFFSET].iter() {
|
for line_count in [64, 2048, MAX_SCROLL_OFFSET] {
|
||||||
let lines = random_lines(line_count);
|
let lines = random_lines(line_count);
|
||||||
let lines = lines.as_str();
|
let lines = lines.as_str();
|
||||||
|
|
||||||
// benchmark that measures the overhead of creating a paragraph separately from rendering
|
// benchmark that measures the overhead of creating a paragraph separately from rendering
|
||||||
group.bench_with_input(BenchmarkId::new("new", line_count), lines, |b, lines| {
|
group.bench_with_input(BenchmarkId::new("new", line_count), lines, |b, lines| {
|
||||||
b.iter(|| Paragraph::new(black_box(lines)))
|
b.iter(|| Paragraph::new(black_box(lines)));
|
||||||
});
|
});
|
||||||
|
|
||||||
// render the paragraph with no scroll
|
// render the paragraph with no scroll
|
||||||
|
@ -38,14 +38,14 @@ pub fn paragraph(c: &mut Criterion) {
|
||||||
// scroll the paragraph by half the number of lines and render
|
// scroll the paragraph by half the number of lines and render
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("render_scroll_half", line_count),
|
BenchmarkId::new("render_scroll_half", line_count),
|
||||||
&Paragraph::new(lines).scroll((0u16, line_count / 2)),
|
&Paragraph::new(lines).scroll((0, line_count / 2)),
|
||||||
|bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH),
|
|bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH),
|
||||||
);
|
);
|
||||||
|
|
||||||
// scroll the paragraph by the full number of lines and render
|
// scroll the paragraph by the full number of lines and render
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("render_scroll_full", line_count),
|
BenchmarkId::new("render_scroll_full", line_count),
|
||||||
&Paragraph::new(lines).scroll((0u16, line_count)),
|
&Paragraph::new(lines).scroll((0, line_count)),
|
||||||
|bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH),
|
|bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ pub fn paragraph(c: &mut Criterion) {
|
||||||
BenchmarkId::new("render_wrap_scroll_full", line_count),
|
BenchmarkId::new("render_wrap_scroll_full", line_count),
|
||||||
&Paragraph::new(lines)
|
&Paragraph::new(lines)
|
||||||
.wrap(Wrap { trim: false })
|
.wrap(Wrap { trim: false })
|
||||||
.scroll((0u16, line_count)),
|
.scroll((0, line_count)),
|
||||||
|bencher, paragraph| render(bencher, paragraph, WRAP_WIDTH),
|
|bencher, paragraph| render(bencher, paragraph, WRAP_WIDTH),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ fn render(bencher: &mut Bencher, paragraph: &Paragraph, width: u16) {
|
||||||
bench_paragraph.render(buffer.area, &mut buffer);
|
bench_paragraph.render(buffer.area, &mut buffer);
|
||||||
},
|
},
|
||||||
BatchSize::LargeInput,
|
BatchSize::LargeInput,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a string with the given number of lines filled with nonsense words
|
/// Create a string with the given number of lines filled with nonsense words
|
||||||
|
@ -87,7 +87,7 @@ fn render(bencher: &mut Bencher, paragraph: &Paragraph, width: u16) {
|
||||||
/// English language has about 5.1 average characters per word so including the space between words
|
/// English language has about 5.1 average characters per word so including the space between words
|
||||||
/// this should emit around 200 characters per paragraph on average.
|
/// this should emit around 200 characters per paragraph on average.
|
||||||
fn random_lines(count: u16) -> String {
|
fn random_lines(count: u16) -> String {
|
||||||
let count = count as i64;
|
let count = i64::from(count);
|
||||||
let sentence_count = 3;
|
let sentence_count = 3;
|
||||||
let word_count = 11;
|
let word_count = 11;
|
||||||
fakeit::words::paragraph(count, sentence_count, word_count, "\n".into())
|
fakeit::words::paragraph(count, sentence_count, word_count, "\n".into())
|
||||||
|
|
|
@ -7,7 +7,7 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Benchmark for rendering a sparkline.
|
/// Benchmark for rendering a sparkline.
|
||||||
pub fn sparkline(c: &mut Criterion) {
|
fn sparkline(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("sparkline");
|
let mut group = c.benchmark_group("sparkline");
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ fn render(bencher: &mut Bencher, sparkline: &Sparkline) {
|
||||||
bench_sparkline.render(buffer.area, &mut buffer);
|
bench_sparkline.render(buffer.area, &mut buffer);
|
||||||
},
|
},
|
||||||
criterion::BatchSize::LargeInput,
|
criterion::BatchSize::LargeInput,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, sparkline);
|
criterion_group!(benches, sparkline);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! # [Ratatui] BarChart example
|
//! # [Ratatui] `BarChart` example
|
||||||
//!
|
//!
|
||||||
//! The latest version of this example is available in the [examples] folder in the repository.
|
//! The latest version of this example is available in the [examples] folder in the repository.
|
||||||
//!
|
//!
|
||||||
|
@ -24,7 +24,10 @@ use crossterm::{
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Bar, BarChart, BarGroup, Block, Borders, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
struct Company<'a> {
|
struct Company<'a> {
|
||||||
revenue: [u64; 4],
|
revenue: [u64; 4],
|
||||||
|
@ -41,7 +44,7 @@ struct App<'a> {
|
||||||
const TOTAL_REVENUE: &str = "Total Revenue";
|
const TOTAL_REVENUE: &str = "Total Revenue";
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl<'a> App<'a> {
|
||||||
fn new() -> App<'a> {
|
fn new() -> Self {
|
||||||
App {
|
App {
|
||||||
data: vec![
|
data: vec![
|
||||||
("B1", 9),
|
("B1", 9),
|
||||||
|
@ -137,7 +140,7 @@ fn run_app<B: Backend>(
|
||||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||||
if crossterm::event::poll(timeout)? {
|
if crossterm::event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if let KeyCode::Char('q') = key.code {
|
if key.code == KeyCode::Char('q') {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,6 +170,7 @@ fn ui(frame: &mut Frame, app: &App) {
|
||||||
draw_horizontal_bars(frame, app, right);
|
draw_horizontal_bars(frame, app, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn create_groups<'a>(app: &'a App, combine_values_and_labels: bool) -> Vec<BarGroup<'a>> {
|
fn create_groups<'a>(app: &'a App, combine_values_and_labels: bool) -> Vec<BarGroup<'a>> {
|
||||||
app.months
|
app.months
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -206,7 +210,10 @@ fn create_groups<'a>(app: &'a App, combine_values_and_labels: bool) -> Vec<BarGr
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
|
fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
|
||||||
|
const LEGEND_HEIGHT: u16 = 6;
|
||||||
|
|
||||||
let groups = create_groups(app, false);
|
let groups = create_groups(app, false);
|
||||||
|
|
||||||
let mut barchart = BarChart::default()
|
let mut barchart = BarChart::default()
|
||||||
|
@ -215,12 +222,11 @@ fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
|
||||||
.group_gap(3);
|
.group_gap(3);
|
||||||
|
|
||||||
for group in groups {
|
for group in groups {
|
||||||
barchart = barchart.data(group)
|
barchart = barchart.data(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
f.render_widget(barchart, area);
|
f.render_widget(barchart, area);
|
||||||
|
|
||||||
const LEGEND_HEIGHT: u16 = 6;
|
|
||||||
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
||||||
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
|
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
|
||||||
let legend_area = Rect {
|
let legend_area = Rect {
|
||||||
|
@ -233,7 +239,10 @@ fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn draw_horizontal_bars(f: &mut Frame, app: &App, area: Rect) {
|
fn draw_horizontal_bars(f: &mut Frame, app: &App, area: Rect) {
|
||||||
|
const LEGEND_HEIGHT: u16 = 6;
|
||||||
|
|
||||||
let groups = create_groups(app, true);
|
let groups = create_groups(app, true);
|
||||||
|
|
||||||
let mut barchart = BarChart::default()
|
let mut barchart = BarChart::default()
|
||||||
|
@ -244,12 +253,11 @@ fn draw_horizontal_bars(f: &mut Frame, app: &App, area: Rect) {
|
||||||
.direction(Direction::Horizontal);
|
.direction(Direction::Horizontal);
|
||||||
|
|
||||||
for group in groups {
|
for group in groups {
|
||||||
barchart = barchart.data(group)
|
barchart = barchart.data(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
f.render_widget(barchart, area);
|
f.render_widget(barchart, area);
|
||||||
|
|
||||||
const LEGEND_HEIGHT: u16 = 6;
|
|
||||||
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
|
||||||
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
|
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
|
||||||
let legend_area = Rect {
|
let legend_area = Rect {
|
||||||
|
|
|
@ -77,7 +77,7 @@ fn run(terminal: &mut Terminal) -> Result<()> {
|
||||||
fn handle_events() -> Result<ControlFlow<()>> {
|
fn handle_events() -> Result<ControlFlow<()>> {
|
||||||
if event::poll(Duration::from_millis(100))? {
|
if event::poll(Duration::from_millis(100))? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if let KeyCode::Char('q') = key.code {
|
if key.code == KeyCode::Char('q') {
|
||||||
return Ok(ControlFlow::Break(()));
|
return Ok(ControlFlow::Break(()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ fn placeholder_paragraph() -> Paragraph<'static> {
|
||||||
fn render_borders(paragraph: &Paragraph, border: Borders, frame: &mut Frame, area: Rect) {
|
fn render_borders(paragraph: &Paragraph, border: Borders, frame: &mut Frame, area: Rect) {
|
||||||
let block = Block::new()
|
let block = Block::new()
|
||||||
.borders(border)
|
.borders(border)
|
||||||
.title(format!("Borders::{border:#?}", border = border));
|
.title(format!("Borders::{border:#?}"));
|
||||||
frame.render_widget(paragraph.clone().block(block), area);
|
frame.render_widget(paragraph.clone().block(block), area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::wildcard_imports)]
|
||||||
|
|
||||||
use std::{error::Error, io};
|
use std::{error::Error, io};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
@ -166,15 +168,13 @@ fn make_dates(current_year: i32) -> CalendarEventStore {
|
||||||
mod cals {
|
mod cals {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub(super) fn get_cal<'a, DS: DateStyler>(m: Month, y: i32, es: DS) -> Monthly<'a, DS> {
|
pub fn get_cal<'a, DS: DateStyler>(m: Month, y: i32, es: DS) -> Monthly<'a, DS> {
|
||||||
use Month::*;
|
|
||||||
match m {
|
match m {
|
||||||
May => example1(m, y, es),
|
Month::May => example1(m, y, es),
|
||||||
June => example2(m, y, es),
|
Month::June => example2(m, y, es),
|
||||||
July => example3(m, y, es),
|
Month::July | Month::December => example3(m, y, es),
|
||||||
December => example3(m, y, es),
|
Month::February => example4(m, y, es),
|
||||||
February => example4(m, y, es),
|
Month::November => example5(m, y, es),
|
||||||
November => example5(m, y, es),
|
|
||||||
_ => default(m, y, es),
|
_ => default(m, y, es),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::wildcard_imports)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, stdout, Stdout},
|
io::{self, stdout, Stdout},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
|
@ -44,8 +46,8 @@ struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn new() -> App {
|
fn new() -> Self {
|
||||||
App {
|
Self {
|
||||||
x: 0.0,
|
x: 0.0,
|
||||||
y: 0.0,
|
y: 0.0,
|
||||||
ball: Circle {
|
ball: Circle {
|
||||||
|
@ -64,7 +66,7 @@ impl App {
|
||||||
|
|
||||||
pub fn run() -> io::Result<()> {
|
pub fn run() -> io::Result<()> {
|
||||||
let mut terminal = init_terminal()?;
|
let mut terminal = init_terminal()?;
|
||||||
let mut app = App::new();
|
let mut app = Self::new();
|
||||||
let mut last_tick = Instant::now();
|
let mut last_tick = Instant::now();
|
||||||
let tick_rate = Duration::from_millis(16);
|
let tick_rate = Duration::from_millis(16);
|
||||||
loop {
|
loop {
|
||||||
|
@ -106,13 +108,13 @@ impl App {
|
||||||
// bounce the ball by flipping the velocity vector
|
// bounce the ball by flipping the velocity vector
|
||||||
let ball = &self.ball;
|
let ball = &self.ball;
|
||||||
let playground = self.playground;
|
let playground = self.playground;
|
||||||
if ball.x - ball.radius < playground.left() as f64
|
if ball.x - ball.radius < f64::from(playground.left())
|
||||||
|| ball.x + ball.radius > playground.right() as f64
|
|| ball.x + ball.radius > f64::from(playground.right())
|
||||||
{
|
{
|
||||||
self.vx = -self.vx;
|
self.vx = -self.vx;
|
||||||
}
|
}
|
||||||
if ball.y - ball.radius < playground.top() as f64
|
if ball.y - ball.radius < f64::from(playground.top())
|
||||||
|| ball.y + ball.radius > playground.bottom() as f64
|
|| ball.y + ball.radius > f64::from(playground.bottom())
|
||||||
{
|
{
|
||||||
self.vy = -self.vy;
|
self.vy = -self.vy;
|
||||||
}
|
}
|
||||||
|
@ -160,8 +162,10 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn boxes_canvas(&self, area: Rect) -> impl Widget {
|
fn boxes_canvas(&self, area: Rect) -> impl Widget {
|
||||||
let (left, right, bottom, top) =
|
let left = 0.0;
|
||||||
(0.0, area.width as f64, 0.0, area.height as f64 * 2.0 - 4.0);
|
let right = f64::from(area.width);
|
||||||
|
let bottom = 0.0;
|
||||||
|
let top = f64::from(area.height).mul_add(2.0, -4.0);
|
||||||
Canvas::default()
|
Canvas::default()
|
||||||
.block(Block::default().borders(Borders::ALL).title("Rects"))
|
.block(Block::default().borders(Borders::ALL).title("Rects"))
|
||||||
.marker(self.marker)
|
.marker(self.marker)
|
||||||
|
@ -170,26 +174,26 @@ impl App {
|
||||||
.paint(|ctx| {
|
.paint(|ctx| {
|
||||||
for i in 0..=11 {
|
for i in 0..=11 {
|
||||||
ctx.draw(&Rectangle {
|
ctx.draw(&Rectangle {
|
||||||
x: (i * i + 3 * i) as f64 / 2.0 + 2.0,
|
x: f64::from(i * i + 3 * i) / 2.0 + 2.0,
|
||||||
y: 2.0,
|
y: 2.0,
|
||||||
width: i as f64,
|
width: f64::from(i),
|
||||||
height: i as f64,
|
height: f64::from(i),
|
||||||
color: Color::Red,
|
color: Color::Red,
|
||||||
});
|
});
|
||||||
ctx.draw(&Rectangle {
|
ctx.draw(&Rectangle {
|
||||||
x: (i * i + 3 * i) as f64 / 2.0 + 2.0,
|
x: f64::from(i * i + 3 * i) / 2.0 + 2.0,
|
||||||
y: 21.0,
|
y: 21.0,
|
||||||
width: i as f64,
|
width: f64::from(i),
|
||||||
height: i as f64,
|
height: f64::from(i),
|
||||||
color: Color::Blue,
|
color: Color::Blue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
if i % 10 != 0 {
|
if i % 10 != 0 {
|
||||||
ctx.print(i as f64 + 1.0, 0.0, format!("{i}", i = i % 10));
|
ctx.print(f64::from(i) + 1.0, 0.0, format!("{i}", i = i % 10));
|
||||||
}
|
}
|
||||||
if i % 2 == 0 && i % 10 != 0 {
|
if i % 2 == 0 && i % 10 != 0 {
|
||||||
ctx.print(0.0, i as f64, format!("{i}", i = i % 10));
|
ctx.print(0.0, f64::from(i), format!("{i}", i = i % 10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,11 +26,11 @@ use crossterm::{
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widgets::{block::Title, *},
|
widgets::{block::Title, Axis, Block, Borders, Chart, Dataset, GraphType, LegendPosition},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SinSignal {
|
struct SinSignal {
|
||||||
x: f64,
|
x: f64,
|
||||||
interval: f64,
|
interval: f64,
|
||||||
period: f64,
|
period: f64,
|
||||||
|
@ -38,8 +38,8 @@ pub struct SinSignal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SinSignal {
|
impl SinSignal {
|
||||||
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
|
const fn new(interval: f64, period: f64, scale: f64) -> Self {
|
||||||
SinSignal {
|
Self {
|
||||||
x: 0.0,
|
x: 0.0,
|
||||||
interval,
|
interval,
|
||||||
period,
|
period,
|
||||||
|
@ -66,12 +66,12 @@ struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn new() -> App {
|
fn new() -> Self {
|
||||||
let mut signal1 = SinSignal::new(0.2, 3.0, 18.0);
|
let mut signal1 = SinSignal::new(0.2, 3.0, 18.0);
|
||||||
let mut signal2 = SinSignal::new(0.1, 2.0, 10.0);
|
let mut signal2 = SinSignal::new(0.1, 2.0, 10.0);
|
||||||
let data1 = signal1.by_ref().take(200).collect::<Vec<(f64, f64)>>();
|
let data1 = signal1.by_ref().take(200).collect::<Vec<(f64, f64)>>();
|
||||||
let data2 = signal2.by_ref().take(200).collect::<Vec<(f64, f64)>>();
|
let data2 = signal2.by_ref().take(200).collect::<Vec<(f64, f64)>>();
|
||||||
App {
|
Self {
|
||||||
signal1,
|
signal1,
|
||||||
data1,
|
data1,
|
||||||
signal2,
|
signal2,
|
||||||
|
@ -133,7 +133,7 @@ fn run_app<B: Backend>(
|
||||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||||
if crossterm::event::poll(timeout)? {
|
if crossterm::event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if let KeyCode::Char('q') = key.code {
|
if key.code == KeyCode::Char('q') {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,7 +242,7 @@ fn render_line_chart(f: &mut Frame, area: Rect) {
|
||||||
.legend_position(Some(LegendPosition::TopLeft))
|
.legend_position(Some(LegendPosition::TopLeft))
|
||||||
.hidden_legend_constraints((Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)));
|
.hidden_legend_constraints((Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)));
|
||||||
|
|
||||||
f.render_widget(chart, area)
|
f.render_widget(chart, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_scatter(f: &mut Frame, area: Rect) {
|
fn render_scatter(f: &mut Frame, area: Rect) {
|
||||||
|
@ -310,7 +310,7 @@ const HEAVY_PAYLOAD_DATA: [(f64, f64); 9] = [
|
||||||
const MEDIUM_PAYLOAD_DATA: [(f64, f64); 29] = [
|
const MEDIUM_PAYLOAD_DATA: [(f64, f64); 29] = [
|
||||||
(1963., 29500.),
|
(1963., 29500.),
|
||||||
(1964., 30600.),
|
(1964., 30600.),
|
||||||
(1965., 177900.),
|
(1965., 177_900.),
|
||||||
(1965., 21000.),
|
(1965., 21000.),
|
||||||
(1966., 17900.),
|
(1966., 17900.),
|
||||||
(1966., 8400.),
|
(1966., 8400.),
|
||||||
|
@ -340,7 +340,7 @@ const MEDIUM_PAYLOAD_DATA: [(f64, f64); 29] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const SMALL_PAYLOAD_DATA: [(f64, f64); 23] = [
|
const SMALL_PAYLOAD_DATA: [(f64, f64); 23] = [
|
||||||
(1961., 118500.),
|
(1961., 118_500.),
|
||||||
(1962., 14900.),
|
(1962., 14900.),
|
||||||
(1975., 21400.),
|
(1975., 21400.),
|
||||||
(1980., 32800.),
|
(1980., 32800.),
|
||||||
|
|
|
@ -13,8 +13,9 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
/// This example shows all the colors supported by ratatui. It will render a grid of foreground
|
// This example shows all the colors supported by ratatui. It will render a grid of foreground
|
||||||
/// and background colors with their names and indexes.
|
// and background colors with their names and indexes.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{self, Stdout},
|
io::{self, Stdout},
|
||||||
|
@ -28,7 +29,10 @@ use crossterm::{
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Box<dyn Error>>;
|
type Result<T> = result::Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
@ -48,7 +52,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||||
|
|
||||||
if event::poll(Duration::from_millis(250))? {
|
if event::poll(Duration::from_millis(250))? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if let KeyCode::Char('q') = key.code {
|
if key.code == KeyCode::Char('q') {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! # [Ratatui] Colors_RGB example
|
//! # [Ratatui] `Colors_RGB` example
|
||||||
//!
|
//!
|
||||||
//! The latest version of this example is available in the [examples] folder in the repository.
|
//! The latest version of this example is available in the [examples] folder in the repository.
|
||||||
//!
|
//!
|
||||||
|
@ -13,18 +13,19 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
/// This example shows the full range of RGB colors that can be displayed in the terminal.
|
// This example shows the full range of RGB colors that can be displayed in the terminal.
|
||||||
///
|
//
|
||||||
/// Requires a terminal that supports 24-bit color (true color) and unicode.
|
// Requires a terminal that supports 24-bit color (true color) and unicode.
|
||||||
///
|
//
|
||||||
/// This example also demonstrates how implementing the Widget trait on a mutable reference
|
// This example also demonstrates how implementing the Widget trait on a mutable reference
|
||||||
/// allows the widget to update its state while it is being rendered. This allows the fps
|
// allows the widget to update its state while it is being rendered. This allows the fps
|
||||||
/// widget to update the fps calculation and the colors widget to update a cached version of
|
// widget to update the fps calculation and the colors widget to update a cached version of
|
||||||
/// the colors to render instead of recalculating them every frame.
|
// the colors to render instead of recalculating them every frame.
|
||||||
///
|
//
|
||||||
/// This is an alternative to using the StatefulWidget trait and a separate state struct. It is
|
// This is an alternative to using the `StatefulWidget` trait and a separate state struct. It
|
||||||
/// useful when the state is only used by the widget and doesn't need to be shared with other
|
// is useful when the state is only used by the widget and doesn't need to be shared with
|
||||||
/// widgets.
|
// other widgets.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::stdout,
|
io::stdout,
|
||||||
panic,
|
panic,
|
||||||
|
@ -110,7 +111,7 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_running(&self) -> bool {
|
const fn is_running(&self) -> bool {
|
||||||
matches!(self.state, AppState::Running)
|
matches!(self.state, AppState::Running)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +141,7 @@ impl App {
|
||||||
/// to update the colors to render.
|
/// to update the colors to render.
|
||||||
impl Widget for &mut App {
|
impl Widget for &mut App {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
#[allow(clippy::enum_glob_use)]
|
||||||
use Constraint::*;
|
use Constraint::*;
|
||||||
let [top, colors] = Layout::vertical([Length(1), Min(0)]).areas(area);
|
let [top, colors] = Layout::vertical([Length(1), Min(0)]).areas(area);
|
||||||
let [title, fps] = Layout::horizontal([Min(0), Length(8)]).areas(top);
|
let [title, fps] = Layout::horizontal([Min(0), Length(8)]).areas(top);
|
||||||
|
@ -151,9 +153,9 @@ impl Widget for &mut App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default impl for FpsWidget
|
/// Default impl for `FpsWidget`
|
||||||
///
|
///
|
||||||
/// Manual impl is required because we need to initialize the last_instant field to the current
|
/// Manual impl is required because we need to initialize the `last_instant` field to the current
|
||||||
/// instant.
|
/// instant.
|
||||||
impl Default for FpsWidget {
|
impl Default for FpsWidget {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -165,7 +167,7 @@ impl Default for FpsWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Widget impl for FpsWidget
|
/// Widget impl for `FpsWidget`
|
||||||
///
|
///
|
||||||
/// This is implemented on a mutable reference so that we can update the frame count and fps
|
/// This is implemented on a mutable reference so that we can update the frame count and fps
|
||||||
/// calculation while rendering.
|
/// calculation while rendering.
|
||||||
|
@ -173,7 +175,7 @@ impl Widget for &mut FpsWidget {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
self.calculate_fps();
|
self.calculate_fps();
|
||||||
if let Some(fps) = self.fps {
|
if let Some(fps) = self.fps {
|
||||||
let text = format!("{:.1} fps", fps);
|
let text = format!("{fps:.1} fps");
|
||||||
Text::from(text).render(area, buf);
|
Text::from(text).render(area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,6 +187,7 @@ impl FpsWidget {
|
||||||
/// This updates the fps once a second, but only if the widget has rendered at least 2 frames
|
/// This updates the fps once a second, but only if the widget has rendered at least 2 frames
|
||||||
/// since the last calculation. This avoids noise in the fps calculation when rendering on slow
|
/// since the last calculation. This avoids noise in the fps calculation when rendering on slow
|
||||||
/// machines that can't render at least 2 frames per second.
|
/// machines that can't render at least 2 frames per second.
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn calculate_fps(&mut self) {
|
fn calculate_fps(&mut self) {
|
||||||
self.frame_count += 1;
|
self.frame_count += 1;
|
||||||
let elapsed = self.last_instant.elapsed();
|
let elapsed = self.last_instant.elapsed();
|
||||||
|
@ -196,7 +199,7 @@ impl FpsWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Widget impl for ColorsWidget
|
/// Widget impl for `ColorsWidget`
|
||||||
///
|
///
|
||||||
/// This is implemented on a mutable reference so that we can update the frame count and store a
|
/// This is implemented on a mutable reference so that we can update the frame count and store a
|
||||||
/// cached version of the colors to render instead of recalculating them every frame.
|
/// cached version of the colors to render instead of recalculating them every frame.
|
||||||
|
@ -226,6 +229,7 @@ impl ColorsWidget {
|
||||||
///
|
///
|
||||||
/// This is called once per frame to setup the colors to render. It caches the colors so that
|
/// This is called once per frame to setup the colors to render. It caches the colors so that
|
||||||
/// they don't need to be recalculated every frame.
|
/// they don't need to be recalculated every frame.
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn setup_colors(&mut self, size: Rect) {
|
fn setup_colors(&mut self, size: Rect) {
|
||||||
let Rect { width, height, .. } = size;
|
let Rect { width, height, .. } = size;
|
||||||
// double the height because each screen row has two rows of half block pixels
|
// double the height because each screen row has two rows of half block pixels
|
||||||
|
@ -253,7 +257,7 @@ impl ColorsWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Install color_eyre panic and error hooks
|
/// Install `color_eyre` panic and error hooks
|
||||||
///
|
///
|
||||||
/// The hooks restore the terminal to a usable state before printing the error message.
|
/// The hooks restore the terminal to a usable state before printing the error message.
|
||||||
fn install_error_hooks() -> Result<()> {
|
fn install_error_hooks() -> Result<()> {
|
||||||
|
@ -266,7 +270,7 @@ fn install_error_hooks() -> Result<()> {
|
||||||
}))?;
|
}))?;
|
||||||
panic::set_hook(Box::new(move |info| {
|
panic::set_hook(Box::new(move |info| {
|
||||||
let _ = restore_terminal();
|
let _ = restore_terminal();
|
||||||
panic(info)
|
panic(info);
|
||||||
}));
|
}));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||||
|
|
||||||
use std::io::{self, stdout};
|
use std::io::{self, stdout};
|
||||||
|
|
||||||
use color_eyre::{config::HookBuilder, Result};
|
use color_eyre::{config::HookBuilder, Result};
|
||||||
|
@ -27,7 +29,7 @@ use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
style::palette::tailwind::*,
|
style::palette::tailwind::*,
|
||||||
symbols::line,
|
symbols::line,
|
||||||
widgets::*,
|
widgets::{Block, Paragraph, Wrap},
|
||||||
};
|
};
|
||||||
use strum::{Display, EnumIter, FromRepr};
|
use strum::{Display, EnumIter, FromRepr};
|
||||||
|
|
||||||
|
@ -67,9 +69,9 @@ enum ConstraintName {
|
||||||
/// └──────────────┘
|
/// └──────────────┘
|
||||||
/// ```
|
/// ```
|
||||||
struct ConstraintBlock {
|
struct ConstraintBlock {
|
||||||
selected: bool,
|
|
||||||
legend: bool,
|
|
||||||
constraint: Constraint,
|
constraint: Constraint,
|
||||||
|
legend: bool,
|
||||||
|
selected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A widget that renders a spacer with a label indicating the width of the spacer. E.g.:
|
/// A widget that renders a spacer with a label indicating the width of the spacer. E.g.:
|
||||||
|
@ -146,32 +148,31 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// select the next block with wrap around
|
|
||||||
fn increment_value(&mut self) {
|
fn increment_value(&mut self) {
|
||||||
if self.constraints.is_empty() {
|
let Some(constraint) = self.constraints.get_mut(self.selected_index) else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
self.constraints[self.selected_index] = match self.constraints[self.selected_index] {
|
match constraint {
|
||||||
Constraint::Length(v) => Constraint::Length(v.saturating_add(1)),
|
Constraint::Length(v)
|
||||||
Constraint::Min(v) => Constraint::Min(v.saturating_add(1)),
|
| Constraint::Min(v)
|
||||||
Constraint::Max(v) => Constraint::Max(v.saturating_add(1)),
|
| Constraint::Max(v)
|
||||||
Constraint::Fill(v) => Constraint::Fill(v.saturating_add(1)),
|
| Constraint::Fill(v)
|
||||||
Constraint::Percentage(v) => Constraint::Percentage(v.saturating_add(1)),
|
| Constraint::Percentage(v) => *v = v.saturating_add(1),
|
||||||
Constraint::Ratio(n, d) => Constraint::Ratio(n, d.saturating_add(1)),
|
Constraint::Ratio(_n, d) => *d = d.saturating_add(1),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrement_value(&mut self) {
|
fn decrement_value(&mut self) {
|
||||||
if self.constraints.is_empty() {
|
let Some(constraint) = self.constraints.get_mut(self.selected_index) else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
self.constraints[self.selected_index] = match self.constraints[self.selected_index] {
|
match constraint {
|
||||||
Constraint::Length(v) => Constraint::Length(v.saturating_sub(1)),
|
Constraint::Length(v)
|
||||||
Constraint::Min(v) => Constraint::Min(v.saturating_sub(1)),
|
| Constraint::Min(v)
|
||||||
Constraint::Max(v) => Constraint::Max(v.saturating_sub(1)),
|
| Constraint::Max(v)
|
||||||
Constraint::Fill(v) => Constraint::Fill(v.saturating_sub(1)),
|
| Constraint::Fill(v)
|
||||||
Constraint::Percentage(v) => Constraint::Percentage(v.saturating_sub(1)),
|
| Constraint::Percentage(v) => *v = v.saturating_sub(1),
|
||||||
Constraint::Ratio(n, d) => Constraint::Ratio(n, d.saturating_sub(1)),
|
Constraint::Ratio(_n, d) => *d = d.saturating_sub(1),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +223,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit(&mut self) {
|
fn exit(&mut self) {
|
||||||
self.mode = AppMode::Quit
|
self.mode = AppMode::Quit;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swap_constraint(&mut self, name: ConstraintName) {
|
fn swap_constraint(&mut self, name: ConstraintName) {
|
||||||
|
@ -235,7 +236,7 @@ impl App {
|
||||||
ConstraintName::Min => Min(self.value),
|
ConstraintName::Min => Min(self.value),
|
||||||
ConstraintName::Max => Max(self.value),
|
ConstraintName::Max => Max(self.value),
|
||||||
ConstraintName::Fill => Fill(self.value),
|
ConstraintName::Fill => Fill(self.value),
|
||||||
ConstraintName::Ratio => Ratio(1, self.value as u32 / 4), // for balance
|
ConstraintName::Ratio => Ratio(1, u32::from(self.value) / 4), // for balance
|
||||||
};
|
};
|
||||||
self.constraints[self.selected_index] = constraint;
|
self.constraints[self.selected_index] = constraint;
|
||||||
}
|
}
|
||||||
|
@ -243,14 +244,13 @@ impl App {
|
||||||
|
|
||||||
impl From<Constraint> for ConstraintName {
|
impl From<Constraint> for ConstraintName {
|
||||||
fn from(constraint: Constraint) -> Self {
|
fn from(constraint: Constraint) -> Self {
|
||||||
use Constraint::*;
|
|
||||||
match constraint {
|
match constraint {
|
||||||
Length(_) => ConstraintName::Length,
|
Length(_) => Self::Length,
|
||||||
Percentage(_) => ConstraintName::Percentage,
|
Percentage(_) => Self::Percentage,
|
||||||
Ratio(_, _) => ConstraintName::Ratio,
|
Ratio(_, _) => Self::Ratio,
|
||||||
Min(_) => ConstraintName::Min,
|
Min(_) => Self::Min,
|
||||||
Max(_) => ConstraintName::Max,
|
Max(_) => Self::Max,
|
||||||
Fill(_) => ConstraintName::Fill,
|
Fill(_) => Self::Fill,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,9 +267,9 @@ impl Widget for &App {
|
||||||
])
|
])
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
self.header().render(header_area, buf);
|
App::header().render(header_area, buf);
|
||||||
self.instructions().render(instructions_area, buf);
|
App::instructions().render(instructions_area, buf);
|
||||||
self.swap_legend().render(swap_legend_area, buf);
|
App::swap_legend().render(swap_legend_area, buf);
|
||||||
self.render_layout_blocks(blocks_area, buf);
|
self.render_layout_blocks(blocks_area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,12 +280,12 @@ impl App {
|
||||||
const TEXT_COLOR: Color = SLATE.c400;
|
const TEXT_COLOR: Color = SLATE.c400;
|
||||||
const AXIS_COLOR: Color = SLATE.c500;
|
const AXIS_COLOR: Color = SLATE.c500;
|
||||||
|
|
||||||
fn header(&self) -> impl Widget {
|
fn header() -> impl Widget {
|
||||||
let text = "Constraint Explorer";
|
let text = "Constraint Explorer";
|
||||||
text.bold().fg(Self::HEADER_COLOR).to_centered_line()
|
text.bold().fg(Self::HEADER_COLOR).to_centered_line()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instructions(&self) -> impl Widget {
|
fn instructions() -> impl Widget {
|
||||||
let text = "◄ ►: select, ▲ ▼: edit, 1-6: swap, a: add, x: delete, q: quit, + -: spacing";
|
let text = "◄ ►: select, ▲ ▼: edit, 1-6: swap, a: add, x: delete, q: quit, + -: spacing";
|
||||||
Paragraph::new(text)
|
Paragraph::new(text)
|
||||||
.fg(Self::TEXT_COLOR)
|
.fg(Self::TEXT_COLOR)
|
||||||
|
@ -293,7 +293,7 @@ impl App {
|
||||||
.wrap(Wrap { trim: false })
|
.wrap(Wrap { trim: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swap_legend(&self) -> impl Widget {
|
fn swap_legend() -> impl Widget {
|
||||||
#[allow(unstable_name_collisions)]
|
#[allow(unstable_name_collisions)]
|
||||||
Paragraph::new(
|
Paragraph::new(
|
||||||
Line::from(
|
Line::from(
|
||||||
|
@ -327,7 +327,7 @@ impl App {
|
||||||
let label = if self.spacing != 0 {
|
let label = if self.spacing != 0 {
|
||||||
format!("{} px (gap: {} px)", width, self.spacing)
|
format!("{} px (gap: {} px)", width, self.spacing)
|
||||||
} else {
|
} else {
|
||||||
format!("{} px", width)
|
format!("{width} px")
|
||||||
};
|
};
|
||||||
let bar_width = width.saturating_sub(2) as usize; // we want to `<` and `>` at the ends
|
let bar_width = width.saturating_sub(2) as usize; // we want to `<` and `>` at the ends
|
||||||
let width_bar = format!("<{label:-^bar_width$}>");
|
let width_bar = format!("<{label:-^bar_width$}>");
|
||||||
|
@ -348,7 +348,7 @@ impl App {
|
||||||
self.render_layout_block(Flex::Center, center, buf);
|
self.render_layout_block(Flex::Center, center, buf);
|
||||||
self.render_layout_block(Flex::End, end, buf);
|
self.render_layout_block(Flex::End, end, buf);
|
||||||
self.render_layout_block(Flex::SpaceAround, space_around, buf);
|
self.render_layout_block(Flex::SpaceAround, space_around, buf);
|
||||||
self.render_layout_block(Flex::SpaceBetween, space_between, buf)
|
self.render_layout_block(Flex::SpaceBetween, space_between, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_user_constraints_legend(&self, area: Rect, buf: &mut Buffer) {
|
fn render_user_constraints_legend(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
@ -371,7 +371,7 @@ impl App {
|
||||||
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
|
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
|
||||||
|
|
||||||
if label_area.height > 0 {
|
if label_area.height > 0 {
|
||||||
format!("Flex::{:?}", flex).bold().render(label_area, buf);
|
format!("Flex::{flex:?}").bold().render(label_area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.axis(area.width).render(axis_area, buf);
|
self.axis(area.width).render(axis_area, buf);
|
||||||
|
@ -405,17 +405,17 @@ impl Widget for ConstraintBlock {
|
||||||
impl ConstraintBlock {
|
impl ConstraintBlock {
|
||||||
const TEXT_COLOR: Color = SLATE.c200;
|
const TEXT_COLOR: Color = SLATE.c200;
|
||||||
|
|
||||||
fn new(constraint: Constraint, selected: bool, legend: bool) -> Self {
|
const fn new(constraint: Constraint, selected: bool, legend: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
constraint,
|
constraint,
|
||||||
selected,
|
|
||||||
legend,
|
legend,
|
||||||
|
selected,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn label(&self, width: u16) -> String {
|
fn label(&self, width: u16) -> String {
|
||||||
let long_width = format!("{} px", width);
|
let long_width = format!("{width} px");
|
||||||
let short_width = format!("{}", width);
|
let short_width = format!("{width}");
|
||||||
// border takes up 2 columns
|
// border takes up 2 columns
|
||||||
let available_space = width.saturating_sub(2) as usize;
|
let available_space = width.saturating_sub(2) as usize;
|
||||||
let width_label = if long_width.len() < available_space {
|
let width_label = if long_width.len() < available_space {
|
||||||
|
@ -423,7 +423,7 @@ impl ConstraintBlock {
|
||||||
} else if short_width.len() < available_space {
|
} else if short_width.len() < available_space {
|
||||||
short_width
|
short_width
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
};
|
};
|
||||||
format!("{}\n{}", self.constraint, width_label)
|
format!("{}\n{}", self.constraint, width_label)
|
||||||
}
|
}
|
||||||
|
@ -499,9 +499,9 @@ impl Widget for SpacerBlock {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
match area.height {
|
match area.height {
|
||||||
1 => (),
|
1 => (),
|
||||||
2 => self.render_2px(area, buf),
|
2 => Self::render_2px(area, buf),
|
||||||
3 => self.render_3px(area, buf),
|
3 => Self::render_3px(area, buf),
|
||||||
_ => self.render_4px(area, buf),
|
_ => Self::render_4px(area, buf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -541,7 +541,7 @@ impl SpacerBlock {
|
||||||
/// A label that says "Spacer" if there is enough space
|
/// A label that says "Spacer" if there is enough space
|
||||||
fn spacer_label(width: u16) -> impl Widget {
|
fn spacer_label(width: u16) -> impl Widget {
|
||||||
let label = if width >= 6 { "Spacer" } else { "" };
|
let label = if width >= 6 { "Spacer" } else { "" };
|
||||||
label.fg(SpacerBlock::TEXT_COLOR).to_centered_line()
|
label.fg(Self::TEXT_COLOR).to_centered_line()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A label that says "8 px" if there is enough space
|
/// A label that says "8 px" if there is enough space
|
||||||
|
@ -553,12 +553,12 @@ impl SpacerBlock {
|
||||||
} else if short_label.len() < width as usize {
|
} else if short_label.len() < width as usize {
|
||||||
short_label
|
short_label
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
};
|
};
|
||||||
Line::styled(label, Self::TEXT_COLOR).centered()
|
Line::styled(label, Self::TEXT_COLOR).centered()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_2px(&self, area: Rect, buf: &mut Buffer) {
|
fn render_2px(area: Rect, buf: &mut Buffer) {
|
||||||
if area.width > 1 {
|
if area.width > 1 {
|
||||||
Self::block().render(area, buf);
|
Self::block().render(area, buf);
|
||||||
} else {
|
} else {
|
||||||
|
@ -566,7 +566,7 @@ impl SpacerBlock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_3px(&self, area: Rect, buf: &mut Buffer) {
|
fn render_3px(area: Rect, buf: &mut Buffer) {
|
||||||
if area.width > 1 {
|
if area.width > 1 {
|
||||||
Self::block().render(area, buf);
|
Self::block().render(area, buf);
|
||||||
} else {
|
} else {
|
||||||
|
@ -577,7 +577,7 @@ impl SpacerBlock {
|
||||||
Self::spacer_label(area.width).render(row, buf);
|
Self::spacer_label(area.width).render(row, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_4px(&self, area: Rect, buf: &mut Buffer) {
|
fn render_4px(area: Rect, buf: &mut Buffer) {
|
||||||
if area.width > 1 {
|
if area.width > 1 {
|
||||||
Self::block().render(area, buf);
|
Self::block().render(area, buf);
|
||||||
} else {
|
} else {
|
||||||
|
@ -593,7 +593,7 @@ impl SpacerBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConstraintName {
|
impl ConstraintName {
|
||||||
fn color(&self) -> Color {
|
const fn color(self) -> Color {
|
||||||
match self {
|
match self {
|
||||||
Self::Length => SLATE.c700,
|
Self::Length => SLATE.c700,
|
||||||
Self::Percentage => SLATE.c800,
|
Self::Percentage => SLATE.c800,
|
||||||
|
@ -604,7 +604,7 @@ impl ConstraintName {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lighter_color(&self) -> Color {
|
const fn lighter_color(self) -> Color {
|
||||||
match self {
|
match self {
|
||||||
Self::Length => STONE.c500,
|
Self::Length => STONE.c500,
|
||||||
Self::Percentage => STONE.c600,
|
Self::Percentage => STONE.c600,
|
||||||
|
@ -626,7 +626,7 @@ fn init_error_hooks() -> Result<()> {
|
||||||
}))?;
|
}))?;
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
let _ = restore_terminal();
|
let _ = restore_terminal();
|
||||||
panic(info)
|
panic(info);
|
||||||
}));
|
}));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||||
|
|
||||||
use std::io::{self, stdout};
|
use std::io::{self, stdout};
|
||||||
|
|
||||||
use color_eyre::{config::HookBuilder, Result};
|
use color_eyre::{config::HookBuilder, Result};
|
||||||
|
@ -95,7 +97,7 @@ impl App {
|
||||||
self.max_scroll_offset = (self.selected_tab.get_example_count() - 1) * EXAMPLE_HEIGHT;
|
self.max_scroll_offset = (self.selected_tab.get_example_count() - 1) * EXAMPLE_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_running(&self) -> bool {
|
fn is_running(self) -> bool {
|
||||||
self.state == AppState::Running
|
self.state == AppState::Running
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,14 +140,14 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self) {
|
fn up(&mut self) {
|
||||||
self.scroll_offset = self.scroll_offset.saturating_sub(1)
|
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn down(&mut self) {
|
fn down(&mut self) {
|
||||||
self.scroll_offset = self
|
self.scroll_offset = self
|
||||||
.scroll_offset
|
.scroll_offset
|
||||||
.saturating_add(1)
|
.saturating_add(1)
|
||||||
.min(self.max_scroll_offset)
|
.min(self.max_scroll_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top(&mut self) {
|
fn top(&mut self) {
|
||||||
|
@ -159,17 +161,16 @@ impl App {
|
||||||
|
|
||||||
impl Widget for App {
|
impl Widget for App {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let [tabs, axis, demo] =
|
let [tabs, axis, demo] = Layout::vertical([Length(3), Length(3), Fill(0)]).areas(area);
|
||||||
Layout::vertical([Constraint::Length(3), Constraint::Length(3), Fill(0)]).areas(area);
|
|
||||||
|
|
||||||
self.render_tabs(tabs, buf);
|
self.render_tabs(tabs, buf);
|
||||||
self.render_axis(axis, buf);
|
Self::render_axis(axis, buf);
|
||||||
self.render_demo(demo, buf);
|
self.render_demo(demo, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn render_tabs(&self, area: Rect, buf: &mut Buffer) {
|
fn render_tabs(self, area: Rect, buf: &mut Buffer) {
|
||||||
let titles = SelectedTab::iter().map(SelectedTab::to_tab_title);
|
let titles = SelectedTab::iter().map(SelectedTab::to_tab_title);
|
||||||
let block = Block::new()
|
let block = Block::new()
|
||||||
.title("Constraints ".bold())
|
.title("Constraints ".bold())
|
||||||
|
@ -183,10 +184,10 @@ impl App {
|
||||||
.render(area, buf);
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_axis(&self, area: Rect, buf: &mut Buffer) {
|
fn render_axis(area: Rect, buf: &mut Buffer) {
|
||||||
let width = area.width as usize;
|
let width = area.width as usize;
|
||||||
// a bar like `<----- 80 px ----->`
|
// a bar like `<----- 80 px ----->`
|
||||||
let width_label = format!("{} px", width);
|
let width_label = format!("{width} px");
|
||||||
let width_bar = format!(
|
let width_bar = format!(
|
||||||
"<{width_label:-^width$}>",
|
"<{width_label:-^width$}>",
|
||||||
width = width - width_label.len() / 2
|
width = width - width_label.len() / 2
|
||||||
|
@ -206,7 +207,8 @@ impl App {
|
||||||
///
|
///
|
||||||
/// This function renders the demo content into a separate buffer and then splices the buffer
|
/// This function renders the demo content into a separate buffer and then splices the buffer
|
||||||
/// into the main buffer. This is done to make it possible to handle scrolling easily.
|
/// into the main buffer. This is done to make it possible to handle scrolling easily.
|
||||||
fn render_demo(&self, area: Rect, buf: &mut Buffer) {
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
fn render_demo(self, area: Rect, buf: &mut Buffer) {
|
||||||
// render demo content into a separate buffer so all examples fit we add an extra
|
// render demo content into a separate buffer so all examples fit we add an extra
|
||||||
// area.height to make sure the last example is fully visible even when the scroll offset is
|
// area.height to make sure the last example is fully visible even when the scroll offset is
|
||||||
// at the max
|
// at the max
|
||||||
|
@ -246,41 +248,40 @@ impl App {
|
||||||
|
|
||||||
impl SelectedTab {
|
impl SelectedTab {
|
||||||
/// Get the previous tab, if there is no previous tab return the current tab.
|
/// Get the previous tab, if there is no previous tab return the current tab.
|
||||||
fn previous(&self) -> Self {
|
fn previous(self) -> Self {
|
||||||
let current_index: usize = *self as usize;
|
let current_index: usize = self as usize;
|
||||||
let previous_index = current_index.saturating_sub(1);
|
let previous_index = current_index.saturating_sub(1);
|
||||||
Self::from_repr(previous_index).unwrap_or(*self)
|
Self::from_repr(previous_index).unwrap_or(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the next tab, if there is no next tab return the current tab.
|
/// Get the next tab, if there is no next tab return the current tab.
|
||||||
fn next(&self) -> Self {
|
fn next(self) -> Self {
|
||||||
let current_index = *self as usize;
|
let current_index = self as usize;
|
||||||
let next_index = current_index.saturating_add(1);
|
let next_index = current_index.saturating_add(1);
|
||||||
Self::from_repr(next_index).unwrap_or(*self)
|
Self::from_repr(next_index).unwrap_or(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_example_count(&self) -> u16 {
|
const fn get_example_count(self) -> u16 {
|
||||||
use SelectedTab::*;
|
#[allow(clippy::match_same_arms)]
|
||||||
match self {
|
match self {
|
||||||
Length => 4,
|
Self::Length => 4,
|
||||||
Percentage => 5,
|
Self::Percentage => 5,
|
||||||
Ratio => 4,
|
Self::Ratio => 4,
|
||||||
Fill => 2,
|
Self::Fill => 2,
|
||||||
Min => 5,
|
Self::Min => 5,
|
||||||
Max => 5,
|
Self::Max => 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_tab_title(value: SelectedTab) -> Line<'static> {
|
fn to_tab_title(value: Self) -> Line<'static> {
|
||||||
use SelectedTab::*;
|
|
||||||
let text = format!(" {value} ");
|
let text = format!(" {value} ");
|
||||||
let color = match value {
|
let color = match value {
|
||||||
Length => LENGTH_COLOR,
|
Self::Length => LENGTH_COLOR,
|
||||||
Percentage => PERCENTAGE_COLOR,
|
Self::Percentage => PERCENTAGE_COLOR,
|
||||||
Ratio => RATIO_COLOR,
|
Self::Ratio => RATIO_COLOR,
|
||||||
Fill => FILL_COLOR,
|
Self::Fill => FILL_COLOR,
|
||||||
Min => MIN_COLOR,
|
Self::Min => MIN_COLOR,
|
||||||
Max => MAX_COLOR,
|
Self::Max => MAX_COLOR,
|
||||||
};
|
};
|
||||||
text.fg(tailwind::SLATE.c200).bg(color).into()
|
text.fg(tailwind::SLATE.c200).bg(color).into()
|
||||||
}
|
}
|
||||||
|
@ -289,18 +290,18 @@ impl SelectedTab {
|
||||||
impl Widget for SelectedTab {
|
impl Widget for SelectedTab {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
match self {
|
match self {
|
||||||
SelectedTab::Length => self.render_length_example(area, buf),
|
Self::Length => Self::render_length_example(area, buf),
|
||||||
SelectedTab::Percentage => self.render_percentage_example(area, buf),
|
Self::Percentage => Self::render_percentage_example(area, buf),
|
||||||
SelectedTab::Ratio => self.render_ratio_example(area, buf),
|
Self::Ratio => Self::render_ratio_example(area, buf),
|
||||||
SelectedTab::Fill => self.render_fill_example(area, buf),
|
Self::Fill => Self::render_fill_example(area, buf),
|
||||||
SelectedTab::Min => self.render_min_example(area, buf),
|
Self::Min => Self::render_min_example(area, buf),
|
||||||
SelectedTab::Max => self.render_max_example(area, buf),
|
Self::Max => Self::render_max_example(area, buf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectedTab {
|
impl SelectedTab {
|
||||||
fn render_length_example(&self, area: Rect, buf: &mut Buffer) {
|
fn render_length_example(area: Rect, buf: &mut Buffer) {
|
||||||
let [example1, example2, example3, _] =
|
let [example1, example2, example3, _] =
|
||||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 4]).areas(area);
|
Layout::vertical([Length(EXAMPLE_HEIGHT); 4]).areas(area);
|
||||||
|
|
||||||
|
@ -309,7 +310,7 @@ impl SelectedTab {
|
||||||
Example::new(&[Length(20), Max(20)]).render(example3, buf);
|
Example::new(&[Length(20), Max(20)]).render(example3, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_percentage_example(&self, area: Rect, buf: &mut Buffer) {
|
fn render_percentage_example(area: Rect, buf: &mut Buffer) {
|
||||||
let [example1, example2, example3, example4, example5, _] =
|
let [example1, example2, example3, example4, example5, _] =
|
||||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
||||||
|
|
||||||
|
@ -320,7 +321,7 @@ impl SelectedTab {
|
||||||
Example::new(&[Percentage(0), Fill(0)]).render(example5, buf);
|
Example::new(&[Percentage(0), Fill(0)]).render(example5, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_ratio_example(&self, area: Rect, buf: &mut Buffer) {
|
fn render_ratio_example(area: Rect, buf: &mut Buffer) {
|
||||||
let [example1, example2, example3, example4, _] =
|
let [example1, example2, example3, example4, _] =
|
||||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 5]).areas(area);
|
Layout::vertical([Length(EXAMPLE_HEIGHT); 5]).areas(area);
|
||||||
|
|
||||||
|
@ -330,14 +331,14 @@ impl SelectedTab {
|
||||||
Example::new(&[Ratio(1, 2), Percentage(25), Length(10)]).render(example4, buf);
|
Example::new(&[Ratio(1, 2), Percentage(25), Length(10)]).render(example4, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_fill_example(&self, area: Rect, buf: &mut Buffer) {
|
fn render_fill_example(area: Rect, buf: &mut Buffer) {
|
||||||
let [example1, example2, _] = Layout::vertical([Length(EXAMPLE_HEIGHT); 3]).areas(area);
|
let [example1, example2, _] = Layout::vertical([Length(EXAMPLE_HEIGHT); 3]).areas(area);
|
||||||
|
|
||||||
Example::new(&[Fill(1), Fill(2), Fill(3)]).render(example1, buf);
|
Example::new(&[Fill(1), Fill(2), Fill(3)]).render(example1, buf);
|
||||||
Example::new(&[Fill(1), Percentage(50), Fill(1)]).render(example2, buf);
|
Example::new(&[Fill(1), Percentage(50), Fill(1)]).render(example2, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_min_example(&self, area: Rect, buf: &mut Buffer) {
|
fn render_min_example(area: Rect, buf: &mut Buffer) {
|
||||||
let [example1, example2, example3, example4, example5, _] =
|
let [example1, example2, example3, example4, example5, _] =
|
||||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
||||||
|
|
||||||
|
@ -348,7 +349,7 @@ impl SelectedTab {
|
||||||
Example::new(&[Percentage(100), Min(80)]).render(example5, buf);
|
Example::new(&[Percentage(100), Min(80)]).render(example5, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_max_example(&self, area: Rect, buf: &mut Buffer) {
|
fn render_max_example(area: Rect, buf: &mut Buffer) {
|
||||||
let [example1, example2, example3, example4, example5, _] =
|
let [example1, example2, example3, example4, example5, _] =
|
||||||
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
|
||||||
|
|
||||||
|
@ -379,14 +380,13 @@ impl Widget for Example {
|
||||||
let blocks = Layout::horizontal(&self.constraints).split(area);
|
let blocks = Layout::horizontal(&self.constraints).split(area);
|
||||||
|
|
||||||
for (block, constraint) in blocks.iter().zip(&self.constraints) {
|
for (block, constraint) in blocks.iter().zip(&self.constraints) {
|
||||||
self.illustration(*constraint, block.width)
|
Self::illustration(*constraint, block.width).render(*block, buf);
|
||||||
.render(*block, buf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Example {
|
impl Example {
|
||||||
fn illustration(&self, constraint: Constraint, width: u16) -> Paragraph {
|
fn illustration(constraint: Constraint, width: u16) -> impl Widget {
|
||||||
let color = match constraint {
|
let color = match constraint {
|
||||||
Constraint::Length(_) => LENGTH_COLOR,
|
Constraint::Length(_) => LENGTH_COLOR,
|
||||||
Constraint::Percentage(_) => PERCENTAGE_COLOR,
|
Constraint::Percentage(_) => PERCENTAGE_COLOR,
|
||||||
|
@ -417,7 +417,7 @@ fn init_error_hooks() -> Result<()> {
|
||||||
}))?;
|
}))?;
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
let _ = restore_terminal();
|
let _ = restore_terminal();
|
||||||
panic(info)
|
panic(info);
|
||||||
}));
|
}));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ use crossterm::{
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::Paragraph};
|
||||||
|
|
||||||
/// A custom widget that renders a button with a label, theme and state.
|
/// A custom widget that renders a button with a label, theme and state.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -71,7 +71,7 @@ const GREEN: Theme = Theme {
|
||||||
|
|
||||||
/// A button with a label that can be themed.
|
/// A button with a label that can be themed.
|
||||||
impl<'a> Button<'a> {
|
impl<'a> Button<'a> {
|
||||||
pub fn new<T: Into<Line<'a>>>(label: T) -> Button<'a> {
|
pub fn new<T: Into<Line<'a>>>(label: T) -> Self {
|
||||||
Button {
|
Button {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
theme: BLUE,
|
theme: BLUE,
|
||||||
|
@ -79,18 +79,19 @@ impl<'a> Button<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn theme(mut self, theme: Theme) -> Button<'a> {
|
pub const fn theme(mut self, theme: Theme) -> Self {
|
||||||
self.theme = theme;
|
self.theme = theme;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(mut self, state: State) -> Button<'a> {
|
pub const fn state(mut self, state: State) -> Self {
|
||||||
self.state = state;
|
self.state = state;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Button<'a> {
|
impl<'a> Widget for Button<'a> {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let (background, text, shadow, highlight) = self.colors();
|
let (background, text, shadow, highlight) = self.colors();
|
||||||
buf.set_style(area, Style::new().bg(background).fg(text));
|
buf.set_style(area, Style::new().bg(background).fg(text));
|
||||||
|
@ -124,7 +125,7 @@ impl<'a> Widget for Button<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Button<'_> {
|
impl Button<'_> {
|
||||||
fn colors(&self) -> (Color, Color, Color, Color) {
|
const fn colors(&self) -> (Color, Color, Color, Color) {
|
||||||
let theme = self.theme;
|
let theme = self.theme;
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Normal => (theme.background, theme.text, theme.shadow, theme.highlight),
|
State::Normal => (theme.background, theme.text, theme.shadow, theme.highlight),
|
||||||
|
@ -163,7 +164,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||||
let mut selected_button: usize = 0;
|
let mut selected_button: usize = 0;
|
||||||
let button_states = &mut [State::Selected, State::Normal, State::Normal];
|
let mut button_states = [State::Selected, State::Normal, State::Normal];
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|frame| ui(frame, button_states))?;
|
terminal.draw(|frame| ui(frame, button_states))?;
|
||||||
if !event::poll(Duration::from_millis(100))? {
|
if !event::poll(Duration::from_millis(100))? {
|
||||||
|
@ -174,18 +175,20 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||||
if key.kind != event::KeyEventKind::Press {
|
if key.kind != event::KeyEventKind::Press {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if handle_key_event(key, button_states, &mut selected_button).is_break() {
|
if handle_key_event(key, &mut button_states, &mut selected_button).is_break() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse) => handle_mouse_event(mouse, button_states, &mut selected_button),
|
Event::Mouse(mouse) => {
|
||||||
|
handle_mouse_event(mouse, &mut button_states, &mut selected_button);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(frame: &mut Frame, states: &[State; 3]) {
|
fn ui(frame: &mut Frame, states: [State; 3]) {
|
||||||
let vertical = Layout::vertical([
|
let vertical = Layout::vertical([
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
Constraint::Max(3),
|
Constraint::Max(3),
|
||||||
|
@ -202,7 +205,7 @@ fn ui(frame: &mut Frame, states: &[State; 3]) {
|
||||||
frame.render_widget(Paragraph::new("←/→: select, Space: toggle, q: quit"), help);
|
frame.render_widget(Paragraph::new("←/→: select, Space: toggle, q: quit"), help);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_buttons(frame: &mut Frame<'_>, area: Rect, states: &[State; 3]) {
|
fn render_buttons(frame: &mut Frame<'_>, area: Rect, states: [State; 3]) {
|
||||||
let horizontal = Layout::horizontal([
|
let horizontal = Layout::horizontal([
|
||||||
Constraint::Length(15),
|
Constraint::Length(15),
|
||||||
Constraint::Length(15),
|
Constraint::Length(15),
|
||||||
|
|
|
@ -2,7 +2,7 @@ use rand::{
|
||||||
distributions::{Distribution, Uniform},
|
distributions::{Distribution, Uniform},
|
||||||
rngs::ThreadRng,
|
rngs::ThreadRng,
|
||||||
};
|
};
|
||||||
use ratatui::widgets::*;
|
use ratatui::widgets::ListState;
|
||||||
|
|
||||||
const TASKS: [&str; 24] = [
|
const TASKS: [&str; 24] = [
|
||||||
"Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10",
|
"Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10",
|
||||||
|
@ -73,8 +73,8 @@ pub struct RandomSignal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RandomSignal {
|
impl RandomSignal {
|
||||||
pub fn new(lower: u64, upper: u64) -> RandomSignal {
|
pub fn new(lower: u64, upper: u64) -> Self {
|
||||||
RandomSignal {
|
Self {
|
||||||
distribution: Uniform::new(lower, upper),
|
distribution: Uniform::new(lower, upper),
|
||||||
rng: rand::thread_rng(),
|
rng: rand::thread_rng(),
|
||||||
}
|
}
|
||||||
|
@ -97,8 +97,8 @@ pub struct SinSignal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SinSignal {
|
impl SinSignal {
|
||||||
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
|
pub const fn new(interval: f64, period: f64, scale: f64) -> Self {
|
||||||
SinSignal {
|
Self {
|
||||||
x: 0.0,
|
x: 0.0,
|
||||||
interval,
|
interval,
|
||||||
period,
|
period,
|
||||||
|
@ -144,8 +144,8 @@ pub struct StatefulList<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> StatefulList<T> {
|
impl<T> StatefulList<T> {
|
||||||
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
pub fn with_items(items: Vec<T>) -> Self {
|
||||||
StatefulList {
|
Self {
|
||||||
state: ListState::default(),
|
state: ListState::default(),
|
||||||
items,
|
items,
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,7 @@ pub struct App<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl<'a> App<'a> {
|
||||||
pub fn new(title: &'a str, enhanced_graphics: bool) -> App<'a> {
|
pub fn new(title: &'a str, enhanced_graphics: bool) -> Self {
|
||||||
let mut rand_signal = RandomSignal::new(0, 100);
|
let mut rand_signal = RandomSignal::new(0, 100);
|
||||||
let sparkline_points = rand_signal.by_ref().take(300).collect();
|
let sparkline_points = rand_signal.by_ref().take(300).collect();
|
||||||
let mut sin_signal = SinSignal::new(0.2, 3.0, 18.0);
|
let mut sin_signal = SinSignal::new(0.2, 3.0, 18.0);
|
||||||
|
|
|
@ -4,7 +4,10 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use ratatui::prelude::*;
|
use ratatui::prelude::*;
|
||||||
use termwiz::{input::*, terminal::Terminal as TermwizTerminal};
|
use termwiz::{
|
||||||
|
input::{InputEvent, KeyCode},
|
||||||
|
terminal::Terminal as TermwizTerminal,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{app::App, ui};
|
use crate::{app::App, ui};
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widgets::{canvas::*, *},
|
widgets::{canvas::*, *},
|
||||||
|
@ -85,6 +86,7 @@ fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
|
||||||
f.render_widget(line_gauge, chunks[2]);
|
f.render_widget(line_gauge, chunks[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
|
fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
|
||||||
let constraints = if app.show_chart {
|
let constraints = if app.show_chart {
|
||||||
vec![Constraint::Percentage(50), Constraint::Percentage(50)]
|
vec![Constraint::Percentage(50), Constraint::Percentage(50)]
|
||||||
|
|
|
@ -80,11 +80,12 @@ impl App {
|
||||||
let timeout = Duration::from_secs_f64(1.0 / 50.0);
|
let timeout = Duration::from_secs_f64(1.0 / 50.0);
|
||||||
match term::next_event(timeout)? {
|
match term::next_event(timeout)? {
|
||||||
Some(Event::Key(key)) if key.kind == KeyEventKind::Press => self.handle_key_press(key),
|
Some(Event::Key(key)) if key.kind == KeyEventKind::Press => self.handle_key_press(key),
|
||||||
_ => Ok(()),
|
_ => {}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_key_press(&mut self, key: KeyEvent) -> Result<()> {
|
fn handle_key_press(&mut self, key: KeyEvent) {
|
||||||
use KeyCode::*;
|
use KeyCode::*;
|
||||||
match key.code {
|
match key.code {
|
||||||
Char('q') | Esc => self.mode = Mode::Quit,
|
Char('q') | Esc => self.mode = Mode::Quit,
|
||||||
|
@ -93,10 +94,8 @@ impl App {
|
||||||
Char('k') | Up => self.prev(),
|
Char('k') | Up => self.prev(),
|
||||||
Char('j') | Down => self.next(),
|
Char('j') | Down => self.next(),
|
||||||
Char('d') | Delete => self.destroy(),
|
Char('d') | Delete => self.destroy(),
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prev(&mut self) {
|
fn prev(&mut self) {
|
||||||
|
@ -124,11 +123,11 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_tab(&mut self) {
|
fn next_tab(&mut self) {
|
||||||
self.tab = self.tab.next()
|
self.tab = self.tab.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn destroy(&mut self) {
|
fn destroy(&mut self) {
|
||||||
self.mode = Mode::Destroy
|
self.mode = Mode::Destroy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +146,7 @@ impl Widget for &App {
|
||||||
Block::new().style(THEME.root).render(area, buf);
|
Block::new().style(THEME.root).render(area, buf);
|
||||||
self.render_title_bar(title_bar, buf);
|
self.render_title_bar(title_bar, buf);
|
||||||
self.render_selected_tab(tab, buf);
|
self.render_selected_tab(tab, buf);
|
||||||
self.render_bottom_bar(bottom_bar, buf);
|
App::render_bottom_bar(bottom_bar, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +156,7 @@ impl App {
|
||||||
let [title, tabs] = layout.areas(area);
|
let [title, tabs] = layout.areas(area);
|
||||||
|
|
||||||
Span::styled("Ratatui", THEME.app_title).render(title, buf);
|
Span::styled("Ratatui", THEME.app_title).render(title, buf);
|
||||||
let titles = Tab::iter().map(|tab| tab.title());
|
let titles = Tab::iter().map(Tab::title);
|
||||||
Tabs::new(titles)
|
Tabs::new(titles)
|
||||||
.style(THEME.tabs)
|
.style(THEME.tabs)
|
||||||
.highlight_style(THEME.tabs_selected)
|
.highlight_style(THEME.tabs_selected)
|
||||||
|
@ -177,7 +176,7 @@ impl App {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_bottom_bar(&self, area: Rect, buf: &mut Buffer) {
|
fn render_bottom_bar(area: Rect, buf: &mut Buffer) {
|
||||||
let keys = [
|
let keys = [
|
||||||
("H/←", "Left"),
|
("H/←", "Left"),
|
||||||
("L/→", "Right"),
|
("L/→", "Right"),
|
||||||
|
@ -189,8 +188,8 @@ impl App {
|
||||||
let spans = keys
|
let spans = keys
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(key, desc)| {
|
.flat_map(|(key, desc)| {
|
||||||
let key = Span::styled(format!(" {} ", key), THEME.key_binding.key);
|
let key = Span::styled(format!(" {key} "), THEME.key_binding.key);
|
||||||
let desc = Span::styled(format!(" {} ", desc), THEME.key_binding.description);
|
let desc = Span::styled(format!(" {desc} "), THEME.key_binding.description);
|
||||||
[key, desc]
|
[key, desc]
|
||||||
})
|
})
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
@ -202,22 +201,22 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
fn next(&self) -> Self {
|
fn next(self) -> Self {
|
||||||
let current_index = *self as usize;
|
let current_index = self as usize;
|
||||||
let next_index = current_index.saturating_add(1);
|
let next_index = current_index.saturating_add(1);
|
||||||
Self::from_repr(next_index).unwrap_or(*self)
|
Self::from_repr(next_index).unwrap_or(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prev(&self) -> Self {
|
fn prev(self) -> Self {
|
||||||
let current_index = *self as usize;
|
let current_index = self as usize;
|
||||||
let prev_index = current_index.saturating_sub(1);
|
let prev_index = current_index.saturating_sub(1);
|
||||||
Self::from_repr(prev_index).unwrap_or(*self)
|
Self::from_repr(prev_index).unwrap_or(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Tab::About => "".to_string(),
|
Self::About => String::new(),
|
||||||
tab => format!(" {} ", tab),
|
tab => format!(" {tab} "),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,17 +136,17 @@ pub struct BigText<'a> {
|
||||||
|
|
||||||
impl Widget for BigText<'_> {
|
impl Widget for BigText<'_> {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let layout = layout(area, &self.pixel_size);
|
let layout = layout(area, self.pixel_size);
|
||||||
for (line, line_layout) in self.lines.iter().zip(layout) {
|
for (line, line_layout) in self.lines.iter().zip(layout) {
|
||||||
for (g, cell) in line.styled_graphemes(self.style).zip(line_layout) {
|
for (g, cell) in line.styled_graphemes(self.style).zip(line_layout) {
|
||||||
render_symbol(g, cell, buf, &self.pixel_size);
|
render_symbol(&g, cell, buf, self.pixel_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns how many cells are needed to display a full 8x8 glyphe using the given font size
|
/// Returns how many cells are needed to display a full 8x8 glyphe using the given font size
|
||||||
fn cells_per_glyph(size: &PixelSize) -> (u16, u16) {
|
const fn cells_per_glyph(size: PixelSize) -> (u16, u16) {
|
||||||
match size {
|
match size {
|
||||||
PixelSize::Full => (8, 8),
|
PixelSize::Full => (8, 8),
|
||||||
PixelSize::HalfHeight => (8, 4),
|
PixelSize::HalfHeight => (8, 4),
|
||||||
|
@ -160,7 +160,7 @@ fn cells_per_glyph(size: &PixelSize) -> (u16, u16) {
|
||||||
/// The size of each cell depends on given font size
|
/// The size of each cell depends on given font size
|
||||||
fn layout(
|
fn layout(
|
||||||
area: Rect,
|
area: Rect,
|
||||||
pixel_size: &PixelSize,
|
pixel_size: PixelSize,
|
||||||
) -> impl IntoIterator<Item = impl IntoIterator<Item = Rect>> {
|
) -> impl IntoIterator<Item = impl IntoIterator<Item = Rect>> {
|
||||||
let (width, height) = cells_per_glyph(pixel_size);
|
let (width, height) = cells_per_glyph(pixel_size);
|
||||||
(area.top()..area.bottom())
|
(area.top()..area.bottom())
|
||||||
|
@ -178,7 +178,7 @@ fn layout(
|
||||||
|
|
||||||
/// Render a single grapheme into a cell by looking up the corresponding 8x8 bitmap in the
|
/// Render a single grapheme into a cell by looking up the corresponding 8x8 bitmap in the
|
||||||
/// `BITMAPS` array and setting the corresponding cells in the buffer.
|
/// `BITMAPS` array and setting the corresponding cells in the buffer.
|
||||||
fn render_symbol(grapheme: StyledGrapheme, area: Rect, buf: &mut Buffer, pixel_size: &PixelSize) {
|
fn render_symbol(grapheme: &StyledGrapheme, area: Rect, buf: &mut Buffer, pixel_size: PixelSize) {
|
||||||
buf.set_style(area, grapheme.style);
|
buf.set_style(area, grapheme.style);
|
||||||
let c = grapheme.symbol.chars().next().unwrap(); // TODO: handle multi-char graphemes
|
let c = grapheme.symbol.chars().next().unwrap(); // TODO: handle multi-char graphemes
|
||||||
if let Some(glyph) = font8x8::BASIC_FONTS.get(c) {
|
if let Some(glyph) = font8x8::BASIC_FONTS.get(c) {
|
||||||
|
@ -187,7 +187,7 @@ fn render_symbol(grapheme: StyledGrapheme, area: Rect, buf: &mut Buffer, pixel_s
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the correct unicode symbol for two vertical "pixels"
|
/// Get the correct unicode symbol for two vertical "pixels"
|
||||||
fn get_symbol_half_height(top: u8, bottom: u8) -> char {
|
const fn get_symbol_half_height(top: u8, bottom: u8) -> char {
|
||||||
match top {
|
match top {
|
||||||
0 => match bottom {
|
0 => match bottom {
|
||||||
0 => ' ',
|
0 => ' ',
|
||||||
|
@ -201,7 +201,7 @@ fn get_symbol_half_height(top: u8, bottom: u8) -> char {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the correct unicode symbol for two horizontal "pixels"
|
/// Get the correct unicode symbol for two horizontal "pixels"
|
||||||
fn get_symbol_half_width(left: u8, right: u8) -> char {
|
const fn get_symbol_half_width(left: u8, right: u8) -> char {
|
||||||
match left {
|
match left {
|
||||||
0 => match right {
|
0 => match right {
|
||||||
0 => ' ',
|
0 => ' ',
|
||||||
|
@ -215,20 +215,26 @@ fn get_symbol_half_width(left: u8, right: u8) -> char {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the correct unicode symbol for 2x2 "pixels"
|
/// Get the correct unicode symbol for 2x2 "pixels"
|
||||||
fn get_symbol_half_size(top_left: u8, top_right: u8, bottom_left: u8, bottom_right: u8) -> char {
|
const fn get_symbol_half_size(
|
||||||
let top_left = if top_left > 0 { 1 } else { 0 };
|
top_left: u8,
|
||||||
let top_right = if top_right > 0 { 1 } else { 0 };
|
top_right: u8,
|
||||||
let bottom_left = if bottom_left > 0 { 1 } else { 0 };
|
bottom_left: u8,
|
||||||
let bottom_right = if bottom_right > 0 { 1 } else { 0 };
|
bottom_right: u8,
|
||||||
|
) -> char {
|
||||||
const QUADRANT_SYMBOLS: [char; 16] = [
|
const QUADRANT_SYMBOLS: [char; 16] = [
|
||||||
' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛', '▗', '▚', '▐', '▜', '▄', '▙', '▟', '█',
|
' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛', '▗', '▚', '▐', '▜', '▄', '▙', '▟', '█',
|
||||||
];
|
];
|
||||||
QUADRANT_SYMBOLS[top_left + (top_right << 1) + (bottom_left << 2) + (bottom_right << 3)]
|
|
||||||
|
let top_left = if top_left > 0 { 1 } else { 0 };
|
||||||
|
let top_right = if top_right > 0 { 1 << 1 } else { 0 };
|
||||||
|
let bottom_left = if bottom_left > 0 { 1 << 2 } else { 0 };
|
||||||
|
let bottom_right = if bottom_right > 0 { 1 << 3 } else { 0 };
|
||||||
|
|
||||||
|
QUADRANT_SYMBOLS[top_left + top_right + bottom_left + bottom_right]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render a single 8x8 glyph into a cell by setting the corresponding cells in the buffer.
|
/// Render a single 8x8 glyph into a cell by setting the corresponding cells in the buffer.
|
||||||
fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: &PixelSize) {
|
fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: PixelSize) {
|
||||||
let (width, height) = cells_per_glyph(pixel_size);
|
let (width, height) = cells_per_glyph(pixel_size);
|
||||||
|
|
||||||
let glyph_vertical_index = (0..glyph.len()).step_by(8 / height as usize);
|
let glyph_vertical_index = (0..glyph.len()).step_by(8 / height as usize);
|
||||||
|
|
|
@ -9,13 +9,14 @@ use ratatui::prelude::*;
|
||||||
pub struct RgbSwatch;
|
pub struct RgbSwatch;
|
||||||
|
|
||||||
impl Widget for RgbSwatch {
|
impl Widget for RgbSwatch {
|
||||||
|
#[allow(clippy::cast_precision_loss, clippy::similar_names)]
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
for (yi, y) in (area.top()..area.bottom()).enumerate() {
|
for (yi, y) in (area.top()..area.bottom()).enumerate() {
|
||||||
let value = area.height as f32 - yi as f32;
|
let value = f32::from(area.height) - yi as f32;
|
||||||
let value_fg = value / (area.height as f32);
|
let value_fg = value / f32::from(area.height);
|
||||||
let value_bg = (value - 0.5) / (area.height as f32);
|
let value_bg = (value - 0.5) / f32::from(area.height);
|
||||||
for (xi, x) in (area.left()..area.right()).enumerate() {
|
for (xi, x) in (area.left()..area.right()).enumerate() {
|
||||||
let hue = xi as f32 * 360.0 / area.width as f32;
|
let hue = xi as f32 * 360.0 / f32::from(area.width);
|
||||||
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value_fg);
|
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value_fg);
|
||||||
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value_bg);
|
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value_bg);
|
||||||
buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
|
buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
|
||||||
|
@ -24,7 +25,7 @@ impl Widget for RgbSwatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a hue and value into an RGB color via the OkLab color space.
|
/// Convert a hue and value into an RGB color via the Oklab color space.
|
||||||
///
|
///
|
||||||
/// See <https://bottosson.github.io/posts/oklab/> for more details.
|
/// See <https://bottosson.github.io/posts/oklab/> for more details.
|
||||||
pub fn color_from_oklab(hue: f32, saturation: f32, value: f32) -> Color {
|
pub fn color_from_oklab(hue: f32, saturation: f32, value: f32) -> Color {
|
||||||
|
|
|
@ -30,11 +30,16 @@ pub fn destroy(frame: &mut Frame<'_>) {
|
||||||
///
|
///
|
||||||
/// Each pick some random pixels and move them each down one row. This is a very inefficient way to
|
/// Each pick some random pixels and move them each down one row. This is a very inefficient way to
|
||||||
/// do this, but it works well enough for this demo.
|
/// do this, but it works well enough for this demo.
|
||||||
|
#[allow(
|
||||||
|
clippy::cast_possible_truncation,
|
||||||
|
clippy::cast_precision_loss,
|
||||||
|
clippy::cast_sign_loss
|
||||||
|
)]
|
||||||
fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||||
// a seeded rng as we have to move the same random pixels each frame
|
// a seeded rng as we have to move the same random pixels each frame
|
||||||
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(10);
|
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(10);
|
||||||
let ramp_frames = 450;
|
let ramp_frames = 450;
|
||||||
let fractional_speed = frame_count as f64 / ramp_frames as f64;
|
let fractional_speed = frame_count as f64 / f64::from(ramp_frames);
|
||||||
let variable_speed = DRIP_SPEED as f64 * fractional_speed * fractional_speed * fractional_speed;
|
let variable_speed = DRIP_SPEED as f64 * fractional_speed * fractional_speed * fractional_speed;
|
||||||
let pixel_count = (frame_count as f64 * variable_speed).floor() as usize;
|
let pixel_count = (frame_count as f64 * variable_speed).floor() as usize;
|
||||||
for _ in 0..pixel_count {
|
for _ in 0..pixel_count {
|
||||||
|
@ -68,6 +73,7 @@ fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draw some text fading in and out from black to red and back
|
/// draw some text fading in and out from black to red and back
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
|
||||||
fn text(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
fn text(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
||||||
let sub_frame = frame_count.saturating_sub(TEXT_DELAY);
|
let sub_frame = frame_count.saturating_sub(TEXT_DELAY);
|
||||||
if sub_frame == 0 {
|
if sub_frame == 0 {
|
||||||
|
@ -114,10 +120,13 @@ fn blend(mask_color: Color, cell_color: Color, percentage: f64) -> Color {
|
||||||
return mask_color;
|
return mask_color;
|
||||||
};
|
};
|
||||||
|
|
||||||
let red = mask_red as f64 * percentage + cell_red as f64 * (1.0 - percentage);
|
let remain = 1.0 - percentage;
|
||||||
let green = mask_green as f64 * percentage + cell_green as f64 * (1.0 - percentage);
|
|
||||||
let blue = mask_blue as f64 * percentage + cell_blue as f64 * (1.0 - percentage);
|
|
||||||
|
|
||||||
|
let red = f64::from(mask_red).mul_add(percentage, f64::from(cell_red) * remain);
|
||||||
|
let green = f64::from(mask_green).mul_add(percentage, f64::from(cell_green) * remain);
|
||||||
|
let blue = f64::from(mask_blue).mul_add(percentage, f64::from(cell_blue) * remain);
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||||
Color::Rgb(red as u8, green as u8, blue as u8)
|
Color::Rgb(red as u8, green as u8, blue as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub fn init_hooks() -> Result<()> {
|
||||||
}))?;
|
}))?;
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
let _ = term::restore();
|
let _ = term::restore();
|
||||||
panic(info)
|
panic(info);
|
||||||
}));
|
}));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,14 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(
|
||||||
|
clippy::enum_glob_use,
|
||||||
|
clippy::missing_errors_doc,
|
||||||
|
clippy::module_name_repetitions,
|
||||||
|
clippy::must_use_candidate,
|
||||||
|
clippy::wildcard_imports
|
||||||
|
)]
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod big_text;
|
mod big_text;
|
||||||
mod colors;
|
mod colors;
|
||||||
|
|
|
@ -97,10 +97,11 @@ fn render_crate_description(area: Rect, buf: &mut Buffer) {
|
||||||
.render(area, buf);
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use half block characters to render a logo based on the RATATUI_LOGO const.
|
/// Use half block characters to render a logo based on the `RATATUI_LOGO` const.
|
||||||
///
|
///
|
||||||
/// The logo is rendered in three colors, one for the rat, one for the terminal, and one for the
|
/// The logo is rendered in three colors, one for the rat, one for the terminal, and one for the
|
||||||
/// rat's eye. The eye color alternates between two colors based on the selected row.
|
/// rat's eye. The eye color alternates between two colors based on the selected row.
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
pub fn render_logo(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
pub fn render_logo(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||||
let eye_color = if selected_row % 2 == 0 {
|
let eye_color = if selected_row % 2 == 0 {
|
||||||
THEME.logo.rat_eye
|
THEME.logo.rat_eye
|
||||||
|
|
|
@ -10,6 +10,7 @@ struct Ingredient {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ingredient {
|
impl Ingredient {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn height(&self) -> u16 {
|
fn height(&self) -> u16 {
|
||||||
self.name.lines().count() as u16
|
self.name.lines().count() as u16
|
||||||
}
|
}
|
||||||
|
@ -148,7 +149,7 @@ fn render_recipe(area: Rect, buf: &mut Buffer) {
|
||||||
|
|
||||||
fn render_ingredients(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
fn render_ingredients(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
||||||
let mut state = TableState::default().with_selected(Some(selected_row));
|
let mut state = TableState::default().with_selected(Some(selected_row));
|
||||||
let rows = INGREDIENTS.iter().cloned();
|
let rows = INGREDIENTS.iter().copied();
|
||||||
let theme = THEME.recipe;
|
let theme = THEME.recipe;
|
||||||
StatefulWidget::render(
|
StatefulWidget::render(
|
||||||
Table::new(rows, [Constraint::Length(7), Constraint::Length(30)])
|
Table::new(rows, [Constraint::Length(7), Constraint::Length(30)])
|
||||||
|
@ -171,5 +172,5 @@ fn render_scrollbar(position: usize, area: Rect, buf: &mut Buffer) {
|
||||||
.end_symbol(None)
|
.end_symbol(None)
|
||||||
.track_symbol(None)
|
.track_symbol(None)
|
||||||
.thumb_symbol("▐")
|
.thumb_symbol("▐")
|
||||||
.render(area, buf, &mut state)
|
.render(area, buf, &mut state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ fn render_simple_barchart(area: Rect, buf: &mut Buffer) {
|
||||||
// This doesn't actually render correctly as the text is too wide for the bar
|
// This doesn't actually render correctly as the text is too wide for the bar
|
||||||
// See https://github.com/ratatui-org/ratatui/issues/513 for more info
|
// See https://github.com/ratatui-org/ratatui/issues/513 for more info
|
||||||
// (the demo GIFs hack around this by hacking the calculation in bars.rs)
|
// (the demo GIFs hack around this by hacking the calculation in bars.rs)
|
||||||
.text_value(format!("{}°", value))
|
.text_value(format!("{value}°"))
|
||||||
.style(if value > 70 {
|
.style(if value > 70 {
|
||||||
Style::new().fg(Color::Red)
|
Style::new().fg(Color::Red)
|
||||||
} else {
|
} else {
|
||||||
|
@ -128,12 +128,14 @@ fn render_horizontal_barchart(area: Rect, buf: &mut Buffer) {
|
||||||
.render(area, buf);
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
pub fn render_gauge(progress: usize, area: Rect, buf: &mut Buffer) {
|
pub fn render_gauge(progress: usize, area: Rect, buf: &mut Buffer) {
|
||||||
let percent = (progress * 3).min(100) as f64;
|
let percent = (progress * 3).min(100) as f64;
|
||||||
|
|
||||||
render_line_gauge(percent, area, buf);
|
render_line_gauge(percent, area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
|
fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
|
||||||
// cycle color hue based on the percent for a neat effect yellow -> red
|
// cycle color hue based on the percent for a neat effect yellow -> red
|
||||||
let hue = 90.0 - (percent as f32 * 0.6);
|
let hue = 90.0 - (percent as f32 * 0.6);
|
||||||
|
@ -141,7 +143,7 @@ fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
|
||||||
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value);
|
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value);
|
||||||
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5);
|
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5);
|
||||||
let label = if percent < 100.0 {
|
let label = if percent < 100.0 {
|
||||||
format!("Downloading: {}%", percent)
|
format!("Downloading: {percent}%")
|
||||||
} else {
|
} else {
|
||||||
"Download Complete!".into()
|
"Download Complete!".into()
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,10 @@ use crossterm::{
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
ExecutableCommand,
|
ExecutableCommand,
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
/// Example code for lib.rs
|
/// Example code for lib.rs
|
||||||
///
|
///
|
||||||
|
@ -34,7 +37,6 @@ fn main() -> io::Result<()> {
|
||||||
let mut should_quit = false;
|
let mut should_quit = false;
|
||||||
while !should_quit {
|
while !should_quit {
|
||||||
terminal.draw(match arg.as_str() {
|
terminal.draw(match arg.as_str() {
|
||||||
"hello_world" => hello_world,
|
|
||||||
"layout" => layout,
|
"layout" => layout,
|
||||||
"styling" => styling,
|
"styling" => styling,
|
||||||
_ => hello_world,
|
_ => hello_world,
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||||
|
|
||||||
use std::io::{self, stdout};
|
use std::io::{self, stdout};
|
||||||
|
|
||||||
use color_eyre::{config::HookBuilder, Result};
|
use color_eyre::{config::HookBuilder, Result};
|
||||||
|
@ -165,7 +167,7 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_running(&self) -> bool {
|
fn is_running(self) -> bool {
|
||||||
self.state == AppState::Running
|
self.state == AppState::Running
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,14 +205,14 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self) {
|
fn up(&mut self) {
|
||||||
self.scroll_offset = self.scroll_offset.saturating_sub(1)
|
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn down(&mut self) {
|
fn down(&mut self) {
|
||||||
self.scroll_offset = self
|
self.scroll_offset = self
|
||||||
.scroll_offset
|
.scroll_offset
|
||||||
.saturating_add(1)
|
.saturating_add(1)
|
||||||
.min(max_scroll_offset())
|
.min(max_scroll_offset());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top(&mut self) {
|
fn top(&mut self) {
|
||||||
|
@ -239,8 +241,7 @@ fn max_scroll_offset() -> u16 {
|
||||||
example_height()
|
example_height()
|
||||||
- EXAMPLE_DATA
|
- EXAMPLE_DATA
|
||||||
.last()
|
.last()
|
||||||
.map(|(desc, _)| get_description_height(desc) + 4)
|
.map_or(0, |(desc, _)| get_description_height(desc) + 4)
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The height of all examples combined
|
/// The height of all examples combined
|
||||||
|
@ -264,12 +265,12 @@ impl Widget for App {
|
||||||
} else {
|
} else {
|
||||||
axis.width
|
axis.width
|
||||||
};
|
};
|
||||||
self.axis(axis_width, self.spacing).render(axis, buf);
|
Self::axis(axis_width, self.spacing).render(axis, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn tabs(&self) -> impl Widget {
|
fn tabs(self) -> impl Widget {
|
||||||
let tab_titles = SelectedTab::iter().map(SelectedTab::to_tab_title);
|
let tab_titles = SelectedTab::iter().map(SelectedTab::to_tab_title);
|
||||||
let block = Block::new()
|
let block = Block::new()
|
||||||
.title(Title::from("Flex Layouts ".bold()))
|
.title(Title::from("Flex Layouts ".bold()))
|
||||||
|
@ -283,13 +284,13 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a bar like `<----- 80 px (gap: 2 px)? ----->`
|
/// a bar like `<----- 80 px (gap: 2 px)? ----->`
|
||||||
fn axis(&self, width: u16, spacing: u16) -> impl Widget {
|
fn axis(width: u16, spacing: u16) -> impl Widget {
|
||||||
let width = width as usize;
|
let width = width as usize;
|
||||||
// only show gap when spacing is not zero
|
// only show gap when spacing is not zero
|
||||||
let label = if spacing != 0 {
|
let label = if spacing != 0 {
|
||||||
format!("{} px (gap: {} px)", width, spacing)
|
format!("{width} px (gap: {spacing} px)")
|
||||||
} else {
|
} else {
|
||||||
format!("{} px", width)
|
format!("{width} px")
|
||||||
};
|
};
|
||||||
let bar_width = width.saturating_sub(2); // we want to `<` and `>` at the ends
|
let bar_width = width.saturating_sub(2); // we want to `<` and `>` at the ends
|
||||||
let width_bar = format!("<{label:-^bar_width$}>");
|
let width_bar = format!("<{label:-^bar_width$}>");
|
||||||
|
@ -302,6 +303,7 @@ impl App {
|
||||||
/// into the main buffer. This is done to make it possible to handle scrolling easily.
|
/// into the main buffer. This is done to make it possible to handle scrolling easily.
|
||||||
///
|
///
|
||||||
/// Returns bool indicating whether scroll was needed
|
/// Returns bool indicating whether scroll was needed
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn render_demo(self, area: Rect, buf: &mut Buffer) -> bool {
|
fn render_demo(self, area: Rect, buf: &mut Buffer) -> bool {
|
||||||
// render demo content into a separate buffer so all examples fit we add an extra
|
// render demo content into a separate buffer so all examples fit we add an extra
|
||||||
// area.height to make sure the last example is fully visible even when the scroll offset is
|
// area.height to make sure the last example is fully visible even when the scroll offset is
|
||||||
|
@ -347,31 +349,30 @@ impl App {
|
||||||
|
|
||||||
impl SelectedTab {
|
impl SelectedTab {
|
||||||
/// Get the previous tab, if there is no previous tab return the current tab.
|
/// Get the previous tab, if there is no previous tab return the current tab.
|
||||||
fn previous(&self) -> Self {
|
fn previous(self) -> Self {
|
||||||
let current_index: usize = *self as usize;
|
let current_index: usize = self as usize;
|
||||||
let previous_index = current_index.saturating_sub(1);
|
let previous_index = current_index.saturating_sub(1);
|
||||||
Self::from_repr(previous_index).unwrap_or(*self)
|
Self::from_repr(previous_index).unwrap_or(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the next tab, if there is no next tab return the current tab.
|
/// Get the next tab, if there is no next tab return the current tab.
|
||||||
fn next(&self) -> Self {
|
fn next(self) -> Self {
|
||||||
let current_index = *self as usize;
|
let current_index = self as usize;
|
||||||
let next_index = current_index.saturating_add(1);
|
let next_index = current_index.saturating_add(1);
|
||||||
Self::from_repr(next_index).unwrap_or(*self)
|
Self::from_repr(next_index).unwrap_or(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a `SelectedTab` into a `Line` to display it by the `Tabs` widget.
|
/// Convert a `SelectedTab` into a `Line` to display it by the `Tabs` widget.
|
||||||
fn to_tab_title(value: SelectedTab) -> Line<'static> {
|
fn to_tab_title(value: Self) -> Line<'static> {
|
||||||
use tailwind::*;
|
use tailwind::*;
|
||||||
use SelectedTab::*;
|
|
||||||
let text = value.to_string();
|
let text = value.to_string();
|
||||||
let color = match value {
|
let color = match value {
|
||||||
Legacy => ORANGE.c400,
|
Self::Legacy => ORANGE.c400,
|
||||||
Start => SKY.c400,
|
Self::Start => SKY.c400,
|
||||||
Center => SKY.c300,
|
Self::Center => SKY.c300,
|
||||||
End => SKY.c200,
|
Self::End => SKY.c200,
|
||||||
SpaceAround => INDIGO.c400,
|
Self::SpaceAround => INDIGO.c400,
|
||||||
SpaceBetween => INDIGO.c300,
|
Self::SpaceBetween => INDIGO.c300,
|
||||||
};
|
};
|
||||||
format!(" {text} ").fg(color).bg(Color::Black).into()
|
format!(" {text} ").fg(color).bg(Color::Black).into()
|
||||||
}
|
}
|
||||||
|
@ -382,20 +383,18 @@ impl StatefulWidget for SelectedTab {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer, spacing: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, spacing: &mut Self::State) {
|
||||||
let spacing = *spacing;
|
let spacing = *spacing;
|
||||||
match self {
|
match self {
|
||||||
SelectedTab::Legacy => self.render_examples(area, buf, Flex::Legacy, spacing),
|
Self::Legacy => Self::render_examples(area, buf, Flex::Legacy, spacing),
|
||||||
SelectedTab::Start => self.render_examples(area, buf, Flex::Start, spacing),
|
Self::Start => Self::render_examples(area, buf, Flex::Start, spacing),
|
||||||
SelectedTab::Center => self.render_examples(area, buf, Flex::Center, spacing),
|
Self::Center => Self::render_examples(area, buf, Flex::Center, spacing),
|
||||||
SelectedTab::End => self.render_examples(area, buf, Flex::End, spacing),
|
Self::End => Self::render_examples(area, buf, Flex::End, spacing),
|
||||||
SelectedTab::SpaceAround => self.render_examples(area, buf, Flex::SpaceAround, spacing),
|
Self::SpaceAround => Self::render_examples(area, buf, Flex::SpaceAround, spacing),
|
||||||
SelectedTab::SpaceBetween => {
|
Self::SpaceBetween => Self::render_examples(area, buf, Flex::SpaceBetween, spacing),
|
||||||
self.render_examples(area, buf, Flex::SpaceBetween, spacing)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectedTab {
|
impl SelectedTab {
|
||||||
fn render_examples(&self, area: Rect, buf: &mut Buffer, flex: Flex, spacing: u16) {
|
fn render_examples(area: Rect, buf: &mut Buffer, flex: Flex, spacing: u16) {
|
||||||
let heights = EXAMPLE_DATA
|
let heights = EXAMPLE_DATA
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(desc, _)| get_description_height(desc) + 4);
|
.map(|(desc, _)| get_description_height(desc) + 4);
|
||||||
|
@ -432,7 +431,7 @@ impl Widget for Example {
|
||||||
Paragraph::new(
|
Paragraph::new(
|
||||||
self.description
|
self.description
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(|s| format!("// {}", s).italic().fg(tailwind::SLATE.c400))
|
.map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
|
||||||
.map(Line::from)
|
.map(Line::from)
|
||||||
.collect::<Vec<Line>>(),
|
.collect::<Vec<Line>>(),
|
||||||
)
|
)
|
||||||
|
@ -440,18 +439,17 @@ impl Widget for Example {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (block, constraint) in blocks.iter().zip(&self.constraints) {
|
for (block, constraint) in blocks.iter().zip(&self.constraints) {
|
||||||
self.illustration(*constraint, block.width)
|
Self::illustration(*constraint, block.width).render(*block, buf);
|
||||||
.render(*block, buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for spacer in spacers.iter() {
|
for spacer in spacers.iter() {
|
||||||
self.render_spacer(*spacer, buf);
|
Self::render_spacer(*spacer, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Example {
|
impl Example {
|
||||||
fn render_spacer(&self, spacer: Rect, buf: &mut Buffer) {
|
fn render_spacer(spacer: Rect, buf: &mut Buffer) {
|
||||||
if spacer.width > 1 {
|
if spacer.width > 1 {
|
||||||
let corners_only = symbols::border::Set {
|
let corners_only = symbols::border::Set {
|
||||||
top_left: line::NORMAL.top_left,
|
top_left: line::NORMAL.top_left,
|
||||||
|
@ -483,7 +481,7 @@ impl Example {
|
||||||
} else if width > 2 {
|
} else if width > 2 {
|
||||||
format!("{width}")
|
format!("{width}")
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
String::new()
|
||||||
};
|
};
|
||||||
let text = Text::from(vec![
|
let text = Text::from(vec![
|
||||||
Line::raw(""),
|
Line::raw(""),
|
||||||
|
@ -496,7 +494,7 @@ impl Example {
|
||||||
.render(spacer, buf);
|
.render(spacer, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn illustration(&self, constraint: Constraint, width: u16) -> Paragraph {
|
fn illustration(constraint: Constraint, width: u16) -> impl Widget {
|
||||||
let main_color = color_for_constraint(constraint);
|
let main_color = color_for_constraint(constraint);
|
||||||
let fg_color = Color::White;
|
let fg_color = Color::White;
|
||||||
let title = format!("{constraint}");
|
let title = format!("{constraint}");
|
||||||
|
@ -510,7 +508,7 @@ impl Example {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_for_constraint(constraint: Constraint) -> Color {
|
const fn color_for_constraint(constraint: Constraint) -> Color {
|
||||||
use tailwind::*;
|
use tailwind::*;
|
||||||
match constraint {
|
match constraint {
|
||||||
Constraint::Min(_) => BLUE.c900,
|
Constraint::Min(_) => BLUE.c900,
|
||||||
|
@ -532,7 +530,7 @@ fn init_error_hooks() -> Result<()> {
|
||||||
}))?;
|
}))?;
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
let _ = restore_terminal();
|
let _ = restore_terminal();
|
||||||
panic(info)
|
panic(info);
|
||||||
}));
|
}));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -551,6 +549,7 @@ fn restore_terminal() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn get_description_height(s: &str) -> u16 {
|
fn get_description_height(s: &str) -> u16 {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
0
|
0
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::enum_glob_use)]
|
||||||
|
|
||||||
use std::{io::stdout, time::Duration};
|
use std::{io::stdout, time::Duration};
|
||||||
|
|
||||||
use color_eyre::{config::HookBuilder, Result};
|
use color_eyre::{config::HookBuilder, Result};
|
||||||
|
@ -24,7 +26,7 @@ use crossterm::{
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
style::palette::tailwind,
|
style::palette::tailwind,
|
||||||
widgets::{block::Title, *},
|
widgets::{block::Title, Block, Borders, Gauge, Padding, Paragraph},
|
||||||
};
|
};
|
||||||
|
|
||||||
const GAUGE1_COLOR: Color = tailwind::RED.c800;
|
const GAUGE1_COLOR: Color = tailwind::RED.c800;
|
||||||
|
@ -84,7 +86,7 @@ impl App {
|
||||||
// difference between how a continuous gauge acts for floor and rounded values.
|
// difference between how a continuous gauge acts for floor and rounded values.
|
||||||
self.progress_columns = (self.progress_columns + 1).clamp(0, terminal_width);
|
self.progress_columns = (self.progress_columns + 1).clamp(0, terminal_width);
|
||||||
self.progress1 = self.progress_columns * 100 / terminal_width;
|
self.progress1 = self.progress_columns * 100 / terminal_width;
|
||||||
self.progress2 = self.progress_columns as f64 * 100.0 / terminal_width as f64;
|
self.progress2 = f64::from(self.progress_columns) * 100.0 / f64::from(terminal_width);
|
||||||
|
|
||||||
// progress3 and progress4 similarly show the difference between unicode and non-unicode
|
// progress3 and progress4 similarly show the difference between unicode and non-unicode
|
||||||
// gauges measuring the same thing.
|
// gauges measuring the same thing.
|
||||||
|
@ -119,6 +121,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &App {
|
impl Widget for &App {
|
||||||
|
#[allow(clippy::similar_names)]
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
use Constraint::*;
|
use Constraint::*;
|
||||||
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
|
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
|
||||||
|
@ -127,8 +130,8 @@ impl Widget for &App {
|
||||||
let layout = Layout::vertical([Ratio(1, 4); 4]);
|
let layout = Layout::vertical([Ratio(1, 4); 4]);
|
||||||
let [gauge1_area, gauge2_area, gauge3_area, gauge4_area] = layout.areas(gauge_area);
|
let [gauge1_area, gauge2_area, gauge3_area, gauge4_area] = layout.areas(gauge_area);
|
||||||
|
|
||||||
self.render_header(header_area, buf);
|
render_header(header_area, buf);
|
||||||
self.render_footer(footer_area, buf);
|
render_footer(footer_area, buf);
|
||||||
|
|
||||||
self.render_gauge1(gauge1_area, buf);
|
self.render_gauge1(gauge1_area, buf);
|
||||||
self.render_gauge2(gauge2_area, buf);
|
self.render_gauge2(gauge2_area, buf);
|
||||||
|
@ -137,23 +140,23 @@ impl Widget for &App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_header(area: Rect, buf: &mut Buffer) {
|
||||||
|
Paragraph::new("Ratatui Gauge Example")
|
||||||
|
.bold()
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.fg(CUSTOM_LABEL_COLOR)
|
||||||
|
.render(area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn render_header(&self, area: Rect, buf: &mut Buffer) {
|
|
||||||
Paragraph::new("Ratatui Gauge Example")
|
|
||||||
.bold()
|
|
||||||
.alignment(Alignment::Center)
|
|
||||||
.fg(CUSTOM_LABEL_COLOR)
|
|
||||||
.render(area, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_footer(&self, area: Rect, buf: &mut Buffer) {
|
|
||||||
Paragraph::new("Press ENTER to start")
|
|
||||||
.alignment(Alignment::Center)
|
|
||||||
.fg(CUSTOM_LABEL_COLOR)
|
|
||||||
.bold()
|
|
||||||
.render(area, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_gauge1(&self, area: Rect, buf: &mut Buffer) {
|
fn render_gauge1(&self, area: Rect, buf: &mut Buffer) {
|
||||||
let title = title_block("Gauge with percentage");
|
let title = title_block("Gauge with percentage");
|
||||||
Gauge::default()
|
Gauge::default()
|
||||||
|
@ -220,7 +223,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
|
||||||
}))?;
|
}))?;
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
let _ = restore_terminal();
|
let _ = restore_terminal();
|
||||||
panic(info)
|
panic(info);
|
||||||
}));
|
}));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ use crossterm::{
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::Paragraph};
|
||||||
|
|
||||||
/// This is a bare minimum example. There are many approaches to running an application loop, so
|
/// This is a bare minimum example. There are many approaches to running an application loop, so
|
||||||
/// this is not meant to be prescriptive. It is only meant to demonstrate the basic setup and
|
/// this is not meant to be prescriptive. It is only meant to demonstrate the basic setup and
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::wildcard_imports)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, VecDeque},
|
collections::{BTreeMap, VecDeque},
|
||||||
error::Error,
|
error::Error,
|
||||||
|
@ -129,6 +131,7 @@ fn input_handling(tx: mpsc::Sender<Event>) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_precision_loss, clippy::needless_pass_by_value)]
|
||||||
fn workers(tx: mpsc::Sender<Event>) -> Vec<Worker> {
|
fn workers(tx: mpsc::Sender<Event>) -> Vec<Worker> {
|
||||||
(0..4)
|
(0..4)
|
||||||
.map(|id| {
|
.map(|id| {
|
||||||
|
@ -168,6 +171,7 @@ fn downloads() -> Downloads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn run_app<B: Backend>(
|
fn run_app<B: Backend>(
|
||||||
terminal: &mut Terminal<B>,
|
terminal: &mut Terminal<B>,
|
||||||
workers: Vec<Worker>,
|
workers: Vec<Worker>,
|
||||||
|
@ -194,7 +198,7 @@ fn run_app<B: Backend>(
|
||||||
Event::DownloadUpdate(worker_id, _download_id, progress) => {
|
Event::DownloadUpdate(worker_id, _download_id, progress) => {
|
||||||
let download = downloads.in_progress.get_mut(&worker_id).unwrap();
|
let download = downloads.in_progress.get_mut(&worker_id).unwrap();
|
||||||
download.progress = progress;
|
download.progress = progress;
|
||||||
redraw = false
|
redraw = false;
|
||||||
}
|
}
|
||||||
Event::DownloadDone(worker_id, download_id) => {
|
Event::DownloadDone(worker_id, download_id) => {
|
||||||
let download = downloads.in_progress.remove(&worker_id).unwrap();
|
let download = downloads.in_progress.remove(&worker_id).unwrap();
|
||||||
|
@ -242,6 +246,7 @@ fn ui(f: &mut Frame, downloads: &Downloads) {
|
||||||
|
|
||||||
// total progress
|
// total progress
|
||||||
let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
|
let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
let progress = LineGauge::default()
|
let progress = LineGauge::default()
|
||||||
.gauge_style(Style::default().fg(Color::Blue))
|
.gauge_style(Style::default().fg(Color::Blue))
|
||||||
.label(format!("{done}/{NUM_DOWNLOADS}"))
|
.label(format!("{done}/{NUM_DOWNLOADS}"))
|
||||||
|
@ -271,6 +276,7 @@ fn ui(f: &mut Frame, downloads: &Downloads) {
|
||||||
let list = List::new(items);
|
let list = List::new(items);
|
||||||
f.render_widget(list, list_area);
|
f.render_widget(list, list_area);
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
for (i, (_, download)) in downloads.in_progress.iter().enumerate() {
|
for (i, (_, download)) in downloads.in_progress.iter().enumerate() {
|
||||||
let gauge = Gauge::default()
|
let gauge = Gauge::default()
|
||||||
.gauge_style(Style::default().fg(Color::Yellow))
|
.gauge_style(Style::default().fg(Color::Yellow))
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::enum_glob_use)]
|
||||||
|
|
||||||
use std::{error::Error, io};
|
use std::{error::Error, io};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
@ -21,7 +23,11 @@ use crossterm::{
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui::{layout::Constraint::*, prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
layout::Constraint::*,
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// setup terminal
|
// setup terminal
|
||||||
|
@ -55,13 +61,14 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||||
terminal.draw(ui)?;
|
terminal.draw(ui)?;
|
||||||
|
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if let KeyCode::Char('q') = key.code {
|
if key.code == KeyCode::Char('q') {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn ui(frame: &mut Frame) {
|
fn ui(frame: &mut Frame) {
|
||||||
let vertical = Layout::vertical([
|
let vertical = Layout::vertical([
|
||||||
Length(4), // text
|
Length(4), // text
|
||||||
|
@ -94,12 +101,12 @@ fn ui(frame: &mut Frame) {
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|area| {
|
.flat_map(|area| {
|
||||||
Layout::horizontal([
|
Layout::horizontal([
|
||||||
Constraint::Length(14),
|
Length(14),
|
||||||
Constraint::Length(14),
|
Length(14),
|
||||||
Constraint::Length(14),
|
Length(14),
|
||||||
Constraint::Length(14),
|
Length(14),
|
||||||
Constraint::Length(14),
|
Length(14),
|
||||||
Constraint::Min(0), // fills remaining space
|
Min(0), // fills remaining space
|
||||||
])
|
])
|
||||||
.split(*area)
|
.split(*area)
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -191,8 +198,8 @@ fn render_example_combination(
|
||||||
let inner = block.inner(area);
|
let inner = block.inner(area);
|
||||||
frame.render_widget(block, area);
|
frame.render_widget(block, area);
|
||||||
let layout = Layout::vertical(vec![Length(1); constraints.len() + 1]).split(inner);
|
let layout = Layout::vertical(vec![Length(1); constraints.len() + 1]).split(inner);
|
||||||
for (i, (a, b)) in constraints.iter().enumerate() {
|
for (i, (a, b)) in constraints.into_iter().enumerate() {
|
||||||
render_single_example(frame, layout[i], vec![*a, *b, Min(0)]);
|
render_single_example(frame, layout[i], vec![a, b, Min(0)]);
|
||||||
}
|
}
|
||||||
// This is to make it easy to visually see the alignment of the examples
|
// This is to make it easy to visually see the alignment of the examples
|
||||||
// with the constraints.
|
// with the constraints.
|
||||||
|
@ -213,11 +220,11 @@ fn render_single_example(frame: &mut Frame, area: Rect, constraints: Vec<Constra
|
||||||
|
|
||||||
fn constraint_label(constraint: Constraint) -> String {
|
fn constraint_label(constraint: Constraint) -> String {
|
||||||
match constraint {
|
match constraint {
|
||||||
Length(n) => format!("{n}"),
|
Constraint::Ratio(a, b) => format!("{a}:{b}"),
|
||||||
Min(n) => format!("{n}"),
|
Constraint::Length(n)
|
||||||
Max(n) => format!("{n}"),
|
| Constraint::Min(n)
|
||||||
Percentage(n) => format!("{n}"),
|
| Constraint::Max(n)
|
||||||
Fill(n) => format!("{n}"),
|
| Constraint::Percentage(n)
|
||||||
Ratio(a, b) => format!("{a}:{b}"),
|
| Constraint::Fill(n) => format!("{n}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||||
|
|
||||||
use std::{error::Error, io, io::stdout};
|
use std::{error::Error, io, io::stdout};
|
||||||
|
|
||||||
use color_eyre::config::HookBuilder;
|
use color_eyre::config::HookBuilder;
|
||||||
|
@ -81,7 +83,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
|
||||||
}))?;
|
}))?;
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
let _ = restore_terminal();
|
let _ = restore_terminal();
|
||||||
panic(info)
|
panic(info);
|
||||||
}));
|
}));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -100,9 +102,9 @@ fn restore_terminal() -> color_eyre::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App<'_> {
|
impl<'a> App<'a> {
|
||||||
fn new<'a>() -> App<'a> {
|
fn new() -> Self {
|
||||||
App {
|
Self {
|
||||||
items: StatefulList::with_items([
|
items: StatefulList::with_items([
|
||||||
("Rewrite everything with Rust!", "I can't hold my inner voice. He tells me to rewrite the complete universe with Rust", Status::Todo),
|
("Rewrite everything with Rust!", "I can't hold my inner voice. He tells me to rewrite the complete universe with Rust", Status::Todo),
|
||||||
("Rewrite all of your tui apps with Ratatui", "Yes, you heard that right. Go and replace your tui with Ratatui.", Status::Completed),
|
("Rewrite all of your tui apps with Ratatui", "Yes, you heard that right. Go and replace your tui with Ratatui.", Status::Completed),
|
||||||
|
@ -125,11 +127,11 @@ impl App<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn go_top(&mut self) {
|
fn go_top(&mut self) {
|
||||||
self.items.state.select(Some(0))
|
self.items.state.select(Some(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn go_bottom(&mut self) {
|
fn go_bottom(&mut self) {
|
||||||
self.items.state.select(Some(self.items.items.len() - 1))
|
self.items.state.select(Some(self.items.items.len() - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,21 +179,14 @@ impl Widget for &mut App<'_> {
|
||||||
let vertical = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]);
|
let vertical = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]);
|
||||||
let [upper_item_list_area, lower_item_list_area] = vertical.areas(rest_area);
|
let [upper_item_list_area, lower_item_list_area] = vertical.areas(rest_area);
|
||||||
|
|
||||||
self.render_title(header_area, buf);
|
render_title(header_area, buf);
|
||||||
self.render_todo(upper_item_list_area, buf);
|
self.render_todo(upper_item_list_area, buf);
|
||||||
self.render_info(lower_item_list_area, buf);
|
self.render_info(lower_item_list_area, buf);
|
||||||
self.render_footer(footer_area, buf);
|
render_footer(footer_area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App<'_> {
|
impl App<'_> {
|
||||||
fn render_title(&self, area: Rect, buf: &mut Buffer) {
|
|
||||||
Paragraph::new("Ratatui List Example")
|
|
||||||
.bold()
|
|
||||||
.centered()
|
|
||||||
.render(area, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_todo(&mut self, area: Rect, buf: &mut Buffer) {
|
fn render_todo(&mut self, area: Rect, buf: &mut Buffer) {
|
||||||
// We create two blocks, one is for the header (outer) and the other is for list (inner).
|
// We create two blocks, one is for the header (outer) and the other is for list (inner).
|
||||||
let outer_block = Block::default()
|
let outer_block = Block::default()
|
||||||
|
@ -278,14 +273,19 @@ impl App<'_> {
|
||||||
// We can now render the item info
|
// We can now render the item info
|
||||||
info_paragraph.render(inner_info_area, buf);
|
info_paragraph.render(inner_info_area, buf);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_footer(&self, area: Rect, buf: &mut Buffer) {
|
fn render_title(area: Rect, buf: &mut Buffer) {
|
||||||
Paragraph::new(
|
Paragraph::new("Ratatui List Example")
|
||||||
"\nUse ↓↑ to move, ← to unselect, → to change status, g/G to go top/bottom.",
|
.bold()
|
||||||
)
|
.centered()
|
||||||
|
.render(area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_footer(area: Rect, buf: &mut Buffer) {
|
||||||
|
Paragraph::new("\nUse ↓↑ to move, ← to unselect, → to change status, g/G to go top/bottom.")
|
||||||
.centered()
|
.centered()
|
||||||
.render(area, buf);
|
.render(area, buf);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatefulList<'_> {
|
impl StatefulList<'_> {
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
/// This example is useful for testing how your terminal emulator handles different modifiers.
|
// This example is useful for testing how your terminal emulator handles different modifiers.
|
||||||
/// It will render a grid of combinations of foreground and background colors with all
|
// It will render a grid of combinations of foreground and background colors with all
|
||||||
/// modifiers applied to them.
|
// modifiers applied to them.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{self, Stdout},
|
io::{self, Stdout},
|
||||||
|
@ -30,7 +31,7 @@ use crossterm::{
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::Paragraph};
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Box<dyn Error>>;
|
type Result<T> = result::Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||||
|
|
||||||
if event::poll(Duration::from_millis(250))? {
|
if event::poll(Duration::from_millis(250))? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if let KeyCode::Char('q') = key.code {
|
if key.code == KeyCode::Char('q') {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,8 +88,8 @@ fn ui(frame: &mut Frame) {
|
||||||
.chain(Modifier::all().iter())
|
.chain(Modifier::all().iter())
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
for bg in colors.iter() {
|
for bg in &colors {
|
||||||
for fg in colors.iter() {
|
for fg in &colors {
|
||||||
for modifier in &all_modifiers {
|
for modifier in &all_modifiers {
|
||||||
let modifier_name = format!("{modifier:11?}");
|
let modifier_name = format!("{modifier:11?}");
|
||||||
let padding = (" ").repeat(12 - modifier_name.len());
|
let padding = (" ").repeat(12 - modifier_name.len());
|
||||||
|
|
|
@ -35,7 +35,10 @@ use crossterm::{
|
||||||
event::{self, Event, KeyCode},
|
event::{self, Event, KeyCode},
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
|
|
@ -24,15 +24,18 @@ use crossterm::{
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Paragraph, Wrap},
|
||||||
|
};
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
scroll: u16,
|
scroll: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn new() -> App {
|
const fn new() -> Self {
|
||||||
App { scroll: 0 }
|
Self { scroll: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_tick(&mut self) {
|
fn on_tick(&mut self) {
|
||||||
|
@ -82,7 +85,7 @@ fn run_app<B: Backend>(
|
||||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||||
if crossterm::event::poll(timeout)? {
|
if crossterm::event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if let KeyCode::Char('q') = key.code {
|
if key.code == KeyCode::Char('q') {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,9 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
/// See also https://github.com/joshka/tui-popup and
|
// See also https://github.com/joshka/tui-popup and
|
||||||
/// https://github.com/sephiroth74/tui-confirm-dialog
|
// https://github.com/sephiroth74/tui-confirm-dialog
|
||||||
|
|
||||||
use std::{error::Error, io};
|
use std::{error::Error, io};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
@ -22,15 +23,18 @@ use crossterm::{
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Clear, Paragraph, Wrap},
|
||||||
|
};
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
show_popup: bool,
|
show_popup: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn new() -> App {
|
const fn new() -> Self {
|
||||||
App { show_popup: false }
|
Self { show_popup: false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,49 +22,46 @@ use std::{
|
||||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use itertools::izip;
|
use itertools::izip;
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::Paragraph};
|
||||||
|
|
||||||
/// A fun example of using half block characters to draw a logo
|
/// A fun example of using half block characters to draw a logo
|
||||||
fn main() -> io::Result<()> {
|
#[allow(clippy::many_single_char_names)]
|
||||||
|
fn logo() -> String {
|
||||||
let r = indoc! {"
|
let r = indoc! {"
|
||||||
▄▄▄
|
▄▄▄
|
||||||
█▄▄▀
|
█▄▄▀
|
||||||
█ █
|
█ █
|
||||||
"}
|
"};
|
||||||
.lines();
|
|
||||||
let a = indoc! {"
|
let a = indoc! {"
|
||||||
▄▄
|
▄▄
|
||||||
█▄▄█
|
█▄▄█
|
||||||
█ █
|
█ █
|
||||||
"}
|
"};
|
||||||
.lines();
|
|
||||||
let t = indoc! {"
|
let t = indoc! {"
|
||||||
▄▄▄
|
▄▄▄
|
||||||
█
|
█
|
||||||
█
|
█
|
||||||
"}
|
"};
|
||||||
.lines();
|
|
||||||
let u = indoc! {"
|
let u = indoc! {"
|
||||||
▄ ▄
|
▄ ▄
|
||||||
█ █
|
█ █
|
||||||
▀▄▄▀
|
▀▄▄▀
|
||||||
"}
|
"};
|
||||||
.lines();
|
|
||||||
let i = indoc! {"
|
let i = indoc! {"
|
||||||
▄
|
▄
|
||||||
█
|
█
|
||||||
█
|
█
|
||||||
"}
|
"};
|
||||||
.lines();
|
izip!(r.lines(), a.lines(), t.lines(), u.lines(), i.lines())
|
||||||
|
.map(|(r, a, t, u, i)| format!("{r:5}{a:5}{t:4}{a:5}{t:4}{u:5}{i:5}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
let mut terminal = init()?;
|
let mut terminal = init()?;
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
let logo = izip!(r, a.clone(), t.clone(), a, t, u, i)
|
frame.render_widget(Paragraph::new(logo()), frame.size());
|
||||||
.map(|(r, a, t, a2, t2, u, i)| {
|
|
||||||
format!("{:5}{:5}{:4}{:5}{:4}{:5}{:5}", r, a, t, a2, t2, u, i)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n");
|
|
||||||
frame.render_widget(Paragraph::new(logo), frame.size());
|
|
||||||
})?;
|
})?;
|
||||||
sleep(Duration::from_secs(5));
|
sleep(Duration::from_secs(5));
|
||||||
restore()?;
|
restore()?;
|
||||||
|
@ -72,7 +69,7 @@ fn main() -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> io::Result<Terminal<impl Backend>> {
|
fn init() -> io::Result<Terminal<impl Backend>> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let options = TerminalOptions {
|
let options = TerminalOptions {
|
||||||
viewport: Viewport::Inline(3),
|
viewport: Viewport::Inline(3),
|
||||||
|
@ -80,7 +77,7 @@ pub fn init() -> io::Result<Terminal<impl Backend>> {
|
||||||
Terminal::with_options(CrosstermBackend::new(stdout()), options)
|
Terminal::with_options(CrosstermBackend::new(stdout()), options)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore() -> io::Result<()> {
|
fn restore() -> io::Result<()> {
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,17 +28,20 @@ use rand::{
|
||||||
distributions::{Distribution, Uniform},
|
distributions::{Distribution, Uniform},
|
||||||
rngs::ThreadRng,
|
rngs::ThreadRng,
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Sparkline},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RandomSignal {
|
struct RandomSignal {
|
||||||
distribution: Uniform<u64>,
|
distribution: Uniform<u64>,
|
||||||
rng: ThreadRng,
|
rng: ThreadRng,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RandomSignal {
|
impl RandomSignal {
|
||||||
pub fn new(lower: u64, upper: u64) -> RandomSignal {
|
fn new(lower: u64, upper: u64) -> Self {
|
||||||
RandomSignal {
|
Self {
|
||||||
distribution: Uniform::new(lower, upper),
|
distribution: Uniform::new(lower, upper),
|
||||||
rng: rand::thread_rng(),
|
rng: rand::thread_rng(),
|
||||||
}
|
}
|
||||||
|
@ -60,12 +63,12 @@ struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn new() -> App {
|
fn new() -> Self {
|
||||||
let mut signal = RandomSignal::new(0, 100);
|
let mut signal = RandomSignal::new(0, 100);
|
||||||
let data1 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
let data1 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
||||||
let data2 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
let data2 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
||||||
let data3 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
let data3 = signal.by_ref().take(200).collect::<Vec<u64>>();
|
||||||
App {
|
Self {
|
||||||
signal,
|
signal,
|
||||||
data1,
|
data1,
|
||||||
data2,
|
data2,
|
||||||
|
@ -127,7 +130,7 @@ fn run_app<B: Backend>(
|
||||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||||
if crossterm::event::poll(timeout)? {
|
if crossterm::event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if let KeyCode::Char('q') = key.code {
|
if key.code == KeyCode::Char('q') {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
#![allow(clippy::enum_glob_use, clippy::wildcard_imports)]
|
||||||
|
|
||||||
use std::{error::Error, io};
|
use std::{error::Error, io};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
@ -48,7 +50,7 @@ struct TableColors {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableColors {
|
impl TableColors {
|
||||||
fn new(color: &tailwind::Palette) -> Self {
|
const fn new(color: &tailwind::Palette) -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffer_bg: tailwind::SLATE.c950,
|
buffer_bg: tailwind::SLATE.c950,
|
||||||
header_bg: color.c900,
|
header_bg: color.c900,
|
||||||
|
@ -69,7 +71,7 @@ struct Data {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
fn ref_array(&self) -> [&String; 3] {
|
const fn ref_array(&self) -> [&String; 3] {
|
||||||
[&self.name, &self.address, &self.email]
|
[&self.name, &self.address, &self.email]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +98,9 @@ struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn new() -> App {
|
fn new() -> Self {
|
||||||
let data_vec = generate_fake_names();
|
let data_vec = generate_fake_names();
|
||||||
App {
|
Self {
|
||||||
state: TableState::default().with_selected(0),
|
state: TableState::default().with_selected(0),
|
||||||
longest_item_lens: constraint_len_calculator(&data_vec),
|
longest_item_lens: constraint_len_calculator(&data_vec),
|
||||||
scroll_state: ScrollbarState::new((data_vec.len() - 1) * ITEM_HEIGHT),
|
scroll_state: ScrollbarState::new((data_vec.len() - 1) * ITEM_HEIGHT),
|
||||||
|
@ -147,7 +149,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_colors(&mut self) {
|
pub fn set_colors(&mut self) {
|
||||||
self.colors = TableColors::new(&PALETTES[self.color_index])
|
self.colors = TableColors::new(&PALETTES[self.color_index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,8 +247,7 @@ fn render_table(f: &mut Frame, app: &mut App, area: Rect) {
|
||||||
.fg(app.colors.selected_style_fg);
|
.fg(app.colors.selected_style_fg);
|
||||||
|
|
||||||
let header = ["Name", "Address", "Email"]
|
let header = ["Name", "Address", "Email"]
|
||||||
.iter()
|
.into_iter()
|
||||||
.cloned()
|
|
||||||
.map(Cell::from)
|
.map(Cell::from)
|
||||||
.collect::<Row>()
|
.collect::<Row>()
|
||||||
.style(header_style)
|
.style(header_style)
|
||||||
|
@ -257,9 +258,8 @@ fn render_table(f: &mut Frame, app: &mut App, area: Rect) {
|
||||||
_ => app.colors.alt_row_color,
|
_ => app.colors.alt_row_color,
|
||||||
};
|
};
|
||||||
let item = data.ref_array();
|
let item = data.ref_array();
|
||||||
item.iter()
|
item.into_iter()
|
||||||
.cloned()
|
.map(|content| Cell::from(Text::from(format!("\n{content}\n"))))
|
||||||
.map(|content| Cell::from(Text::from(format!("\n{}\n", content))))
|
|
||||||
.collect::<Row>()
|
.collect::<Row>()
|
||||||
.style(Style::new().fg(app.colors.row_fg).bg(color))
|
.style(Style::new().fg(app.colors.row_fg).bg(color))
|
||||||
.height(4)
|
.height(4)
|
||||||
|
@ -308,6 +308,7 @@ fn constraint_len_calculator(items: &[Data]) -> (u16, u16, u16) {
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
(name_len as u16, address_len as u16, email_len as u16)
|
(name_len as u16, address_len as u16, email_len as u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,7 +326,7 @@ fn render_scrollbar(f: &mut Frame, app: &mut App, area: Rect) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_footer(f: &mut Frame, app: &mut App, area: Rect) {
|
fn render_footer(f: &mut Frame, app: &App, area: Rect) {
|
||||||
let info_footer = Paragraph::new(Line::from(INFO_TEXT))
|
let info_footer = Paragraph::new(Line::from(INFO_TEXT))
|
||||||
.style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg))
|
.style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg))
|
||||||
.centered()
|
.centered()
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
use std::{io, io::stdout};
|
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
|
||||||
|
|
||||||
|
use std::io::stdout;
|
||||||
|
|
||||||
use color_eyre::{config::HookBuilder, Result};
|
use color_eyre::{config::HookBuilder, Result};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
|
@ -72,7 +74,7 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self) -> Result<(), io::Error> {
|
fn handle_events(&mut self) -> std::io::Result<()> {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
if key.kind == KeyEventKind::Press {
|
if key.kind == KeyEventKind::Press {
|
||||||
use KeyCode::*;
|
use KeyCode::*;
|
||||||
|
@ -102,17 +104,17 @@ impl App {
|
||||||
|
|
||||||
impl SelectedTab {
|
impl SelectedTab {
|
||||||
/// Get the previous tab, if there is no previous tab return the current tab.
|
/// Get the previous tab, if there is no previous tab return the current tab.
|
||||||
fn previous(&self) -> Self {
|
fn previous(self) -> Self {
|
||||||
let current_index: usize = *self as usize;
|
let current_index: usize = self as usize;
|
||||||
let previous_index = current_index.saturating_sub(1);
|
let previous_index = current_index.saturating_sub(1);
|
||||||
Self::from_repr(previous_index).unwrap_or(*self)
|
Self::from_repr(previous_index).unwrap_or(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the next tab, if there is no next tab return the current tab.
|
/// Get the next tab, if there is no next tab return the current tab.
|
||||||
fn next(&self) -> Self {
|
fn next(self) -> Self {
|
||||||
let current_index = *self as usize;
|
let current_index = self as usize;
|
||||||
let next_index = current_index.saturating_add(1);
|
let next_index = current_index.saturating_add(1);
|
||||||
Self::from_repr(next_index).unwrap_or(*self)
|
Self::from_repr(next_index).unwrap_or(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,20 +127,16 @@ impl Widget for &App {
|
||||||
let horizontal = Layout::horizontal([Min(0), Length(20)]);
|
let horizontal = Layout::horizontal([Min(0), Length(20)]);
|
||||||
let [tabs_area, title_area] = horizontal.areas(header_area);
|
let [tabs_area, title_area] = horizontal.areas(header_area);
|
||||||
|
|
||||||
self.render_title(title_area, buf);
|
render_title(title_area, buf);
|
||||||
self.render_tabs(tabs_area, buf);
|
self.render_tabs(tabs_area, buf);
|
||||||
self.selected_tab.render(inner_area, buf);
|
self.selected_tab.render(inner_area, buf);
|
||||||
self.render_footer(footer_area, buf);
|
render_footer(footer_area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn render_title(&self, area: Rect, buf: &mut Buffer) {
|
|
||||||
"Ratatui Tabs Example".bold().render(area, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_tabs(&self, area: Rect, buf: &mut Buffer) {
|
fn render_tabs(&self, area: Rect, buf: &mut Buffer) {
|
||||||
let titles = SelectedTab::iter().map(|tab| tab.title());
|
let titles = SelectedTab::iter().map(SelectedTab::title);
|
||||||
let highlight_style = (Color::default(), self.selected_tab.palette().c700);
|
let highlight_style = (Color::default(), self.selected_tab.palette().c700);
|
||||||
let selected_tab_index = self.selected_tab as usize;
|
let selected_tab_index = self.selected_tab as usize;
|
||||||
Tabs::new(titles)
|
Tabs::new(titles)
|
||||||
|
@ -148,61 +146,65 @@ impl App {
|
||||||
.divider(" ")
|
.divider(" ")
|
||||||
.render(area, buf);
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_footer(&self, area: Rect, buf: &mut Buffer) {
|
fn render_title(area: Rect, buf: &mut Buffer) {
|
||||||
Line::raw("◄ ► to change tab | Press q to quit")
|
"Ratatui Tabs Example".bold().render(area, buf);
|
||||||
.centered()
|
}
|
||||||
.render(area, buf);
|
|
||||||
}
|
fn render_footer(area: Rect, buf: &mut Buffer) {
|
||||||
|
Line::raw("◄ ► to change tab | Press q to quit")
|
||||||
|
.centered()
|
||||||
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for SelectedTab {
|
impl Widget for SelectedTab {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
// in a real app these might be separate widgets
|
// in a real app these might be separate widgets
|
||||||
match self {
|
match self {
|
||||||
SelectedTab::Tab1 => self.render_tab0(area, buf),
|
Self::Tab1 => self.render_tab0(area, buf),
|
||||||
SelectedTab::Tab2 => self.render_tab1(area, buf),
|
Self::Tab2 => self.render_tab1(area, buf),
|
||||||
SelectedTab::Tab3 => self.render_tab2(area, buf),
|
Self::Tab3 => self.render_tab2(area, buf),
|
||||||
SelectedTab::Tab4 => self.render_tab3(area, buf),
|
Self::Tab4 => self.render_tab3(area, buf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectedTab {
|
impl SelectedTab {
|
||||||
/// Return tab's name as a styled `Line`
|
/// Return tab's name as a styled `Line`
|
||||||
fn title(&self) -> Line<'static> {
|
fn title(self) -> Line<'static> {
|
||||||
format!(" {self} ")
|
format!(" {self} ")
|
||||||
.fg(tailwind::SLATE.c200)
|
.fg(tailwind::SLATE.c200)
|
||||||
.bg(self.palette().c900)
|
.bg(self.palette().c900)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tab0(&self, area: Rect, buf: &mut Buffer) {
|
fn render_tab0(self, area: Rect, buf: &mut Buffer) {
|
||||||
Paragraph::new("Hello, World!")
|
Paragraph::new("Hello, World!")
|
||||||
.block(self.block())
|
.block(self.block())
|
||||||
.render(area, buf)
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tab1(&self, area: Rect, buf: &mut Buffer) {
|
fn render_tab1(self, area: Rect, buf: &mut Buffer) {
|
||||||
Paragraph::new("Welcome to the Ratatui tabs example!")
|
Paragraph::new("Welcome to the Ratatui tabs example!")
|
||||||
.block(self.block())
|
.block(self.block())
|
||||||
.render(area, buf)
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tab2(&self, area: Rect, buf: &mut Buffer) {
|
fn render_tab2(self, area: Rect, buf: &mut Buffer) {
|
||||||
Paragraph::new("Look! I'm different than others!")
|
Paragraph::new("Look! I'm different than others!")
|
||||||
.block(self.block())
|
.block(self.block())
|
||||||
.render(area, buf)
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tab3(&self, area: Rect, buf: &mut Buffer) {
|
fn render_tab3(self, area: Rect, buf: &mut Buffer) {
|
||||||
Paragraph::new("I know, these are some basic changes. But I think you got the main idea.")
|
Paragraph::new("I know, these are some basic changes. But I think you got the main idea.")
|
||||||
.block(self.block())
|
.block(self.block())
|
||||||
.render(area, buf)
|
.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A block surrounding the tab's content
|
/// A block surrounding the tab's content
|
||||||
fn block(&self) -> Block<'static> {
|
fn block(self) -> Block<'static> {
|
||||||
Block::default()
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_set(symbols::border::PROPORTIONAL_TALL)
|
.border_set(symbols::border::PROPORTIONAL_TALL)
|
||||||
|
@ -210,12 +212,12 @@ impl SelectedTab {
|
||||||
.border_style(self.palette().c700)
|
.border_style(self.palette().c700)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn palette(&self) -> tailwind::Palette {
|
const fn palette(self) -> tailwind::Palette {
|
||||||
match self {
|
match self {
|
||||||
SelectedTab::Tab1 => tailwind::BLUE,
|
Self::Tab1 => tailwind::BLUE,
|
||||||
SelectedTab::Tab2 => tailwind::EMERALD,
|
Self::Tab2 => tailwind::EMERALD,
|
||||||
SelectedTab::Tab3 => tailwind::INDIGO,
|
Self::Tab3 => tailwind::INDIGO,
|
||||||
SelectedTab::Tab4 => tailwind::RED,
|
Self::Tab4 => tailwind::RED,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,7 +232,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
|
||||||
}))?;
|
}))?;
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
let _ = restore_terminal();
|
let _ = restore_terminal();
|
||||||
panic(info)
|
panic(info);
|
||||||
}));
|
}));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,28 +13,31 @@
|
||||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||||
|
|
||||||
|
// A simple example demonstrating how to handle user input. This is a bit out of the scope of
|
||||||
|
// the library as it does not provide any input handling out of the box. However, it may helps
|
||||||
|
// some to get started.
|
||||||
|
//
|
||||||
|
// This is a very simple example:
|
||||||
|
// * An input box always focused. Every character you type is registered here.
|
||||||
|
// * An entered character is inserted at the cursor position.
|
||||||
|
// * Pressing Backspace erases the left character before the cursor position
|
||||||
|
// * Pressing Enter pushes the current input in the history of previous messages. **Note: ** as
|
||||||
|
// this is a relatively simple example unicode characters are unsupported and their use will
|
||||||
|
// result in undefined behaviour.
|
||||||
|
//
|
||||||
|
// See also https://github.com/rhysd/tui-textarea and https://github.com/sayanarijit/tui-input/
|
||||||
|
|
||||||
use std::{error::Error, io};
|
use std::{error::Error, io};
|
||||||
|
|
||||||
/// A simple example demonstrating how to handle user input. This is a bit out of the scope of
|
|
||||||
/// the library as it does not provide any input handling out of the box. However, it may helps
|
|
||||||
/// some to get started.
|
|
||||||
///
|
|
||||||
/// This is a very simple example:
|
|
||||||
/// * An input box always focused. Every character you type is registered here.
|
|
||||||
/// * An entered character is inserted at the cursor position.
|
|
||||||
/// * Pressing Backspace erases the left character before the cursor position
|
|
||||||
/// * Pressing Enter pushes the current input in the history of previous messages. **Note: **
|
|
||||||
/// as
|
|
||||||
/// this is a relatively simple example unicode characters are unsupported and their use will
|
|
||||||
/// result in undefined behaviour.
|
|
||||||
///
|
|
||||||
/// See also https://github.com/rhysd/tui-textarea and https://github.com/sayanarijit/tui-input/
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, List, ListItem, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
enum InputMode {
|
enum InputMode {
|
||||||
Normal,
|
Normal,
|
||||||
|
@ -53,18 +56,16 @@ struct App {
|
||||||
messages: Vec<String>,
|
messages: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for App {
|
impl App {
|
||||||
fn default() -> App {
|
const fn new() -> Self {
|
||||||
App {
|
Self {
|
||||||
input: String::new(),
|
input: String::new(),
|
||||||
input_mode: InputMode::Normal,
|
input_mode: InputMode::Normal,
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
cursor_position: 0,
|
cursor_position: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
fn move_cursor_left(&mut self) {
|
fn move_cursor_left(&mut self) {
|
||||||
let cursor_moved_left = self.cursor_position.saturating_sub(1);
|
let cursor_moved_left = self.cursor_position.saturating_sub(1);
|
||||||
self.cursor_position = self.clamp_cursor(cursor_moved_left);
|
self.cursor_position = self.clamp_cursor(cursor_moved_left);
|
||||||
|
@ -127,7 +128,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
// create app and run it
|
// create app and run it
|
||||||
let app = App::default();
|
let app = App::new();
|
||||||
let res = run_app(&mut terminal, app);
|
let res = run_app(&mut terminal, app);
|
||||||
|
|
||||||
// restore terminal
|
// restore terminal
|
||||||
|
@ -180,7 +181,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
_ => {}
|
InputMode::Editing => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,13 +236,14 @@ fn ui(f: &mut Frame, app: &App) {
|
||||||
InputMode::Editing => {
|
InputMode::Editing => {
|
||||||
// Make the cursor visible and ask ratatui to put it at the specified coordinates after
|
// Make the cursor visible and ask ratatui to put it at the specified coordinates after
|
||||||
// rendering
|
// rendering
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
f.set_cursor(
|
f.set_cursor(
|
||||||
// Draw the cursor at the current position in the input field.
|
// Draw the cursor at the current position in the input field.
|
||||||
// This position is can be controlled via the left and right arrow key
|
// This position is can be controlled via the left and right arrow key
|
||||||
input_area.x + app.cursor_position as u16 + 1,
|
input_area.x + app.cursor_position as u16 + 1,
|
||||||
// Move one line down, from the border to the input line
|
// Move one line down, from the border to the input line
|
||||||
input_area.y + 1,
|
input_area.y + 1,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,59 +18,62 @@ use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
list_state: ListState,
|
list: ListState,
|
||||||
table_state: TableState,
|
table: TableState,
|
||||||
scrollbar_state: ScrollbarState,
|
scrollbar: ScrollbarState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppState {
|
impl Default for AppState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
list_state: ListState::default(),
|
list: ListState::default(),
|
||||||
table_state: TableState::default(),
|
table: TableState::default(),
|
||||||
scrollbar_state: ScrollbarState::new(10),
|
scrollbar: ScrollbarState::new(10),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl AppState {
|
impl AppState {
|
||||||
fn select(&mut self, index: usize) {
|
fn select(&mut self, index: usize) {
|
||||||
self.list_state.select(Some(index));
|
self.list.select(Some(index));
|
||||||
self.table_state.select(Some(index));
|
self.table.select(Some(index));
|
||||||
self.scrollbar_state = self.scrollbar_state.position(index);
|
self.scrollbar = self.scrollbar.position(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the list to a TestBackend and asserts that the result matches the expected buffer.
|
/// Renders the list to a `TestBackend` and asserts that the result matches the expected buffer.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_buffer(state: &mut AppState, expected: &Buffer) {
|
fn assert_buffer(state: &mut AppState, expected: &Buffer) {
|
||||||
let backend = TestBackend::new(21, 5);
|
let backend = TestBackend::new(21, 5);
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
terminal
|
terminal
|
||||||
.draw(|f| {
|
.draw(|f| {
|
||||||
let items = vec![
|
let items = [
|
||||||
"awa", "banana", "Cats!!", "d20", "Echo", "Foxtrot", "Golf", "Hotel", "IwI",
|
"awa", "banana", "Cats!!", "d20", "Echo", "Foxtrot", "Golf", "Hotel", "IwI",
|
||||||
"Juliett",
|
"Juliett",
|
||||||
];
|
];
|
||||||
|
|
||||||
use Constraint::*;
|
|
||||||
let layout = Layout::default()
|
let layout = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Length(10), Length(10), Length(1)])
|
.constraints([
|
||||||
|
Constraint::Length(10),
|
||||||
|
Constraint::Length(10),
|
||||||
|
Constraint::Length(1),
|
||||||
|
])
|
||||||
.split(f.size());
|
.split(f.size());
|
||||||
let list = List::new(items.clone())
|
let list = List::new(items)
|
||||||
.highlight_symbol(">>")
|
.highlight_symbol(">>")
|
||||||
.block(Block::default().borders(Borders::RIGHT));
|
.block(Block::default().borders(Borders::RIGHT));
|
||||||
f.render_stateful_widget(list, layout[0], &mut state.list_state);
|
f.render_stateful_widget(list, layout[0], &mut state.list);
|
||||||
|
|
||||||
let table = Table::new(
|
let table = Table::new(
|
||||||
items.iter().map(|i| Row::new(vec![*i])),
|
items.into_iter().map(|i| Row::new(vec![i])),
|
||||||
[Constraint::Length(10); 1],
|
[Constraint::Length(10); 1],
|
||||||
)
|
)
|
||||||
.highlight_symbol(">>");
|
.highlight_symbol(">>");
|
||||||
f.render_stateful_widget(table, layout[1], &mut state.table_state);
|
f.render_stateful_widget(table, layout[1], &mut state.table);
|
||||||
|
|
||||||
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight);
|
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight);
|
||||||
f.render_stateful_widget(scrollbar, layout[2], &mut state.scrollbar_state);
|
f.render_stateful_widget(scrollbar, layout[2], &mut state.scrollbar);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
terminal.backend().assert_buffer(expected);
|
terminal.backend().assert_buffer(expected);
|
||||||
|
@ -85,15 +88,15 @@ const DEFAULT_STATE_BUFFER: [&str; 5] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_STATE_REPR: &str = r#"{
|
const DEFAULT_STATE_REPR: &str = r#"{
|
||||||
"list_state": {
|
"list": {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"selected": null
|
"selected": null
|
||||||
},
|
},
|
||||||
"table_state": {
|
"table": {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"selected": null
|
"selected": null
|
||||||
},
|
},
|
||||||
"scrollbar_state": {
|
"scrollbar": {
|
||||||
"content_length": 10,
|
"content_length": 10,
|
||||||
"position": 0,
|
"position": 0,
|
||||||
"viewport_content_length": 0
|
"viewport_content_length": 0
|
||||||
|
@ -126,15 +129,15 @@ const SELECTED_STATE_BUFFER: [&str; 5] = [
|
||||||
" Echo │ Echo ▼",
|
" Echo │ Echo ▼",
|
||||||
];
|
];
|
||||||
const SELECTED_STATE_REPR: &str = r#"{
|
const SELECTED_STATE_REPR: &str = r#"{
|
||||||
"list_state": {
|
"list": {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"selected": 1
|
"selected": 1
|
||||||
},
|
},
|
||||||
"table_state": {
|
"table": {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"selected": 1
|
"selected": 1
|
||||||
},
|
},
|
||||||
"scrollbar_state": {
|
"scrollbar": {
|
||||||
"content_length": 10,
|
"content_length": 10,
|
||||||
"position": 1,
|
"position": 1,
|
||||||
"viewport_content_length": 0
|
"viewport_content_length": 0
|
||||||
|
@ -169,15 +172,15 @@ const SCROLLED_STATE_BUFFER: [&str; 5] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const SCROLLED_STATE_REPR: &str = r#"{
|
const SCROLLED_STATE_REPR: &str = r#"{
|
||||||
"list_state": {
|
"list": {
|
||||||
"offset": 4,
|
"offset": 4,
|
||||||
"selected": 8
|
"selected": 8
|
||||||
},
|
},
|
||||||
"table_state": {
|
"table": {
|
||||||
"offset": 4,
|
"offset": 4,
|
||||||
"selected": 8
|
"selected": 8
|
||||||
},
|
},
|
||||||
"scrollbar_state": {
|
"scrollbar": {
|
||||||
"content_length": 10,
|
"content_length": 10,
|
||||||
"position": 8,
|
"position": 8,
|
||||||
"viewport_content_length": 0
|
"viewport_content_length": 0
|
||||||
|
|
|
@ -42,8 +42,8 @@ fn barchart_can_be_stylized() {
|
||||||
expected.get_mut(x, y).set_bg(Color::White);
|
expected.get_mut(x, y).set_bg(Color::White);
|
||||||
}
|
}
|
||||||
// bars
|
// bars
|
||||||
for x in [0, 1, 3, 4, 6, 7].iter() {
|
for x in [0, 1, 3, 4, 6, 7] {
|
||||||
expected.get_mut(*x, y).set_fg(Color::Red);
|
expected.get_mut(x, y).set_fg(Color::Red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// values
|
// values
|
||||||
|
|
|
@ -44,7 +44,7 @@ fn widgets_barchart_not_full_below_max_value() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_barchart_group() {
|
fn widgets_barchart_group() {
|
||||||
const TERMINAL_HEIGHT: u16 = 11u16;
|
const TERMINAL_HEIGHT: u16 = 11;
|
||||||
let test_case = |expected| {
|
let test_case = |expected| {
|
||||||
let backend = TestBackend::new(35, TERMINAL_HEIGHT);
|
let backend = TestBackend::new(35, TERMINAL_HEIGHT);
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
@ -67,9 +67,9 @@ fn widgets_barchart_group() {
|
||||||
.text_value("20M".to_string()),
|
.text_value("20M".to_string()),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.data(&vec![("C1", 50u64), ("C2", 40u64)])
|
.data(&vec![("C1", 50), ("C2", 40)])
|
||||||
.data(&[("C1", 60u64), ("C2", 90u64)])
|
.data(&[("C1", 60), ("C2", 90)])
|
||||||
.data(&[("xx", 10u64), ("xx", 10u64)])
|
.data(&[("xx", 10), ("xx", 10)])
|
||||||
.group_gap(2)
|
.group_gap(2)
|
||||||
.bar_width(4)
|
.bar_width(4)
|
||||||
.bar_gap(1);
|
.bar_gap(1);
|
||||||
|
|
|
@ -41,6 +41,7 @@ fn widgets_block_renders() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_block_titles_overlap() {
|
fn widgets_block_titles_overlap() {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_case(block: Block, area: Rect, expected: Buffer) {
|
fn test_case(block: Block, area: Rect, expected: Buffer) {
|
||||||
let backend = TestBackend::new(area.width, area.height);
|
let backend = TestBackend::new(area.width, area.height);
|
||||||
|
@ -94,6 +95,7 @@ fn widgets_block_titles_overlap() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_block_renders_on_small_areas() {
|
fn widgets_block_renders_on_small_areas() {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_case(block: Block, area: Rect, expected: Buffer) {
|
fn test_case(block: Block, area: Rect, expected: Buffer) {
|
||||||
let backend = TestBackend::new(area.width, area.height);
|
let backend = TestBackend::new(area.width, area.height);
|
||||||
|
@ -112,7 +114,7 @@ fn widgets_block_renders_on_small_areas() {
|
||||||
(Borders::BOTTOM, "T"),
|
(Borders::BOTTOM, "T"),
|
||||||
(Borders::ALL, "┌"),
|
(Borders::ALL, "┌"),
|
||||||
];
|
];
|
||||||
for (borders, symbol) in one_cell_test_cases.iter().cloned() {
|
for (borders, symbol) in one_cell_test_cases {
|
||||||
test_case(
|
test_case(
|
||||||
Block::default().title("Test").borders(borders),
|
Block::default().title("Test").borders(borders),
|
||||||
Rect::new(0, 0, 0, 0),
|
Rect::new(0, 0, 0, 0),
|
||||||
|
@ -182,8 +184,10 @@ fn widgets_block_renders_on_small_areas() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_block_title_alignment() {
|
fn widgets_block_title_alignment() {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_case(alignment: Alignment, borders: Borders, expected: Buffer) {
|
fn test_case(alignment: Alignment, borders: Borders, expected: Buffer) {
|
||||||
let backend = TestBackend::new(15, 3);
|
let backend = TestBackend::new(15, 3);
|
||||||
|
@ -374,8 +378,10 @@ fn widgets_block_title_alignment() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_block_title_alignment_bottom() {
|
fn widgets_block_title_alignment_bottom() {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_case(alignment: Alignment, borders: Borders, expected: Buffer) {
|
fn test_case(alignment: Alignment, borders: Borders, expected: Buffer) {
|
||||||
let backend = TestBackend::new(15, 3);
|
let backend = TestBackend::new(15, 3);
|
||||||
|
@ -558,8 +564,10 @@ fn widgets_block_title_alignment_bottom() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_block_multiple_titles() {
|
fn widgets_block_multiple_titles() {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_case(title_a: Title, title_b: Title, borders: Borders, expected: Buffer) {
|
fn test_case(title_a: Title, title_b: Title, borders: Borders, expected: Buffer) {
|
||||||
let backend = TestBackend::new(15, 3);
|
let backend = TestBackend::new(15, 3);
|
||||||
|
|
|
@ -12,14 +12,14 @@ use ratatui::{
|
||||||
use time::{Date, Month};
|
use time::{Date, Month};
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_render<W: Widget>(widget: W, expected: Buffer, size: (u16, u16)) {
|
fn test_render<W: Widget>(widget: W, expected: &Buffer, width: u16, height: u16) {
|
||||||
let backend = TestBackend::new(size.0, size.1);
|
let backend = TestBackend::new(width, height);
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
||||||
terminal
|
terminal
|
||||||
.draw(|f| f.render_widget(widget, f.size()))
|
.draw(|f| f.render_widget(widget, f.size()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
terminal.backend().assert_buffer(&expected);
|
terminal.backend().assert_buffer(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -35,7 +35,7 @@ fn days_layout() {
|
||||||
" 22 23 24 25 26 27 28",
|
" 22 23 24 25 26 27 28",
|
||||||
" 29 30 31",
|
" 29 30 31",
|
||||||
]);
|
]);
|
||||||
test_render(c, expected, (21, 5));
|
test_render(c, &expected, 21, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -53,7 +53,7 @@ fn days_layout_show_surrounding() {
|
||||||
" 24 25 26 27 28 29 30",
|
" 24 25 26 27 28 29 30",
|
||||||
" 31 1 2 3 4 5 6",
|
" 31 1 2 3 4 5 6",
|
||||||
]);
|
]);
|
||||||
test_render(c, expected, (21, 6));
|
test_render(c, &expected, 21, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -71,7 +71,7 @@ fn show_month_header() {
|
||||||
" 22 23 24 25 26 27 28",
|
" 22 23 24 25 26 27 28",
|
||||||
" 29 30 31",
|
" 29 30 31",
|
||||||
]);
|
]);
|
||||||
test_render(c, expected, (21, 6));
|
test_render(c, &expected, 21, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -89,7 +89,7 @@ fn show_weekdays_header() {
|
||||||
" 22 23 24 25 26 27 28",
|
" 22 23 24 25 26 27 28",
|
||||||
" 29 30 31",
|
" 29 30 31",
|
||||||
]);
|
]);
|
||||||
test_render(c, expected, (21, 6));
|
test_render(c, &expected, 21, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -110,5 +110,5 @@ fn show_combo() {
|
||||||
" 22 23 24 25 26 27 28",
|
" 22 23 24 25 26 27 28",
|
||||||
" 29 30 31 1 2 3 4",
|
" 29 30 31 1 2 3 4",
|
||||||
]);
|
]);
|
||||||
test_render(c, expected, (21, 7));
|
test_render(c, &expected, 21, 7);
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,6 +366,7 @@ fn widgets_chart_can_have_empty_datasets() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_chart_can_have_a_legend() {
|
fn widgets_chart_can_have_a_legend() {
|
||||||
let backend = TestBackend::new(60, 30);
|
let backend = TestBackend::new(60, 30);
|
||||||
|
|
|
@ -86,15 +86,15 @@ fn widgets_list_should_highlight_the_selected_item_wide_symbol() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_list_should_truncate_items() {
|
fn widgets_list_should_truncate_items() {
|
||||||
let backend = TestBackend::new(10, 2);
|
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
|
||||||
|
|
||||||
struct TruncateTestCase<'a> {
|
struct TruncateTestCase<'a> {
|
||||||
selected: Option<usize>,
|
selected: Option<usize>,
|
||||||
items: Vec<ListItem<'a>>,
|
items: Vec<ListItem<'a>>,
|
||||||
expected: Buffer,
|
expected: Buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let backend = TestBackend::new(10, 2);
|
||||||
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
// An item is selected
|
// An item is selected
|
||||||
TruncateTestCase {
|
TruncateTestCase {
|
||||||
|
@ -274,6 +274,7 @@ fn widget_list_should_not_ignore_empty_string_items() {
|
||||||
terminal.backend().assert_buffer(&expected);
|
terminal.backend().assert_buffer(&expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_list_enable_always_highlight_spacing() {
|
fn widgets_list_enable_always_highlight_spacing() {
|
||||||
let test_case = |state: &mut ListState, space: HighlightSpacing, expected: Buffer| {
|
let test_case = |state: &mut ListState, space: HighlightSpacing, expected: Buffer| {
|
||||||
|
|
|
@ -9,6 +9,7 @@ use ratatui::{
|
||||||
|
|
||||||
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
|
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
|
||||||
/// area and comparing the rendered and expected content.
|
/// area and comparing the rendered and expected content.
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn test_case(paragraph: Paragraph, expected: Buffer) {
|
fn test_case(paragraph: Paragraph, expected: Buffer) {
|
||||||
let backend = TestBackend::new(expected.area.width, expected.area.height);
|
let backend = TestBackend::new(expected.area.width, expected.area.height);
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
|
@ -202,6 +202,7 @@ fn widgets_table_columns_widths_can_use_fixed_length_constraints() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_table_columns_widths_can_use_percentage_constraints() {
|
fn widgets_table_columns_widths_can_use_percentage_constraints() {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_case(widths: &[Constraint], expected: Buffer) {
|
fn test_case(widths: &[Constraint], expected: Buffer) {
|
||||||
let backend = TestBackend::new(30, 10);
|
let backend = TestBackend::new(30, 10);
|
||||||
|
@ -311,6 +312,7 @@ fn widgets_table_columns_widths_can_use_percentage_constraints() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_table_columns_widths_can_use_mixed_constraints() {
|
fn widgets_table_columns_widths_can_use_mixed_constraints() {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_case(widths: &[Constraint], expected: Buffer) {
|
fn test_case(widths: &[Constraint], expected: Buffer) {
|
||||||
let backend = TestBackend::new(30, 10);
|
let backend = TestBackend::new(30, 10);
|
||||||
|
@ -423,6 +425,7 @@ fn widgets_table_columns_widths_can_use_mixed_constraints() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_table_columns_widths_can_use_ratio_constraints() {
|
fn widgets_table_columns_widths_can_use_ratio_constraints() {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_case(widths: &[Constraint], expected: Buffer) {
|
fn test_case(widths: &[Constraint], expected: Buffer) {
|
||||||
let backend = TestBackend::new(30, 10);
|
let backend = TestBackend::new(30, 10);
|
||||||
|
@ -626,6 +629,7 @@ fn widgets_table_can_have_rows_with_multi_lines() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
#[test]
|
#[test]
|
||||||
fn widgets_table_enable_always_highlight_spacing() {
|
fn widgets_table_enable_always_highlight_spacing() {
|
||||||
let test_case = |state: &mut TableState, space: HighlightSpacing, expected: Buffer| {
|
let test_case = |state: &mut TableState, space: HighlightSpacing, expected: Buffer| {
|
||||||
|
|
Loading…
Reference in a new issue