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:
EdJoPaTo 2024-03-02 10:06:53 +01:00 committed by GitHub
parent 94f4547dcf
commit c12bcfefa2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 637 additions and 531 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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())

View file

@ -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);

View file

@ -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 {

View file

@ -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);
} }

View file

@ -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),
} }
} }

View file

@ -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));
} }
} }
}) })

View file

@ -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.),

View file

@ -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(());
} }
} }

View file

@ -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(())
} }

View file

@ -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(())
} }

View file

@ -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(())
} }

View file

@ -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),

View file

@ -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);

View file

@ -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};

View file

@ -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)]

View file

@ -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} "),
} }
} }
} }

View file

@ -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);

View file

@ -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 {

View file

@ -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)
} }

View file

@ -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(())
} }

View file

@ -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;

View file

@ -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

View file

@ -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);
} }

View file

@ -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()
}; };

View file

@ -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,

View file

@ -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

View file

@ -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(())
} }

View file

@ -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

View file

@ -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))

View file

@ -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}"),
} }
} }

View file

@ -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<'_> {

View file

@ -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());

View file

@ -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>>;

View file

@ -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(());
} }
} }

View file

@ -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 }
} }
} }

View file

@ -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(())
} }

View file

@ -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(());
} }
} }

View file

@ -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()

View file

@ -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(())
} }

View file

@ -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,
) );
} }
} }

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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);
} }

View file

@ -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);

View file

@ -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| {

View file

@ -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();

View file

@ -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| {