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.
pub fn barchart(c: &mut Criterion) {
fn barchart(c: &mut Criterion) {
let mut group = c.benchmark_group("barchart");
let mut rng = rand::thread_rng();
@ -66,7 +66,7 @@ fn render(bencher: &mut Bencher, barchart: &BarChart) {
bench_barchart.render(buffer.area, &mut buffer);
},
criterion::BatchSize::LargeInput,
)
);
}
criterion_group!(benches, barchart);

View file

@ -10,10 +10,10 @@ use ratatui::{
};
/// Benchmark for rendering a block.
pub fn block(c: &mut Criterion) {
fn block(c: &mut Criterion) {
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, 200, 50), // 1080p fullscreen with medium font
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`
fn render(bencher: &mut Bencher, block: &Block, size: &Rect) {
let mut buffer = Buffer::empty(*size);
fn render(bencher: &mut Bencher, block: &Block, size: Rect) {
let mut buffer = Buffer::empty(size);
// We use `iter_batched` to clone the value in the setup function.
// See https://github.com/ratatui-org/ratatui/pull/377.
bencher.iter_batched(
@ -57,7 +57,7 @@ fn render(bencher: &mut Bencher, block: &Block, size: &Rect) {
bench_block.render(buffer.area, &mut buffer);
},
BatchSize::SmallInput,
)
);
}
criterion_group!(benches, block);

View file

@ -7,7 +7,7 @@ use ratatui::{
/// Benchmark for rendering a list.
/// 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");
for line_count in [64, 2048, 16384] {
@ -33,7 +33,7 @@ pub fn list(c: &mut Criterion) {
ListState::default()
.with_offset(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);
},
BatchSize::LargeInput,
)
);
}
/// 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);
},
BatchSize::LargeInput,
)
);
}
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
/// 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.
pub fn paragraph(c: &mut Criterion) {
fn paragraph(c: &mut Criterion) {
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 = lines.as_str();
// benchmark that measures the overhead of creating a paragraph separately from rendering
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
@ -38,14 +38,14 @@ pub fn paragraph(c: &mut Criterion) {
// scroll the paragraph by half the number of lines and render
group.bench_with_input(
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),
);
// scroll the paragraph by the full number of lines and render
group.bench_with_input(
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),
);
@ -61,7 +61,7 @@ pub fn paragraph(c: &mut Criterion) {
BenchmarkId::new("render_wrap_scroll_full", line_count),
&Paragraph::new(lines)
.wrap(Wrap { trim: false })
.scroll((0u16, line_count)),
.scroll((0, line_count)),
|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);
},
BatchSize::LargeInput,
)
);
}
/// 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
/// this should emit around 200 characters per paragraph on average.
fn random_lines(count: u16) -> String {
let count = count as i64;
let count = i64::from(count);
let sentence_count = 3;
let word_count = 11;
fakeit::words::paragraph(count, sentence_count, word_count, "\n".into())

View file

@ -7,7 +7,7 @@ use ratatui::{
};
/// 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 rng = rand::thread_rng();
@ -38,7 +38,7 @@ fn render(bencher: &mut Bencher, sparkline: &Sparkline) {
bench_sparkline.render(buffer.area, &mut buffer);
},
criterion::BatchSize::LargeInput,
)
);
}
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.
//!
@ -24,7 +24,10 @@ use crossterm::{
execute,
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> {
revenue: [u64; 4],
@ -41,7 +44,7 @@ struct App<'a> {
const TOTAL_REVENUE: &str = "Total Revenue";
impl<'a> App<'a> {
fn new() -> App<'a> {
fn new() -> Self {
App {
data: vec![
("B1", 9),
@ -137,7 +140,7 @@ fn run_app<B: Backend>(
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if crossterm::event::poll(timeout)? {
if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
if key.code == KeyCode::Char('q') {
return Ok(());
}
}
@ -167,6 +170,7 @@ fn ui(frame: &mut Frame, app: &App) {
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>> {
app.months
.iter()
@ -206,7 +210,10 @@ fn create_groups<'a>(app: &'a App, combine_values_and_labels: bool) -> Vec<BarGr
.collect()
}
#[allow(clippy::cast_possible_truncation)]
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 mut barchart = BarChart::default()
@ -215,12 +222,11 @@ fn draw_bar_with_group_labels(f: &mut Frame, app: &App, area: Rect) {
.group_gap(3);
for group in groups {
barchart = barchart.data(group)
barchart = barchart.data(group);
}
f.render_widget(barchart, area);
const LEGEND_HEIGHT: u16 = 6;
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
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) {
const LEGEND_HEIGHT: u16 = 6;
let groups = create_groups(app, true);
let mut barchart = BarChart::default()
@ -244,12 +253,11 @@ fn draw_horizontal_bars(f: &mut Frame, app: &App, area: Rect) {
.direction(Direction::Horizontal);
for group in groups {
barchart = barchart.data(group)
barchart = barchart.data(group);
}
f.render_widget(barchart, area);
const LEGEND_HEIGHT: u16 = 6;
if area.height >= LEGEND_HEIGHT && area.width >= TOTAL_REVENUE.len() as u16 + 2 {
let legend_width = TOTAL_REVENUE.len() as u16 + 2;
let legend_area = Rect {

View file

@ -77,7 +77,7 @@ fn run(terminal: &mut Terminal) -> Result<()> {
fn handle_events() -> Result<ControlFlow<()>> {
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
if key.code == KeyCode::Char('q') {
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) {
let block = Block::new()
.borders(border)
.title(format!("Borders::{border:#?}", border = border));
.title(format!("Borders::{border:#?}"));
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 readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
#![allow(clippy::wildcard_imports)]
use std::{error::Error, io};
use crossterm::{
@ -166,15 +168,13 @@ fn make_dates(current_year: i32) -> CalendarEventStore {
mod cals {
use super::*;
pub(super) fn get_cal<'a, DS: DateStyler>(m: Month, y: i32, es: DS) -> Monthly<'a, DS> {
use Month::*;
pub fn get_cal<'a, DS: DateStyler>(m: Month, y: i32, es: DS) -> Monthly<'a, DS> {
match m {
May => example1(m, y, es),
June => example2(m, y, es),
July => example3(m, y, es),
December => example3(m, y, es),
February => example4(m, y, es),
November => example5(m, y, es),
Month::May => example1(m, y, es),
Month::June => example2(m, y, es),
Month::July | Month::December => example3(m, y, es),
Month::February => example4(m, y, es),
Month::November => example5(m, y, es),
_ => default(m, y, es),
}
}

View file

@ -13,6 +13,8 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
#![allow(clippy::wildcard_imports)]
use std::{
io::{self, stdout, Stdout},
time::{Duration, Instant},
@ -44,8 +46,8 @@ struct App {
}
impl App {
fn new() -> App {
App {
fn new() -> Self {
Self {
x: 0.0,
y: 0.0,
ball: Circle {
@ -64,7 +66,7 @@ impl App {
pub fn run() -> io::Result<()> {
let mut terminal = init_terminal()?;
let mut app = App::new();
let mut app = Self::new();
let mut last_tick = Instant::now();
let tick_rate = Duration::from_millis(16);
loop {
@ -106,13 +108,13 @@ impl App {
// bounce the ball by flipping the velocity vector
let ball = &self.ball;
let playground = self.playground;
if ball.x - ball.radius < playground.left() as f64
|| ball.x + ball.radius > playground.right() as f64
if ball.x - ball.radius < f64::from(playground.left())
|| ball.x + ball.radius > f64::from(playground.right())
{
self.vx = -self.vx;
}
if ball.y - ball.radius < playground.top() as f64
|| ball.y + ball.radius > playground.bottom() as f64
if ball.y - ball.radius < f64::from(playground.top())
|| ball.y + ball.radius > f64::from(playground.bottom())
{
self.vy = -self.vy;
}
@ -160,8 +162,10 @@ impl App {
}
fn boxes_canvas(&self, area: Rect) -> impl Widget {
let (left, right, bottom, top) =
(0.0, area.width as f64, 0.0, area.height as f64 * 2.0 - 4.0);
let left = 0.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()
.block(Block::default().borders(Borders::ALL).title("Rects"))
.marker(self.marker)
@ -170,26 +174,26 @@ impl App {
.paint(|ctx| {
for i in 0..=11 {
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,
width: i as f64,
height: i as f64,
width: f64::from(i),
height: f64::from(i),
color: Color::Red,
});
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,
width: i as f64,
height: i as f64,
width: f64::from(i),
height: f64::from(i),
color: Color::Blue,
});
}
for i in 0..100 {
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 {
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::{
prelude::*,
widgets::{block::Title, *},
widgets::{block::Title, Axis, Block, Borders, Chart, Dataset, GraphType, LegendPosition},
};
#[derive(Clone)]
pub struct SinSignal {
struct SinSignal {
x: f64,
interval: f64,
period: f64,
@ -38,8 +38,8 @@ pub struct SinSignal {
}
impl SinSignal {
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
SinSignal {
const fn new(interval: f64, period: f64, scale: f64) -> Self {
Self {
x: 0.0,
interval,
period,
@ -66,12 +66,12 @@ struct App {
}
impl App {
fn new() -> App {
fn new() -> Self {
let mut signal1 = SinSignal::new(0.2, 3.0, 18.0);
let mut signal2 = SinSignal::new(0.1, 2.0, 10.0);
let data1 = signal1.by_ref().take(200).collect::<Vec<(f64, f64)>>();
let data2 = signal2.by_ref().take(200).collect::<Vec<(f64, f64)>>();
App {
Self {
signal1,
data1,
signal2,
@ -133,7 +133,7 @@ fn run_app<B: Backend>(
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if crossterm::event::poll(timeout)? {
if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
if key.code == KeyCode::Char('q') {
return Ok(());
}
}
@ -242,7 +242,7 @@ fn render_line_chart(f: &mut Frame, area: Rect) {
.legend_position(Some(LegendPosition::TopLeft))
.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) {
@ -310,7 +310,7 @@ const HEAVY_PAYLOAD_DATA: [(f64, f64); 9] = [
const MEDIUM_PAYLOAD_DATA: [(f64, f64); 29] = [
(1963., 29500.),
(1964., 30600.),
(1965., 177900.),
(1965., 177_900.),
(1965., 21000.),
(1966., 17900.),
(1966., 8400.),
@ -340,7 +340,7 @@ const MEDIUM_PAYLOAD_DATA: [(f64, f64); 29] = [
];
const SMALL_PAYLOAD_DATA: [(f64, f64); 23] = [
(1961., 118500.),
(1961., 118_500.),
(1962., 14900.),
(1975., 21400.),
(1980., 32800.),

View file

@ -13,8 +13,9 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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
/// and background colors with their names and indexes.
// This example shows all the colors supported by ratatui. It will render a grid of foreground
// and background colors with their names and indexes.
use std::{
error::Error,
io::{self, Stdout},
@ -28,7 +29,10 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use ratatui::{
prelude::*,
widgets::{Block, Borders, Paragraph},
};
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 let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
if key.code == KeyCode::Char('q') {
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.
//!
@ -13,18 +13,19 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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.
///
/// 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
/// 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
/// 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
/// useful when the state is only used by the widget and doesn't need to be shared with other
/// widgets.
// 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.
//
// 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
// 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.
//
// This is an alternative to using the `StatefulWidget` trait and a separate state struct. It
// is useful when the state is only used by the widget and doesn't need to be shared with
// other widgets.
use std::{
io::stdout,
panic,
@ -110,7 +111,7 @@ impl App {
Ok(())
}
fn is_running(&self) -> bool {
const fn is_running(&self) -> bool {
matches!(self.state, AppState::Running)
}
@ -140,6 +141,7 @@ impl App {
/// to update the colors to render.
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
#[allow(clippy::enum_glob_use)]
use Constraint::*;
let [top, colors] = Layout::vertical([Length(1), Min(0)]).areas(area);
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.
impl Default for FpsWidget {
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
/// calculation while rendering.
@ -173,7 +175,7 @@ impl Widget for &mut FpsWidget {
fn render(self, area: Rect, buf: &mut Buffer) {
self.calculate_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);
}
}
@ -185,6 +187,7 @@ impl FpsWidget {
/// 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
/// machines that can't render at least 2 frames per second.
#[allow(clippy::cast_precision_loss)]
fn calculate_fps(&mut self) {
self.frame_count += 1;
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
/// 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
/// they don't need to be recalculated every frame.
#[allow(clippy::cast_precision_loss)]
fn setup_colors(&mut self, size: Rect) {
let Rect { width, height, .. } = size;
// 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.
fn install_error_hooks() -> Result<()> {
@ -266,7 +270,7 @@ fn install_error_hooks() -> Result<()> {
}))?;
panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
panic(info)
panic(info);
}));
Ok(())
}

View file

@ -13,6 +13,8 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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 color_eyre::{config::HookBuilder, Result};
@ -27,7 +29,7 @@ use ratatui::{
prelude::*,
style::palette::tailwind::*,
symbols::line,
widgets::*,
widgets::{Block, Paragraph, Wrap},
};
use strum::{Display, EnumIter, FromRepr};
@ -67,9 +69,9 @@ enum ConstraintName {
/// └──────────────┘
/// ```
struct ConstraintBlock {
selected: bool,
legend: bool,
constraint: Constraint,
legend: bool,
selected: bool,
}
/// A widget that renders a spacer with a label indicating the width of the spacer. E.g.:
@ -146,32 +148,31 @@ impl App {
Ok(())
}
/// select the next block with wrap around
fn increment_value(&mut self) {
if self.constraints.is_empty() {
let Some(constraint) = self.constraints.get_mut(self.selected_index) else {
return;
}
self.constraints[self.selected_index] = match self.constraints[self.selected_index] {
Constraint::Length(v) => Constraint::Length(v.saturating_add(1)),
Constraint::Min(v) => Constraint::Min(v.saturating_add(1)),
Constraint::Max(v) => Constraint::Max(v.saturating_add(1)),
Constraint::Fill(v) => Constraint::Fill(v.saturating_add(1)),
Constraint::Percentage(v) => Constraint::Percentage(v.saturating_add(1)),
Constraint::Ratio(n, d) => Constraint::Ratio(n, d.saturating_add(1)),
};
match constraint {
Constraint::Length(v)
| Constraint::Min(v)
| Constraint::Max(v)
| Constraint::Fill(v)
| Constraint::Percentage(v) => *v = v.saturating_add(1),
Constraint::Ratio(_n, d) => *d = d.saturating_add(1),
};
}
fn decrement_value(&mut self) {
if self.constraints.is_empty() {
let Some(constraint) = self.constraints.get_mut(self.selected_index) else {
return;
}
self.constraints[self.selected_index] = match self.constraints[self.selected_index] {
Constraint::Length(v) => Constraint::Length(v.saturating_sub(1)),
Constraint::Min(v) => Constraint::Min(v.saturating_sub(1)),
Constraint::Max(v) => Constraint::Max(v.saturating_sub(1)),
Constraint::Fill(v) => Constraint::Fill(v.saturating_sub(1)),
Constraint::Percentage(v) => Constraint::Percentage(v.saturating_sub(1)),
Constraint::Ratio(n, d) => Constraint::Ratio(n, d.saturating_sub(1)),
};
match constraint {
Constraint::Length(v)
| Constraint::Min(v)
| Constraint::Max(v)
| Constraint::Fill(v)
| Constraint::Percentage(v) => *v = v.saturating_sub(1),
Constraint::Ratio(_n, d) => *d = d.saturating_sub(1),
};
}
@ -222,7 +223,7 @@ impl App {
}
fn exit(&mut self) {
self.mode = AppMode::Quit
self.mode = AppMode::Quit;
}
fn swap_constraint(&mut self, name: ConstraintName) {
@ -235,7 +236,7 @@ impl App {
ConstraintName::Min => Min(self.value),
ConstraintName::Max => Max(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;
}
@ -243,14 +244,13 @@ impl App {
impl From<Constraint> for ConstraintName {
fn from(constraint: Constraint) -> Self {
use Constraint::*;
match constraint {
Length(_) => ConstraintName::Length,
Percentage(_) => ConstraintName::Percentage,
Ratio(_, _) => ConstraintName::Ratio,
Min(_) => ConstraintName::Min,
Max(_) => ConstraintName::Max,
Fill(_) => ConstraintName::Fill,
Length(_) => Self::Length,
Percentage(_) => Self::Percentage,
Ratio(_, _) => Self::Ratio,
Min(_) => Self::Min,
Max(_) => Self::Max,
Fill(_) => Self::Fill,
}
}
}
@ -267,9 +267,9 @@ impl Widget for &App {
])
.areas(area);
self.header().render(header_area, buf);
self.instructions().render(instructions_area, buf);
self.swap_legend().render(swap_legend_area, buf);
App::header().render(header_area, buf);
App::instructions().render(instructions_area, buf);
App::swap_legend().render(swap_legend_area, buf);
self.render_layout_blocks(blocks_area, buf);
}
}
@ -280,12 +280,12 @@ impl App {
const TEXT_COLOR: Color = SLATE.c400;
const AXIS_COLOR: Color = SLATE.c500;
fn header(&self) -> impl Widget {
fn header() -> impl Widget {
let text = "Constraint Explorer";
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";
Paragraph::new(text)
.fg(Self::TEXT_COLOR)
@ -293,7 +293,7 @@ impl App {
.wrap(Wrap { trim: false })
}
fn swap_legend(&self) -> impl Widget {
fn swap_legend() -> impl Widget {
#[allow(unstable_name_collisions)]
Paragraph::new(
Line::from(
@ -327,7 +327,7 @@ impl App {
let label = if self.spacing != 0 {
format!("{} px (gap: {} px)", width, self.spacing)
} else {
format!("{} px", width)
format!("{width} px")
};
let bar_width = width.saturating_sub(2) as usize; // we want to `<` and `>` at the ends
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::End, end, 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) {
@ -371,7 +371,7 @@ impl App {
Layout::vertical([Length(1), Max(1), Length(4)]).areas(area);
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);
@ -405,17 +405,17 @@ impl Widget for ConstraintBlock {
impl ConstraintBlock {
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 {
constraint,
selected,
legend,
selected,
}
}
fn label(&self, width: u16) -> String {
let long_width = format!("{} px", width);
let short_width = format!("{}", width);
let long_width = format!("{width} px");
let short_width = format!("{width}");
// border takes up 2 columns
let available_space = width.saturating_sub(2) as usize;
let width_label = if long_width.len() < available_space {
@ -423,7 +423,7 @@ impl ConstraintBlock {
} else if short_width.len() < available_space {
short_width
} else {
"".to_string()
String::new()
};
format!("{}\n{}", self.constraint, width_label)
}
@ -499,9 +499,9 @@ impl Widget for SpacerBlock {
fn render(self, area: Rect, buf: &mut Buffer) {
match area.height {
1 => (),
2 => self.render_2px(area, buf),
3 => self.render_3px(area, buf),
_ => self.render_4px(area, buf),
2 => Self::render_2px(area, buf),
3 => Self::render_3px(area, buf),
_ => Self::render_4px(area, buf),
}
}
}
@ -541,7 +541,7 @@ impl SpacerBlock {
/// A label that says "Spacer" if there is enough space
fn spacer_label(width: u16) -> impl Widget {
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
@ -553,12 +553,12 @@ impl SpacerBlock {
} else if short_label.len() < width as usize {
short_label
} else {
"".to_string()
String::new()
};
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 {
Self::block().render(area, buf);
} 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 {
Self::block().render(area, buf);
} else {
@ -577,7 +577,7 @@ impl SpacerBlock {
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 {
Self::block().render(area, buf);
} else {
@ -593,7 +593,7 @@ impl SpacerBlock {
}
impl ConstraintName {
fn color(&self) -> Color {
const fn color(self) -> Color {
match self {
Self::Length => SLATE.c700,
Self::Percentage => SLATE.c800,
@ -604,7 +604,7 @@ impl ConstraintName {
}
}
fn lighter_color(&self) -> Color {
const fn lighter_color(self) -> Color {
match self {
Self::Length => STONE.c500,
Self::Percentage => STONE.c600,
@ -626,7 +626,7 @@ fn init_error_hooks() -> Result<()> {
}))?;
std::panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
panic(info)
panic(info);
}));
Ok(())
}

View file

@ -13,6 +13,8 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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 color_eyre::{config::HookBuilder, Result};
@ -95,7 +97,7 @@ impl App {
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
}
@ -138,14 +140,14 @@ impl App {
}
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) {
self.scroll_offset = self
.scroll_offset
.saturating_add(1)
.min(self.max_scroll_offset)
.min(self.max_scroll_offset);
}
fn top(&mut self) {
@ -159,17 +161,16 @@ impl App {
impl Widget for App {
fn render(self, area: Rect, buf: &mut Buffer) {
let [tabs, axis, demo] =
Layout::vertical([Constraint::Length(3), Constraint::Length(3), Fill(0)]).areas(area);
let [tabs, axis, demo] = Layout::vertical([Length(3), Length(3), Fill(0)]).areas(area);
self.render_tabs(tabs, buf);
self.render_axis(axis, buf);
Self::render_axis(axis, buf);
self.render_demo(demo, buf);
}
}
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 block = Block::new()
.title("Constraints ".bold())
@ -183,10 +184,10 @@ impl App {
.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;
// a bar like `<----- 80 px ----->`
let width_label = format!("{} px", width);
let width_label = format!("{width} px");
let width_bar = format!(
"<{width_label:-^width$}>",
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
/// 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
// area.height to make sure the last example is fully visible even when the scroll offset is
// at the max
@ -246,41 +248,40 @@ impl App {
impl SelectedTab {
/// Get the previous tab, if there is no previous tab return the current tab.
fn previous(&self) -> Self {
let current_index: usize = *self as usize;
fn previous(self) -> Self {
let current_index: usize = self as usize;
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.
fn next(&self) -> Self {
let current_index = *self as usize;
fn next(self) -> Self {
let current_index = self as usize;
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 {
use SelectedTab::*;
const fn get_example_count(self) -> u16 {
#[allow(clippy::match_same_arms)]
match self {
Length => 4,
Percentage => 5,
Ratio => 4,
Fill => 2,
Min => 5,
Max => 5,
Self::Length => 4,
Self::Percentage => 5,
Self::Ratio => 4,
Self::Fill => 2,
Self::Min => 5,
Self::Max => 5,
}
}
fn to_tab_title(value: SelectedTab) -> Line<'static> {
use SelectedTab::*;
fn to_tab_title(value: Self) -> Line<'static> {
let text = format!(" {value} ");
let color = match value {
Length => LENGTH_COLOR,
Percentage => PERCENTAGE_COLOR,
Ratio => RATIO_COLOR,
Fill => FILL_COLOR,
Min => MIN_COLOR,
Max => MAX_COLOR,
Self::Length => LENGTH_COLOR,
Self::Percentage => PERCENTAGE_COLOR,
Self::Ratio => RATIO_COLOR,
Self::Fill => FILL_COLOR,
Self::Min => MIN_COLOR,
Self::Max => MAX_COLOR,
};
text.fg(tailwind::SLATE.c200).bg(color).into()
}
@ -289,18 +290,18 @@ impl SelectedTab {
impl Widget for SelectedTab {
fn render(self, area: Rect, buf: &mut Buffer) {
match self {
SelectedTab::Length => self.render_length_example(area, buf),
SelectedTab::Percentage => self.render_percentage_example(area, buf),
SelectedTab::Ratio => self.render_ratio_example(area, buf),
SelectedTab::Fill => self.render_fill_example(area, buf),
SelectedTab::Min => self.render_min_example(area, buf),
SelectedTab::Max => self.render_max_example(area, buf),
Self::Length => Self::render_length_example(area, buf),
Self::Percentage => Self::render_percentage_example(area, buf),
Self::Ratio => Self::render_ratio_example(area, buf),
Self::Fill => Self::render_fill_example(area, buf),
Self::Min => Self::render_min_example(area, buf),
Self::Max => Self::render_max_example(area, buf),
}
}
}
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, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 4]).areas(area);
@ -309,7 +310,7 @@ impl SelectedTab {
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, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
@ -320,7 +321,7 @@ impl SelectedTab {
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, _] =
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);
}
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);
Example::new(&[Fill(1), Fill(2), Fill(3)]).render(example1, 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, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
@ -348,7 +349,7 @@ impl SelectedTab {
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, _] =
Layout::vertical([Length(EXAMPLE_HEIGHT); 6]).areas(area);
@ -379,14 +380,13 @@ impl Widget for Example {
let blocks = Layout::horizontal(&self.constraints).split(area);
for (block, constraint) in blocks.iter().zip(&self.constraints) {
self.illustration(*constraint, block.width)
.render(*block, buf);
Self::illustration(*constraint, block.width).render(*block, buf);
}
}
}
impl Example {
fn illustration(&self, constraint: Constraint, width: u16) -> Paragraph {
fn illustration(constraint: Constraint, width: u16) -> impl Widget {
let color = match constraint {
Constraint::Length(_) => LENGTH_COLOR,
Constraint::Percentage(_) => PERCENTAGE_COLOR,
@ -417,7 +417,7 @@ fn init_error_hooks() -> Result<()> {
}))?;
std::panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
panic(info)
panic(info);
}));
Ok(())
}

View file

@ -23,7 +23,7 @@ use crossterm::{
execute,
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.
#[derive(Debug, Clone)]
@ -71,7 +71,7 @@ const GREEN: Theme = Theme {
/// A button with a label that can be themed.
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 {
label: label.into(),
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
}
pub fn state(mut self, state: State) -> Button<'a> {
pub const fn state(mut self, state: State) -> Self {
self.state = state;
self
}
}
impl<'a> Widget for Button<'a> {
#[allow(clippy::cast_possible_truncation)]
fn render(self, area: Rect, buf: &mut Buffer) {
let (background, text, shadow, highlight) = self.colors();
buf.set_style(area, Style::new().bg(background).fg(text));
@ -124,7 +125,7 @@ impl<'a> Widget for Button<'a> {
}
impl Button<'_> {
fn colors(&self) -> (Color, Color, Color, Color) {
const fn colors(&self) -> (Color, Color, Color, Color) {
let theme = self.theme;
match self.state {
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<()> {
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 {
terminal.draw(|frame| ui(frame, button_states))?;
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 {
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;
}
}
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(())
}
fn ui(frame: &mut Frame, states: &[State; 3]) {
fn ui(frame: &mut Frame, states: [State; 3]) {
let vertical = Layout::vertical([
Constraint::Length(1),
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);
}
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([
Constraint::Length(15),
Constraint::Length(15),

View file

@ -2,7 +2,7 @@ use rand::{
distributions::{Distribution, Uniform},
rngs::ThreadRng,
};
use ratatui::widgets::*;
use ratatui::widgets::ListState;
const TASKS: [&str; 24] = [
"Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10",
@ -73,8 +73,8 @@ pub struct RandomSignal {
}
impl RandomSignal {
pub fn new(lower: u64, upper: u64) -> RandomSignal {
RandomSignal {
pub fn new(lower: u64, upper: u64) -> Self {
Self {
distribution: Uniform::new(lower, upper),
rng: rand::thread_rng(),
}
@ -97,8 +97,8 @@ pub struct SinSignal {
}
impl SinSignal {
pub fn new(interval: f64, period: f64, scale: f64) -> SinSignal {
SinSignal {
pub const fn new(interval: f64, period: f64, scale: f64) -> Self {
Self {
x: 0.0,
interval,
period,
@ -144,8 +144,8 @@ pub struct StatefulList<T> {
}
impl<T> StatefulList<T> {
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
StatefulList {
pub fn with_items(items: Vec<T>) -> Self {
Self {
state: ListState::default(),
items,
}
@ -235,7 +235,7 @@ pub struct 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 sparkline_points = rand_signal.by_ref().take(300).collect();
let mut sin_signal = SinSignal::new(0.2, 3.0, 18.0);

View file

@ -4,7 +4,10 @@ use std::{
};
use ratatui::prelude::*;
use termwiz::{input::*, terminal::Terminal as TermwizTerminal};
use termwiz::{
input::{InputEvent, KeyCode},
terminal::Terminal as TermwizTerminal,
};
use crate::{app::App, ui};

View file

@ -1,3 +1,4 @@
#[allow(clippy::wildcard_imports)]
use ratatui::{
prelude::*,
widgets::{canvas::*, *},
@ -85,6 +86,7 @@ fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
f.render_widget(line_gauge, chunks[2]);
}
#[allow(clippy::too_many_lines)]
fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
let constraints = if app.show_chart {
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);
match term::next_event(timeout)? {
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::*;
match key.code {
Char('q') | Esc => self.mode = Mode::Quit,
@ -93,10 +94,8 @@ impl App {
Char('k') | Up => self.prev(),
Char('j') | Down => self.next(),
Char('d') | Delete => self.destroy(),
_ => {}
};
Ok(())
}
fn prev(&mut self) {
@ -124,11 +123,11 @@ impl App {
}
fn next_tab(&mut self) {
self.tab = self.tab.next()
self.tab = self.tab.next();
}
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);
self.render_title_bar(title_bar, 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);
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)
.style(THEME.tabs)
.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 = [
("H/←", "Left"),
("L/→", "Right"),
@ -189,8 +188,8 @@ impl App {
let spans = keys
.iter()
.flat_map(|(key, desc)| {
let key = Span::styled(format!(" {} ", key), THEME.key_binding.key);
let desc = Span::styled(format!(" {} ", desc), THEME.key_binding.description);
let key = Span::styled(format!(" {key} "), THEME.key_binding.key);
let desc = Span::styled(format!(" {desc} "), THEME.key_binding.description);
[key, desc]
})
.collect_vec();
@ -202,22 +201,22 @@ impl App {
}
impl Tab {
fn next(&self) -> Self {
let current_index = *self as usize;
fn next(self) -> Self {
let current_index = self as usize;
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 {
let current_index = *self as usize;
fn prev(self) -> Self {
let current_index = self as usize;
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 {
Tab::About => "".to_string(),
tab => format!(" {} ", tab),
Self::About => String::new(),
tab => format!(" {tab} "),
}
}
}

View file

@ -136,17 +136,17 @@ pub struct BigText<'a> {
impl Widget for BigText<'_> {
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 (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
fn cells_per_glyph(size: &PixelSize) -> (u16, u16) {
const fn cells_per_glyph(size: PixelSize) -> (u16, u16) {
match size {
PixelSize::Full => (8, 8),
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
fn layout(
area: Rect,
pixel_size: &PixelSize,
pixel_size: PixelSize,
) -> impl IntoIterator<Item = impl IntoIterator<Item = Rect>> {
let (width, height) = cells_per_glyph(pixel_size);
(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
/// `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);
let c = grapheme.symbol.chars().next().unwrap(); // TODO: handle multi-char graphemes
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"
fn get_symbol_half_height(top: u8, bottom: u8) -> char {
const fn get_symbol_half_height(top: u8, bottom: u8) -> char {
match top {
0 => match bottom {
0 => ' ',
@ -201,7 +201,7 @@ fn get_symbol_half_height(top: u8, bottom: u8) -> char {
}
/// 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 {
0 => match right {
0 => ' ',
@ -215,20 +215,26 @@ fn get_symbol_half_width(left: u8, right: u8) -> char {
}
/// 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 {
let top_left = if top_left > 0 { 1 } else { 0 };
let top_right = if top_right > 0 { 1 } else { 0 };
let bottom_left = if bottom_left > 0 { 1 } else { 0 };
let bottom_right = if bottom_right > 0 { 1 } else { 0 };
const fn get_symbol_half_size(
top_left: u8,
top_right: u8,
bottom_left: u8,
bottom_right: u8,
) -> char {
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.
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 glyph_vertical_index = (0..glyph.len()).step_by(8 / height as usize);

View file

@ -9,13 +9,14 @@ use ratatui::prelude::*;
pub struct RgbSwatch;
impl Widget for RgbSwatch {
#[allow(clippy::cast_precision_loss, clippy::similar_names)]
fn render(self, area: Rect, buf: &mut Buffer) {
for (yi, y) in (area.top()..area.bottom()).enumerate() {
let value = area.height as f32 - yi as f32;
let value_fg = value / (area.height as f32);
let value_bg = (value - 0.5) / (area.height as f32);
let value = f32::from(area.height) - yi as f32;
let value_fg = value / f32::from(area.height);
let value_bg = (value - 0.5) / f32::from(area.height);
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 bg = color_from_oklab(hue, Okhsv::max_saturation(), value_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.
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
/// 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) {
// 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 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 pixel_count = (frame_count as f64 * variable_speed).floor() as usize;
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
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
fn text(frame_count: usize, area: Rect, buf: &mut Buffer) {
let sub_frame = frame_count.saturating_sub(TEXT_DELAY);
if sub_frame == 0 {
@ -114,10 +120,13 @@ fn blend(mask_color: Color, cell_color: Color, percentage: f64) -> Color {
return mask_color;
};
let red = mask_red as f64 * percentage + cell_red as f64 * (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 remain = 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)
}

View file

@ -12,7 +12,7 @@ pub fn init_hooks() -> Result<()> {
}))?;
std::panic::set_hook(Box::new(move |info| {
let _ = term::restore();
panic(info)
panic(info);
}));
Ok(())
}

View file

@ -13,6 +13,14 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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 big_text;
mod colors;

View file

@ -97,10 +97,11 @@ fn render_crate_description(area: Rect, buf: &mut Buffer) {
.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
/// 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) {
let eye_color = if selected_row % 2 == 0 {
THEME.logo.rat_eye

View file

@ -10,6 +10,7 @@ struct Ingredient {
}
impl Ingredient {
#[allow(clippy::cast_possible_truncation)]
fn height(&self) -> 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) {
let mut state = TableState::default().with_selected(Some(selected_row));
let rows = INGREDIENTS.iter().cloned();
let rows = INGREDIENTS.iter().copied();
let theme = THEME.recipe;
StatefulWidget::render(
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)
.track_symbol(None)
.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
// 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)
.text_value(format!("{}°", value))
.text_value(format!("{value}°"))
.style(if value > 70 {
Style::new().fg(Color::Red)
} else {
@ -128,12 +128,14 @@ fn render_horizontal_barchart(area: Rect, buf: &mut Buffer) {
.render(area, buf);
}
#[allow(clippy::cast_precision_loss)]
pub fn render_gauge(progress: usize, area: Rect, buf: &mut Buffer) {
let percent = (progress * 3).min(100) as f64;
render_line_gauge(percent, area, buf);
}
#[allow(clippy::cast_possible_truncation)]
fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
// cycle color hue based on the percent for a neat effect yellow -> red
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 bg = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5);
let label = if percent < 100.0 {
format!("Downloading: {}%", percent)
format!("Downloading: {percent}%")
} else {
"Download Complete!".into()
};

View file

@ -19,7 +19,10 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use ratatui::{prelude::*, widgets::*};
use ratatui::{
prelude::*,
widgets::{Block, Borders, Paragraph},
};
/// Example code for lib.rs
///
@ -34,7 +37,6 @@ fn main() -> io::Result<()> {
let mut should_quit = false;
while !should_quit {
terminal.draw(match arg.as_str() {
"hello_world" => hello_world,
"layout" => layout,
"styling" => styling,
_ => hello_world,

View file

@ -13,6 +13,8 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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 color_eyre::{config::HookBuilder, Result};
@ -165,7 +167,7 @@ impl App {
Ok(())
}
fn is_running(&self) -> bool {
fn is_running(self) -> bool {
self.state == AppState::Running
}
@ -203,14 +205,14 @@ impl App {
}
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) {
self.scroll_offset = self
.scroll_offset
.saturating_add(1)
.min(max_scroll_offset())
.min(max_scroll_offset());
}
fn top(&mut self) {
@ -239,8 +241,7 @@ fn max_scroll_offset() -> u16 {
example_height()
- EXAMPLE_DATA
.last()
.map(|(desc, _)| get_description_height(desc) + 4)
.unwrap_or(0)
.map_or(0, |(desc, _)| get_description_height(desc) + 4)
}
/// The height of all examples combined
@ -264,12 +265,12 @@ impl Widget for App {
} else {
axis.width
};
self.axis(axis_width, self.spacing).render(axis, buf);
Self::axis(axis_width, self.spacing).render(axis, buf);
}
}
impl App {
fn tabs(&self) -> impl Widget {
fn tabs(self) -> impl Widget {
let tab_titles = SelectedTab::iter().map(SelectedTab::to_tab_title);
let block = Block::new()
.title(Title::from("Flex Layouts ".bold()))
@ -283,13 +284,13 @@ impl App {
}
/// 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;
// only show gap when spacing is not zero
let label = if spacing != 0 {
format!("{} px (gap: {} px)", width, spacing)
format!("{width} px (gap: {spacing} px)")
} else {
format!("{} px", width)
format!("{width} px")
};
let bar_width = width.saturating_sub(2); // we want to `<` and `>` at the ends
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.
///
/// Returns bool indicating whether scroll was needed
#[allow(clippy::cast_possible_truncation)]
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
// 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 {
/// Get the previous tab, if there is no previous tab return the current tab.
fn previous(&self) -> Self {
let current_index: usize = *self as usize;
fn previous(self) -> Self {
let current_index: usize = self as usize;
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.
fn next(&self) -> Self {
let current_index = *self as usize;
fn next(self) -> Self {
let current_index = self as usize;
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.
fn to_tab_title(value: SelectedTab) -> Line<'static> {
fn to_tab_title(value: Self) -> Line<'static> {
use tailwind::*;
use SelectedTab::*;
let text = value.to_string();
let color = match value {
Legacy => ORANGE.c400,
Start => SKY.c400,
Center => SKY.c300,
End => SKY.c200,
SpaceAround => INDIGO.c400,
SpaceBetween => INDIGO.c300,
Self::Legacy => ORANGE.c400,
Self::Start => SKY.c400,
Self::Center => SKY.c300,
Self::End => SKY.c200,
Self::SpaceAround => INDIGO.c400,
Self::SpaceBetween => INDIGO.c300,
};
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) {
let spacing = *spacing;
match self {
SelectedTab::Legacy => self.render_examples(area, buf, Flex::Legacy, spacing),
SelectedTab::Start => self.render_examples(area, buf, Flex::Start, spacing),
SelectedTab::Center => self.render_examples(area, buf, Flex::Center, spacing),
SelectedTab::End => self.render_examples(area, buf, Flex::End, spacing),
SelectedTab::SpaceAround => self.render_examples(area, buf, Flex::SpaceAround, spacing),
SelectedTab::SpaceBetween => {
self.render_examples(area, buf, Flex::SpaceBetween, spacing)
}
Self::Legacy => Self::render_examples(area, buf, Flex::Legacy, spacing),
Self::Start => Self::render_examples(area, buf, Flex::Start, spacing),
Self::Center => Self::render_examples(area, buf, Flex::Center, spacing),
Self::End => Self::render_examples(area, buf, Flex::End, spacing),
Self::SpaceAround => Self::render_examples(area, buf, Flex::SpaceAround, spacing),
Self::SpaceBetween => Self::render_examples(area, buf, Flex::SpaceBetween, spacing),
}
}
}
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
.iter()
.map(|(desc, _)| get_description_height(desc) + 4);
@ -432,7 +431,7 @@ impl Widget for Example {
Paragraph::new(
self.description
.split('\n')
.map(|s| format!("// {}", s).italic().fg(tailwind::SLATE.c400))
.map(|s| format!("// {s}").italic().fg(tailwind::SLATE.c400))
.map(Line::from)
.collect::<Vec<Line>>(),
)
@ -440,18 +439,17 @@ impl Widget for Example {
}
for (block, constraint) in blocks.iter().zip(&self.constraints) {
self.illustration(*constraint, block.width)
.render(*block, buf);
Self::illustration(*constraint, block.width).render(*block, buf);
}
for spacer in spacers.iter() {
self.render_spacer(*spacer, buf);
Self::render_spacer(*spacer, buf);
}
}
}
impl Example {
fn render_spacer(&self, spacer: Rect, buf: &mut Buffer) {
fn render_spacer(spacer: Rect, buf: &mut Buffer) {
if spacer.width > 1 {
let corners_only = symbols::border::Set {
top_left: line::NORMAL.top_left,
@ -483,7 +481,7 @@ impl Example {
} else if width > 2 {
format!("{width}")
} else {
"".to_string()
String::new()
};
let text = Text::from(vec![
Line::raw(""),
@ -496,7 +494,7 @@ impl Example {
.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 fg_color = Color::White;
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::*;
match constraint {
Constraint::Min(_) => BLUE.c900,
@ -532,7 +530,7 @@ fn init_error_hooks() -> Result<()> {
}))?;
std::panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
panic(info)
panic(info);
}));
Ok(())
}
@ -551,6 +549,7 @@ fn restore_terminal() -> Result<()> {
Ok(())
}
#[allow(clippy::cast_possible_truncation)]
fn get_description_height(s: &str) -> u16 {
if s.is_empty() {
0

View file

@ -13,6 +13,8 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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 color_eyre::{config::HookBuilder, Result};
@ -24,7 +26,7 @@ use crossterm::{
use ratatui::{
prelude::*,
style::palette::tailwind,
widgets::{block::Title, *},
widgets::{block::Title, Block, Borders, Gauge, Padding, Paragraph},
};
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.
self.progress_columns = (self.progress_columns + 1).clamp(0, 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
// gauges measuring the same thing.
@ -119,6 +121,7 @@ impl App {
}
impl Widget for &App {
#[allow(clippy::similar_names)]
fn render(self, area: Rect, buf: &mut Buffer) {
use Constraint::*;
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 [gauge1_area, gauge2_area, gauge3_area, gauge4_area] = layout.areas(gauge_area);
self.render_header(header_area, buf);
self.render_footer(footer_area, buf);
render_header(header_area, buf);
render_footer(footer_area, buf);
self.render_gauge1(gauge1_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 {
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) {
let title = title_block("Gauge with percentage");
Gauge::default()
@ -220,7 +223,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
}))?;
std::panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
panic(info)
panic(info);
}));
Ok(())
}

View file

@ -24,7 +24,7 @@ use crossterm::{
execute,
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 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 readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
#![allow(clippy::wildcard_imports)]
use std::{
collections::{BTreeMap, VecDeque},
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> {
(0..4)
.map(|id| {
@ -168,6 +171,7 @@ fn downloads() -> Downloads {
}
}
#[allow(clippy::needless_pass_by_value)]
fn run_app<B: Backend>(
terminal: &mut Terminal<B>,
workers: Vec<Worker>,
@ -194,7 +198,7 @@ fn run_app<B: Backend>(
Event::DownloadUpdate(worker_id, _download_id, progress) => {
let download = downloads.in_progress.get_mut(&worker_id).unwrap();
download.progress = progress;
redraw = false
redraw = false;
}
Event::DownloadDone(worker_id, download_id) => {
let download = downloads.in_progress.remove(&worker_id).unwrap();
@ -242,6 +246,7 @@ fn ui(f: &mut Frame, downloads: &Downloads) {
// total progress
let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
#[allow(clippy::cast_precision_loss)]
let progress = LineGauge::default()
.gauge_style(Style::default().fg(Color::Blue))
.label(format!("{done}/{NUM_DOWNLOADS}"))
@ -271,6 +276,7 @@ fn ui(f: &mut Frame, downloads: &Downloads) {
let list = List::new(items);
f.render_widget(list, list_area);
#[allow(clippy::cast_possible_truncation)]
for (i, (_, download)) in downloads.in_progress.iter().enumerate() {
let gauge = Gauge::default()
.gauge_style(Style::default().fg(Color::Yellow))

View file

@ -13,6 +13,8 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
#![allow(clippy::enum_glob_use)]
use std::{error::Error, io};
use crossterm::{
@ -21,7 +23,11 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use itertools::Itertools;
use ratatui::{layout::Constraint::*, prelude::*, widgets::*};
use ratatui::{
layout::Constraint::*,
prelude::*,
widgets::{Block, Borders, Paragraph},
};
fn main() -> Result<(), Box<dyn Error>> {
// setup terminal
@ -55,13 +61,14 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
terminal.draw(ui)?;
if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
if key.code == KeyCode::Char('q') {
return Ok(());
}
}
}
}
#[allow(clippy::too_many_lines)]
fn ui(frame: &mut Frame) {
let vertical = Layout::vertical([
Length(4), // text
@ -94,12 +101,12 @@ fn ui(frame: &mut Frame) {
.iter()
.flat_map(|area| {
Layout::horizontal([
Constraint::Length(14),
Constraint::Length(14),
Constraint::Length(14),
Constraint::Length(14),
Constraint::Length(14),
Constraint::Min(0), // fills remaining space
Length(14),
Length(14),
Length(14),
Length(14),
Length(14),
Min(0), // fills remaining space
])
.split(*area)
.iter()
@ -191,8 +198,8 @@ fn render_example_combination(
let inner = block.inner(area);
frame.render_widget(block, area);
let layout = Layout::vertical(vec![Length(1); constraints.len() + 1]).split(inner);
for (i, (a, b)) in constraints.iter().enumerate() {
render_single_example(frame, layout[i], vec![*a, *b, Min(0)]);
for (i, (a, b)) in constraints.into_iter().enumerate() {
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
// 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 {
match constraint {
Length(n) => format!("{n}"),
Min(n) => format!("{n}"),
Max(n) => format!("{n}"),
Percentage(n) => format!("{n}"),
Fill(n) => format!("{n}"),
Ratio(a, b) => format!("{a}:{b}"),
Constraint::Ratio(a, b) => format!("{a}:{b}"),
Constraint::Length(n)
| Constraint::Min(n)
| Constraint::Max(n)
| Constraint::Percentage(n)
| Constraint::Fill(n) => format!("{n}"),
}
}

View file

@ -13,6 +13,8 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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 color_eyre::config::HookBuilder;
@ -81,7 +83,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
}))?;
std::panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
panic(info)
panic(info);
}));
Ok(())
}
@ -100,9 +102,9 @@ fn restore_terminal() -> color_eyre::Result<()> {
Ok(())
}
impl App<'_> {
fn new<'a>() -> App<'a> {
App {
impl<'a> App<'a> {
fn new() -> Self {
Self {
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 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) {
self.items.state.select(Some(0))
self.items.state.select(Some(0));
}
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 [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_info(lower_item_list_area, buf);
self.render_footer(footer_area, buf);
render_footer(footer_area, buf);
}
}
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) {
// We create two blocks, one is for the header (outer) and the other is for list (inner).
let outer_block = Block::default()
@ -278,14 +273,19 @@ impl App<'_> {
// We can now render the item info
info_paragraph.render(inner_info_area, buf);
}
}
fn render_footer(&self, area: Rect, buf: &mut Buffer) {
Paragraph::new(
"\nUse ↓↑ to move, ← to unselect, → to change status, g/G to go top/bottom.",
)
fn render_title(area: Rect, buf: &mut Buffer) {
Paragraph::new("Ratatui List Example")
.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()
.render(area, buf);
}
}
impl StatefulList<'_> {

View file

@ -13,9 +13,10 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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.
/// It will render a grid of combinations of foreground and background colors with all
/// modifiers applied to them.
// 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
// modifiers applied to them.
use std::{
error::Error,
io::{self, Stdout},
@ -30,7 +31,7 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use ratatui::{prelude::*, widgets::Paragraph};
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 let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
if key.code == KeyCode::Char('q') {
return Ok(());
}
}
@ -87,8 +88,8 @@ fn ui(frame: &mut Frame) {
.chain(Modifier::all().iter())
.collect_vec();
let mut index = 0;
for bg in colors.iter() {
for fg in colors.iter() {
for bg in &colors {
for fg in &colors {
for modifier in &all_modifiers {
let modifier_name = format!("{modifier:11?}");
let padding = (" ").repeat(12 - modifier_name.len());

View file

@ -35,7 +35,10 @@ use crossterm::{
event::{self, Event, KeyCode},
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>>;

View file

@ -24,15 +24,18 @@ use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{prelude::*, widgets::*};
use ratatui::{
prelude::*,
widgets::{Block, Borders, Paragraph, Wrap},
};
struct App {
scroll: u16,
}
impl App {
fn new() -> App {
App { scroll: 0 }
const fn new() -> Self {
Self { scroll: 0 }
}
fn on_tick(&mut self) {
@ -82,7 +85,7 @@ fn run_app<B: Backend>(
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if crossterm::event::poll(timeout)? {
if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
if key.code == KeyCode::Char('q') {
return Ok(());
}
}

View file

@ -13,8 +13,9 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
/// See also https://github.com/joshka/tui-popup and
/// https://github.com/sephiroth74/tui-confirm-dialog
// See also https://github.com/joshka/tui-popup and
// https://github.com/sephiroth74/tui-confirm-dialog
use std::{error::Error, io};
use crossterm::{
@ -22,15 +23,18 @@ use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{prelude::*, widgets::*};
use ratatui::{
prelude::*,
widgets::{Block, Borders, Clear, Paragraph, Wrap},
};
struct App {
show_popup: bool,
}
impl App {
fn new() -> App {
App { show_popup: false }
const fn new() -> Self {
Self { show_popup: false }
}
}

View file

@ -22,49 +22,46 @@ use std::{
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use indoc::indoc;
use itertools::izip;
use ratatui::{prelude::*, widgets::*};
use ratatui::{prelude::*, widgets::Paragraph};
/// 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! {"
"}
.lines();
"};
let a = indoc! {"
"}
.lines();
"};
let t = indoc! {"
"}
.lines();
"};
let u = indoc! {"
"}
.lines();
"};
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()?;
terminal.draw(|frame| {
let logo = izip!(r, a.clone(), t.clone(), a, t, u, i)
.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());
frame.render_widget(Paragraph::new(logo()), frame.size());
})?;
sleep(Duration::from_secs(5));
restore()?;
@ -72,7 +69,7 @@ fn main() -> io::Result<()> {
Ok(())
}
pub fn init() -> io::Result<Terminal<impl Backend>> {
fn init() -> io::Result<Terminal<impl Backend>> {
enable_raw_mode()?;
let options = TerminalOptions {
viewport: Viewport::Inline(3),
@ -80,7 +77,7 @@ pub fn init() -> io::Result<Terminal<impl Backend>> {
Terminal::with_options(CrosstermBackend::new(stdout()), options)
}
pub fn restore() -> io::Result<()> {
fn restore() -> io::Result<()> {
disable_raw_mode()?;
Ok(())
}

View file

@ -28,17 +28,20 @@ use rand::{
distributions::{Distribution, Uniform},
rngs::ThreadRng,
};
use ratatui::{prelude::*, widgets::*};
use ratatui::{
prelude::*,
widgets::{Block, Borders, Sparkline},
};
#[derive(Clone)]
pub struct RandomSignal {
struct RandomSignal {
distribution: Uniform<u64>,
rng: ThreadRng,
}
impl RandomSignal {
pub fn new(lower: u64, upper: u64) -> RandomSignal {
RandomSignal {
fn new(lower: u64, upper: u64) -> Self {
Self {
distribution: Uniform::new(lower, upper),
rng: rand::thread_rng(),
}
@ -60,12 +63,12 @@ struct App {
}
impl App {
fn new() -> App {
fn new() -> Self {
let mut signal = RandomSignal::new(0, 100);
let data1 = 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>>();
App {
Self {
signal,
data1,
data2,
@ -127,7 +130,7 @@ fn run_app<B: Backend>(
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if crossterm::event::poll(timeout)? {
if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
if key.code == KeyCode::Char('q') {
return Ok(());
}
}

View file

@ -13,6 +13,8 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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 crossterm::{
@ -48,7 +50,7 @@ struct TableColors {
}
impl TableColors {
fn new(color: &tailwind::Palette) -> Self {
const fn new(color: &tailwind::Palette) -> Self {
Self {
buffer_bg: tailwind::SLATE.c950,
header_bg: color.c900,
@ -69,7 +71,7 @@ struct Data {
}
impl Data {
fn ref_array(&self) -> [&String; 3] {
const fn ref_array(&self) -> [&String; 3] {
[&self.name, &self.address, &self.email]
}
@ -96,9 +98,9 @@ struct App {
}
impl App {
fn new() -> App {
fn new() -> Self {
let data_vec = generate_fake_names();
App {
Self {
state: TableState::default().with_selected(0),
longest_item_lens: constraint_len_calculator(&data_vec),
scroll_state: ScrollbarState::new((data_vec.len() - 1) * ITEM_HEIGHT),
@ -147,7 +149,7 @@ impl App {
}
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);
let header = ["Name", "Address", "Email"]
.iter()
.cloned()
.into_iter()
.map(Cell::from)
.collect::<Row>()
.style(header_style)
@ -257,9 +258,8 @@ fn render_table(f: &mut Frame, app: &mut App, area: Rect) {
_ => app.colors.alt_row_color,
};
let item = data.ref_array();
item.iter()
.cloned()
.map(|content| Cell::from(Text::from(format!("\n{}\n", content))))
item.into_iter()
.map(|content| Cell::from(Text::from(format!("\n{content}\n"))))
.collect::<Row>()
.style(Style::new().fg(app.colors.row_fg).bg(color))
.height(4)
@ -308,6 +308,7 @@ fn constraint_len_calculator(items: &[Data]) -> (u16, u16, u16) {
.max()
.unwrap_or(0);
#[allow(clippy::cast_possible_truncation)]
(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))
.style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg))
.centered()

View file

@ -13,7 +13,9 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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 crossterm::{
@ -72,7 +74,7 @@ impl App {
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 key.kind == KeyEventKind::Press {
use KeyCode::*;
@ -102,17 +104,17 @@ impl App {
impl SelectedTab {
/// Get the previous tab, if there is no previous tab return the current tab.
fn previous(&self) -> Self {
let current_index: usize = *self as usize;
fn previous(self) -> Self {
let current_index: usize = self as usize;
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.
fn next(&self) -> Self {
let current_index = *self as usize;
fn next(self) -> Self {
let current_index = self as usize;
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 [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.selected_tab.render(inner_area, buf);
self.render_footer(footer_area, buf);
render_footer(footer_area, buf);
}
}
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) {
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 selected_tab_index = self.selected_tab as usize;
Tabs::new(titles)
@ -148,61 +146,65 @@ impl App {
.divider(" ")
.render(area, buf);
}
}
fn render_footer(&self, area: Rect, buf: &mut Buffer) {
Line::raw("◄ ► to change tab | Press q to quit")
.centered()
.render(area, buf);
}
fn render_title(area: Rect, buf: &mut Buffer) {
"Ratatui Tabs Example".bold().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 {
fn render(self, area: Rect, buf: &mut Buffer) {
// in a real app these might be separate widgets
match self {
SelectedTab::Tab1 => self.render_tab0(area, buf),
SelectedTab::Tab2 => self.render_tab1(area, buf),
SelectedTab::Tab3 => self.render_tab2(area, buf),
SelectedTab::Tab4 => self.render_tab3(area, buf),
Self::Tab1 => self.render_tab0(area, buf),
Self::Tab2 => self.render_tab1(area, buf),
Self::Tab3 => self.render_tab2(area, buf),
Self::Tab4 => self.render_tab3(area, buf),
}
}
}
impl SelectedTab {
/// Return tab's name as a styled `Line`
fn title(&self) -> Line<'static> {
fn title(self) -> Line<'static> {
format!(" {self} ")
.fg(tailwind::SLATE.c200)
.bg(self.palette().c900)
.into()
}
fn render_tab0(&self, area: Rect, buf: &mut Buffer) {
fn render_tab0(self, area: Rect, buf: &mut Buffer) {
Paragraph::new("Hello, World!")
.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!")
.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!")
.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.")
.block(self.block())
.render(area, buf)
.render(area, buf);
}
/// A block surrounding the tab's content
fn block(&self) -> Block<'static> {
fn block(self) -> Block<'static> {
Block::default()
.borders(Borders::ALL)
.border_set(symbols::border::PROPORTIONAL_TALL)
@ -210,12 +212,12 @@ impl SelectedTab {
.border_style(self.palette().c700)
}
fn palette(&self) -> tailwind::Palette {
const fn palette(self) -> tailwind::Palette {
match self {
SelectedTab::Tab1 => tailwind::BLUE,
SelectedTab::Tab2 => tailwind::EMERALD,
SelectedTab::Tab3 => tailwind::INDIGO,
SelectedTab::Tab4 => tailwind::RED,
Self::Tab1 => tailwind::BLUE,
Self::Tab2 => tailwind::EMERALD,
Self::Tab3 => tailwind::INDIGO,
Self::Tab4 => tailwind::RED,
}
}
}
@ -230,7 +232,7 @@ fn init_error_hooks() -> color_eyre::Result<()> {
}))?;
std::panic::set_hook(Box::new(move |info| {
let _ = restore_terminal();
panic(info)
panic(info);
}));
Ok(())
}

View file

@ -13,28 +13,31 @@
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [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};
/// 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::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{prelude::*, widgets::*};
use ratatui::{
prelude::*,
widgets::{Block, Borders, List, ListItem, Paragraph},
};
enum InputMode {
Normal,
@ -53,18 +56,16 @@ struct App {
messages: Vec<String>,
}
impl Default for App {
fn default() -> App {
App {
impl App {
const fn new() -> Self {
Self {
input: String::new(),
input_mode: InputMode::Normal,
messages: Vec::new(),
cursor_position: 0,
}
}
}
impl App {
fn move_cursor_left(&mut self) {
let cursor_moved_left = self.cursor_position.saturating_sub(1);
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)?;
// create app and run it
let app = App::default();
let app = App::new();
let res = run_app(&mut terminal, app);
// 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 => {
// Make the cursor visible and ask ratatui to put it at the specified coordinates after
// rendering
#[allow(clippy::cast_possible_truncation)]
f.set_cursor(
// Draw the cursor at the current position in the input field.
// This position is can be controlled via the left and right arrow key
input_area.x + app.cursor_position as u16 + 1,
// Move one line down, from the border to the input line
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)]
struct AppState {
list_state: ListState,
table_state: TableState,
scrollbar_state: ScrollbarState,
list: ListState,
table: TableState,
scrollbar: ScrollbarState,
}
impl Default for AppState {
fn default() -> Self {
Self {
list_state: ListState::default(),
table_state: TableState::default(),
scrollbar_state: ScrollbarState::new(10),
list: ListState::default(),
table: TableState::default(),
scrollbar: ScrollbarState::new(10),
}
}
}
impl AppState {
fn select(&mut self, index: usize) {
self.list_state.select(Some(index));
self.table_state.select(Some(index));
self.scrollbar_state = self.scrollbar_state.position(index);
self.list.select(Some(index));
self.table.select(Some(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]
fn assert_buffer(state: &mut AppState, expected: &Buffer) {
let backend = TestBackend::new(21, 5);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let items = vec![
let items = [
"awa", "banana", "Cats!!", "d20", "Echo", "Foxtrot", "Golf", "Hotel", "IwI",
"Juliett",
];
use Constraint::*;
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints([Length(10), Length(10), Length(1)])
.constraints([
Constraint::Length(10),
Constraint::Length(10),
Constraint::Length(1),
])
.split(f.size());
let list = List::new(items.clone())
let list = List::new(items)
.highlight_symbol(">>")
.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(
items.iter().map(|i| Row::new(vec![*i])),
items.into_iter().map(|i| Row::new(vec![i])),
[Constraint::Length(10); 1],
)
.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);
f.render_stateful_widget(scrollbar, layout[2], &mut state.scrollbar_state);
f.render_stateful_widget(scrollbar, layout[2], &mut state.scrollbar);
})
.unwrap();
terminal.backend().assert_buffer(expected);
@ -85,15 +88,15 @@ const DEFAULT_STATE_BUFFER: [&str; 5] = [
];
const DEFAULT_STATE_REPR: &str = r#"{
"list_state": {
"list": {
"offset": 0,
"selected": null
},
"table_state": {
"table": {
"offset": 0,
"selected": null
},
"scrollbar_state": {
"scrollbar": {
"content_length": 10,
"position": 0,
"viewport_content_length": 0
@ -126,15 +129,15 @@ const SELECTED_STATE_BUFFER: [&str; 5] = [
" Echo │ Echo ▼",
];
const SELECTED_STATE_REPR: &str = r#"{
"list_state": {
"list": {
"offset": 0,
"selected": 1
},
"table_state": {
"table": {
"offset": 0,
"selected": 1
},
"scrollbar_state": {
"scrollbar": {
"content_length": 10,
"position": 1,
"viewport_content_length": 0
@ -169,15 +172,15 @@ const SCROLLED_STATE_BUFFER: [&str; 5] = [
];
const SCROLLED_STATE_REPR: &str = r#"{
"list_state": {
"list": {
"offset": 4,
"selected": 8
},
"table_state": {
"table": {
"offset": 4,
"selected": 8
},
"scrollbar_state": {
"scrollbar": {
"content_length": 10,
"position": 8,
"viewport_content_length": 0

View file

@ -42,8 +42,8 @@ fn barchart_can_be_stylized() {
expected.get_mut(x, y).set_bg(Color::White);
}
// bars
for x in [0, 1, 3, 4, 6, 7].iter() {
expected.get_mut(*x, y).set_fg(Color::Red);
for x in [0, 1, 3, 4, 6, 7] {
expected.get_mut(x, y).set_fg(Color::Red);
}
}
// values

View file

@ -44,7 +44,7 @@ fn widgets_barchart_not_full_below_max_value() {
#[test]
fn widgets_barchart_group() {
const TERMINAL_HEIGHT: u16 = 11u16;
const TERMINAL_HEIGHT: u16 = 11;
let test_case = |expected| {
let backend = TestBackend::new(35, TERMINAL_HEIGHT);
let mut terminal = Terminal::new(backend).unwrap();
@ -67,9 +67,9 @@ fn widgets_barchart_group() {
.text_value("20M".to_string()),
]),
)
.data(&vec![("C1", 50u64), ("C2", 40u64)])
.data(&[("C1", 60u64), ("C2", 90u64)])
.data(&[("xx", 10u64), ("xx", 10u64)])
.data(&vec![("C1", 50), ("C2", 40)])
.data(&[("C1", 60), ("C2", 90)])
.data(&[("xx", 10), ("xx", 10)])
.group_gap(2)
.bar_width(4)
.bar_gap(1);

View file

@ -41,6 +41,7 @@ fn widgets_block_renders() {
#[test]
fn widgets_block_titles_overlap() {
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test_case(block: Block, area: Rect, expected: Buffer) {
let backend = TestBackend::new(area.width, area.height);
@ -94,6 +95,7 @@ fn widgets_block_titles_overlap() {
#[test]
fn widgets_block_renders_on_small_areas() {
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test_case(block: Block, area: Rect, expected: Buffer) {
let backend = TestBackend::new(area.width, area.height);
@ -112,7 +114,7 @@ fn widgets_block_renders_on_small_areas() {
(Borders::BOTTOM, "T"),
(Borders::ALL, ""),
];
for (borders, symbol) in one_cell_test_cases.iter().cloned() {
for (borders, symbol) in one_cell_test_cases {
test_case(
Block::default().title("Test").borders(borders),
Rect::new(0, 0, 0, 0),
@ -182,8 +184,10 @@ fn widgets_block_renders_on_small_areas() {
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn widgets_block_title_alignment() {
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test_case(alignment: Alignment, borders: Borders, expected: Buffer) {
let backend = TestBackend::new(15, 3);
@ -374,8 +378,10 @@ fn widgets_block_title_alignment() {
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn widgets_block_title_alignment_bottom() {
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test_case(alignment: Alignment, borders: Borders, expected: Buffer) {
let backend = TestBackend::new(15, 3);
@ -558,8 +564,10 @@ fn widgets_block_title_alignment_bottom() {
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn widgets_block_multiple_titles() {
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test_case(title_a: Title, title_b: Title, borders: Borders, expected: Buffer) {
let backend = TestBackend::new(15, 3);

View file

@ -12,14 +12,14 @@ use ratatui::{
use time::{Date, Month};
#[track_caller]
fn test_render<W: Widget>(widget: W, expected: Buffer, size: (u16, u16)) {
let backend = TestBackend::new(size.0, size.1);
fn test_render<W: Widget>(widget: W, expected: &Buffer, width: u16, height: u16) {
let backend = TestBackend::new(width, height);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| f.render_widget(widget, f.size()))
.unwrap();
terminal.backend().assert_buffer(&expected);
terminal.backend().assert_buffer(expected);
}
#[test]
@ -35,7 +35,7 @@ fn days_layout() {
" 22 23 24 25 26 27 28",
" 29 30 31",
]);
test_render(c, expected, (21, 5));
test_render(c, &expected, 21, 5);
}
#[test]
@ -53,7 +53,7 @@ fn days_layout_show_surrounding() {
" 24 25 26 27 28 29 30",
" 31 1 2 3 4 5 6",
]);
test_render(c, expected, (21, 6));
test_render(c, &expected, 21, 6);
}
#[test]
@ -71,7 +71,7 @@ fn show_month_header() {
" 22 23 24 25 26 27 28",
" 29 30 31",
]);
test_render(c, expected, (21, 6));
test_render(c, &expected, 21, 6);
}
#[test]
@ -89,7 +89,7 @@ fn show_weekdays_header() {
" 22 23 24 25 26 27 28",
" 29 30 31",
]);
test_render(c, expected, (21, 6));
test_render(c, &expected, 21, 6);
}
#[test]
@ -110,5 +110,5 @@ fn show_combo() {
" 22 23 24 25 26 27 28",
" 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();
}
#[allow(clippy::too_many_lines)]
#[test]
fn widgets_chart_can_have_a_legend() {
let backend = TestBackend::new(60, 30);

View file

@ -86,15 +86,15 @@ fn widgets_list_should_highlight_the_selected_item_wide_symbol() {
#[test]
fn widgets_list_should_truncate_items() {
let backend = TestBackend::new(10, 2);
let mut terminal = Terminal::new(backend).unwrap();
struct TruncateTestCase<'a> {
selected: Option<usize>,
items: Vec<ListItem<'a>>,
expected: Buffer,
}
let backend = TestBackend::new(10, 2);
let mut terminal = Terminal::new(backend).unwrap();
let cases = vec![
// An item is selected
TruncateTestCase {
@ -274,6 +274,7 @@ fn widget_list_should_not_ignore_empty_string_items() {
terminal.backend().assert_buffer(&expected);
}
#[allow(clippy::too_many_lines)]
#[test]
fn widgets_list_enable_always_highlight_spacing() {
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
/// area and comparing the rendered and expected content.
#[allow(clippy::needless_pass_by_value)]
fn test_case(paragraph: Paragraph, expected: Buffer) {
let backend = TestBackend::new(expected.area.width, expected.area.height);
let mut terminal = Terminal::new(backend).unwrap();

View file

@ -202,6 +202,7 @@ fn widgets_table_columns_widths_can_use_fixed_length_constraints() {
#[test]
fn widgets_table_columns_widths_can_use_percentage_constraints() {
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test_case(widths: &[Constraint], expected: Buffer) {
let backend = TestBackend::new(30, 10);
@ -311,6 +312,7 @@ fn widgets_table_columns_widths_can_use_percentage_constraints() {
#[test]
fn widgets_table_columns_widths_can_use_mixed_constraints() {
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test_case(widths: &[Constraint], expected: Buffer) {
let backend = TestBackend::new(30, 10);
@ -423,6 +425,7 @@ fn widgets_table_columns_widths_can_use_mixed_constraints() {
#[test]
fn widgets_table_columns_widths_can_use_ratio_constraints() {
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test_case(widths: &[Constraint], expected: Buffer) {
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]
fn widgets_table_enable_always_highlight_spacing() {
let test_case = |state: &mut TableState, space: HighlightSpacing, expected: Buffer| {