mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-25 06:00:34 +00:00
refactor(non-src): apply pedantic lints (#976)
Fixes many not yet enabled lints (mostly pedantic) on everything that is not the lib (examples, benchs, tests). Therefore, this is not containing anything that can be a breaking change. Lints are not enabled as that should be the job of #974. I created this as a separate PR as its mostly independent and would only clutter up the diff of #974 even more. Also see https://github.com/ratatui-org/ratatui/pull/974#discussion_r1506458743 --------- Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
This commit is contained in:
parent
94f4547dcf
commit
c12bcfefa2
52 changed files with 637 additions and 531 deletions
|
@ -8,7 +8,7 @@ use ratatui::{
|
|||
};
|
||||
|
||||
/// Benchmark for rendering a barchart.
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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.),
|
||||
|
|
|
@ -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(());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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} "),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<'_> {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>>;
|
||||
|
||||
|
|
|
@ -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(());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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| {
|
||||
|
|
Loading…
Reference in a new issue