Compare commits
14 Commits
v0.28.0-al
...
jm/crosste
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37be39b15c | ||
|
|
e7a4fd8fc3 | ||
|
|
50eb504be9 | ||
|
|
8ed4a2b5a4 | ||
|
|
706bdd6116 | ||
|
|
cd525c3356 | ||
|
|
0c96035c34 | ||
|
|
ce40f4e315 | ||
|
|
b2baa9d0c9 | ||
|
|
1d8bfdf7c7 | ||
|
|
def85b7401 | ||
|
|
1d176d9307 | ||
|
|
bf43eff17c | ||
|
|
09e263b4ae |
@@ -28,6 +28,7 @@ rust-version = "1.74.0"
|
||||
bitflags = "2.3"
|
||||
cassowary = "0.3"
|
||||
compact_str = "0.7.1"
|
||||
color-eyre = { version = "0.6.2", optional = true }
|
||||
crossterm = { version = "0.27", optional = true }
|
||||
document-features = { version = "0.2.7", optional = true }
|
||||
itertools = "0.13"
|
||||
@@ -106,7 +107,7 @@ use_self = "warn"
|
||||
## By default, we enable the crossterm backend as this is a reasonable choice for most applications
|
||||
## as it is supported on Linux/Mac/Windows systems. We also enable the `underline-color` feature
|
||||
## which allows you to set the underline color of text.
|
||||
default = ["crossterm", "underline-color"]
|
||||
default = ["crossterm", "underline-color", "color-eyre"]
|
||||
#! Generally an application will only use one backend, so you should only enable one of the following features:
|
||||
## enables the [`CrosstermBackend`](backend::CrosstermBackend) backend and adds a dependency on [`crossterm`].
|
||||
crossterm = ["dep:crossterm"]
|
||||
@@ -116,6 +117,12 @@ termion = ["dep:termion"]
|
||||
termwiz = ["dep:termwiz"]
|
||||
|
||||
#! The following optional features are available for all backends:
|
||||
|
||||
## enables the [`color-eyre`](color_eyre) crate which provides a better error handling experience.
|
||||
## See [`CrosstermBackend::with_color_eyre_hooks`](crate::backend::CrosstermBackend::with_color_eyre_hooks)
|
||||
## for more details.
|
||||
color-eyre = ["dep:color-eyre"]
|
||||
|
||||
## enables serialization and deserialization of style and color types using the [`serde`] crate.
|
||||
## This is useful if you want to save themes to a file.
|
||||
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
|
||||
|
||||
@@ -13,22 +13,15 @@
|
||||
//! [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::{
|
||||
error::Error,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
text::{Line, Span},
|
||||
widgets::{Bar, BarChart, BarGroup, Block, Paragraph},
|
||||
};
|
||||
@@ -103,40 +96,13 @@ impl<'a> App<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
|
||||
// create app and run it
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
let app = App::new();
|
||||
let res = run_app(&mut terminal, app, tick_rate);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> io::Result<()> {
|
||||
let mut app = App::new();
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &app))?;
|
||||
|
||||
@@ -13,21 +13,11 @@
|
||||
//! [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::{
|
||||
error::Error,
|
||||
io::{stdout, Stdout},
|
||||
ops::ControlFlow,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{Style, Stylize},
|
||||
terminal::Frame,
|
||||
@@ -38,55 +28,16 @@ use ratatui::{
|
||||
},
|
||||
};
|
||||
|
||||
// These type aliases are used to make the code more readable by reducing repetition of the generic
|
||||
// types. They are not necessary for the functionality of the code.
|
||||
type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
|
||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = setup_terminal()?;
|
||||
let result = run(&mut terminal);
|
||||
restore_terminal(terminal)?;
|
||||
|
||||
if let Err(err) = result {
|
||||
eprintln!("{err:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_terminal() -> Result<Terminal> {
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal(mut terminal: Terminal) -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(terminal: &mut Terminal) -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
loop {
|
||||
terminal.draw(ui)?;
|
||||
if handle_events()?.is_break() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_events() -> Result<ControlFlow<()>> {
|
||||
if event::poll(Duration::from_millis(100))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(ControlFlow::Break(()));
|
||||
if let Event::Key(event) = event::read()? {
|
||||
if event.kind == KeyEventKind::Press && event.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
|
||||
fn ui(frame: &mut Frame) {
|
||||
|
||||
@@ -13,50 +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
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::KeyEventKind;
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
widgets::calendar::{CalendarEventStore, DateStyler, Monthly},
|
||||
Frame, Terminal,
|
||||
Frame,
|
||||
};
|
||||
use time::{Date, Month, OffsetDateTime};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
loop {
|
||||
let _ = terminal.draw(draw);
|
||||
|
||||
terminal.draw(ui)?;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
#[allow(clippy::single_match)]
|
||||
match key.code {
|
||||
KeyCode::Char(_) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
disable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||
terminal.show_cursor()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(frame: &mut Frame) {
|
||||
fn ui(frame: &mut Frame) {
|
||||
let app_area = frame.size();
|
||||
|
||||
let calarea = Rect {
|
||||
|
||||
@@ -13,30 +13,26 @@
|
||||
//! [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::{self, stdout, Stdout},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Stylize},
|
||||
symbols::Marker,
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
widgets::{
|
||||
canvas::{Canvas, Circle, Map, MapResolution, Rectangle},
|
||||
Block, Widget,
|
||||
},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
App::run()
|
||||
fn main() -> Result<()> {
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::new().run(terminal)
|
||||
}
|
||||
|
||||
struct App {
|
||||
@@ -69,33 +65,31 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() -> io::Result<()> {
|
||||
let mut terminal = init_terminal()?;
|
||||
let mut app = Self::new();
|
||||
pub fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
let mut last_tick = Instant::now();
|
||||
let tick_rate = Duration::from_millis(16);
|
||||
loop {
|
||||
let _ = terminal.draw(|frame| app.ui(frame));
|
||||
let _ = terminal.draw(|frame| self.ui(frame));
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => break,
|
||||
KeyCode::Down | KeyCode::Char('j') => app.y += 1.0,
|
||||
KeyCode::Up | KeyCode::Char('k') => app.y -= 1.0,
|
||||
KeyCode::Right | KeyCode::Char('l') => app.x += 1.0,
|
||||
KeyCode::Left | KeyCode::Char('h') => app.x -= 1.0,
|
||||
KeyCode::Down | KeyCode::Char('j') => self.y += 1.0,
|
||||
KeyCode::Up | KeyCode::Char('k') => self.y -= 1.0,
|
||||
KeyCode::Right | KeyCode::Char('l') => self.x += 1.0,
|
||||
KeyCode::Left | KeyCode::Char('h') => self.x -= 1.0,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
app.on_tick();
|
||||
self.on_tick();
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
restore_terminal()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_tick(&mut self) {
|
||||
@@ -204,15 +198,3 @@ impl App {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn init_terminal() -> io::Result<Terminal<CrosstermBackend<Stdout>>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
Terminal::new(CrosstermBackend::new(stdout()))
|
||||
}
|
||||
|
||||
fn restore_terminal() -> io::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,23 +13,16 @@
|
||||
//! [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::{
|
||||
error::Error,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
symbols::{self, Marker},
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
widgets::{block::Title, Axis, Block, Chart, Dataset, GraphType, LegendPosition},
|
||||
};
|
||||
@@ -97,40 +90,13 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
|
||||
// create app and run it
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
let app = App::new();
|
||||
let res = run_app(&mut terminal, app, tick_rate);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> io::Result<()> {
|
||||
let mut app = App::new();
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &app))?;
|
||||
|
||||
@@ -16,49 +16,25 @@
|
||||
// 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},
|
||||
result,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{Color, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
text::Line,
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
|
||||
type Result<T> = result::Result<T, Box<dyn Error>>;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = setup_terminal()?;
|
||||
let res = run_app(&mut terminal);
|
||||
restore_terminal(terminal)?;
|
||||
if let Err(err) = res {
|
||||
eprintln!("{err:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
loop {
|
||||
terminal.draw(ui)?;
|
||||
|
||||
if event::poll(Duration::from_millis(250))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,20 +247,3 @@ fn render_indexed_grayscale(frame: &mut Frame, area: Rect) {
|
||||
frame.render_widget(paragraph, layout[i as usize - 232]);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.hide_cursor()?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||
terminal.show_cursor()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -26,27 +26,19 @@
|
||||
// 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,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::{config::HookBuilder, eyre, Result};
|
||||
use color_eyre::Result;
|
||||
use palette::{convert::FromColorUnclamped, Okhsv, Srgb};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::Color,
|
||||
terminal::Terminal,
|
||||
text::Text,
|
||||
widgets::Widget,
|
||||
Terminal,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -100,11 +92,8 @@ struct ColorsWidget {
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
install_error_hooks()?;
|
||||
let terminal = init_terminal()?;
|
||||
App::default().run(terminal)?;
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -128,9 +117,9 @@ impl App {
|
||||
/// Currently, this only handles the q key to quit the app.
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
// Ensure that the app only blocks for a period that allows the app to render at
|
||||
// approximately 60 FPS (this doesn't account for the time to render the frame, and will
|
||||
// approximately 50 FPS (this doesn't account for the time to render the frame, and will
|
||||
// also update the app immediately any time an event occurs)
|
||||
let timeout = Duration::from_secs_f32(1.0 / 60.0);
|
||||
let timeout = Duration::from_secs_f32(1.0 / 50.0); // 50 FPS is standard for GIFs
|
||||
if event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
@@ -263,36 +252,3 @@ impl ColorsWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||
terminal.clear()?;
|
||||
terminal.hide_cursor()?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,18 +13,12 @@
|
||||
//! [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::{self, stdout};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use color_eyre::Result;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
Flex, Layout, Rect,
|
||||
@@ -91,16 +85,13 @@ struct ConstraintBlock {
|
||||
struct SpacerBlock;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
init_error_hooks()?;
|
||||
let terminal = init_terminal()?;
|
||||
App::default().run(terminal)?;
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
// App behaviour
|
||||
impl App {
|
||||
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
self.insert_test_defaults();
|
||||
|
||||
while self.is_running() {
|
||||
@@ -124,7 +115,7 @@ impl App {
|
||||
self.mode == AppMode::Running
|
||||
}
|
||||
|
||||
fn draw(&self, terminal: &mut Terminal<impl Backend>) -> io::Result<()> {
|
||||
fn draw(&self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
terminal.draw(|frame| frame.render_widget(self, frame.size()))?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -621,32 +612,3 @@ impl ConstraintName {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,17 +13,11 @@
|
||||
//! [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::{self, stdout};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
Layout, Rect,
|
||||
@@ -83,21 +77,14 @@ enum AppState {
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
init_error_hooks()?;
|
||||
let terminal = init_terminal()?;
|
||||
|
||||
// increase the cache size to avoid flickering for indeterminate layouts
|
||||
Layout::init_cache(100);
|
||||
|
||||
App::default().run(terminal)?;
|
||||
|
||||
restore_terminal()?;
|
||||
|
||||
Ok(())
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
self.update_max_scroll_offset();
|
||||
while self.is_running() {
|
||||
self.draw(&mut terminal)?;
|
||||
@@ -114,7 +101,7 @@ impl App {
|
||||
self.state == AppState::Running
|
||||
}
|
||||
|
||||
fn draw(self, terminal: &mut Terminal<impl Backend>) -> io::Result<()> {
|
||||
fn draw(self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
terminal.draw(|frame| frame.render_widget(self, frame.size()))?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -421,32 +408,3 @@ impl Example {
|
||||
Paragraph::new(text).centered().block(block)
|
||||
}
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,19 +13,13 @@
|
||||
//! [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::{error::Error, io, ops::ControlFlow, time::Duration};
|
||||
use std::{ops::ControlFlow, time::Duration};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseButton, MouseEvent,
|
||||
MouseEventKind,
|
||||
},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, MouseButton, MouseEvent, MouseEventKind},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
terminal::{Frame, Terminal},
|
||||
@@ -143,34 +137,14 @@ impl Button<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let res = run_app(&mut terminal);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn main() -> Result<()> {
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
run_app(terminal)
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
fn run_app(mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
let mut selected_button: usize = 0;
|
||||
let mut button_states = [State::Selected, State::Normal, State::Normal];
|
||||
loop {
|
||||
@@ -180,11 +154,10 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
}
|
||||
match event::read()? {
|
||||
Event::Key(key) => {
|
||||
if key.kind != event::KeyEventKind::Press {
|
||||
continue;
|
||||
}
|
||||
if handle_key_event(key, &mut button_states, &mut selected_button).is_break() {
|
||||
break;
|
||||
if key.kind == event::KeyEventKind::Press
|
||||
&& handle_key_event(key, &mut button_states, &mut selected_button).is_break()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse) => {
|
||||
@@ -193,7 +166,6 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ui(frame: &mut Frame, states: [State; 3]) {
|
||||
|
||||
@@ -1,56 +1,26 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
terminal::Terminal,
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
use crate::{app::App, ui};
|
||||
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<()> {
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::new("Crossterm Demo", enhanced_graphics);
|
||||
let res = run_app(&mut terminal, app, tick_rate);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
run_app(app, terminal, tick_rate)
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> io::Result<()> {
|
||||
fn run_app(mut app: App, mut terminal: Terminal<impl Backend>, tick_rate: Duration) -> Result<()> {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
while !app.should_quit {
|
||||
terminal.draw(|f| ui::draw(f, &mut app))?;
|
||||
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
@@ -72,8 +42,6 @@ fn run_app<B: Backend>(
|
||||
app.on_tick();
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
if app.should_quit {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
use std::{error::Error, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use argh::FromArgs;
|
||||
use color_eyre::Result;
|
||||
|
||||
mod app;
|
||||
#[cfg(feature = "crossterm")]
|
||||
@@ -38,7 +39,7 @@ struct Cli {
|
||||
enhanced_graphics: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
fn main() -> Result<()> {
|
||||
let cli: Cli = argh::from_env();
|
||||
let tick_rate = Duration::from_millis(cli.tick_rate);
|
||||
#[cfg(feature = "crossterm")]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{error::Error, io, sync::mpsc, thread, time::Duration};
|
||||
use std::{io, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, TermionBackend},
|
||||
terminal::Terminal,
|
||||
@@ -13,7 +14,7 @@ use ratatui::{
|
||||
|
||||
use crate::{app::App, ui};
|
||||
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<()> {
|
||||
// setup terminal
|
||||
let stdout = io::stdout()
|
||||
.into_raw_mode()
|
||||
@@ -22,22 +23,17 @@ pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn E
|
||||
.unwrap();
|
||||
let stdout = MouseTerminal::from(stdout);
|
||||
let backend = TermionBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
let terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::new("Termion demo", enhanced_graphics);
|
||||
run_app(&mut terminal, app, tick_rate)?;
|
||||
run_app(app, terminal, tick_rate)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
fn run_app(mut app: App, mut terminal: Terminal<impl Backend>, tick_rate: Duration) -> Result<()> {
|
||||
let events = events(tick_rate);
|
||||
loop {
|
||||
while !app.should_quit {
|
||||
terminal.draw(|f| ui::draw(f, &mut app))?;
|
||||
|
||||
match events.recv()? {
|
||||
@@ -51,10 +47,8 @@ fn run_app<B: Backend>(
|
||||
},
|
||||
Event::Tick => app.on_tick(),
|
||||
}
|
||||
if app.should_quit {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum Event {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::{eyre::eyre, Result};
|
||||
use ratatui::{
|
||||
backend::TermwizBackend,
|
||||
terminal::Terminal,
|
||||
@@ -14,14 +12,14 @@ use ratatui::{
|
||||
|
||||
use crate::{app::App, ui};
|
||||
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||
let backend = TermwizBackend::new()?;
|
||||
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<()> {
|
||||
let backend =
|
||||
TermwizBackend::new().map_err(|error| eyre!("failed to init termwiz backend. {error}"))?;
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.hide_cursor()?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::new("Termwiz Demo", enhanced_graphics);
|
||||
let res = run_app(&mut terminal, app, tick_rate);
|
||||
let res = run_app(app, &mut terminal, tick_rate);
|
||||
|
||||
terminal.show_cursor()?;
|
||||
terminal.flush()?;
|
||||
@@ -34,12 +32,12 @@ pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn E
|
||||
}
|
||||
|
||||
fn run_app(
|
||||
terminal: &mut Terminal<TermwizBackend>,
|
||||
mut app: App,
|
||||
terminal: &mut Terminal<TermwizBackend>,
|
||||
tick_rate: Duration,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
) -> Result<()> {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
while !app.should_quit {
|
||||
terminal.draw(|f| ui::draw(f, &mut app))?;
|
||||
|
||||
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||
@@ -72,8 +70,6 @@ fn run_app(
|
||||
app.on_tick();
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
if app.should_quit {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use crossterm::event;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
backend::Backend,
|
||||
@@ -17,7 +18,7 @@ use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
use crate::{
|
||||
destroy,
|
||||
tabs::{AboutTab, EmailTab, RecipeTab, TracerouteTab, WeatherTab},
|
||||
term, THEME,
|
||||
THEME,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -49,15 +50,11 @@ enum Tab {
|
||||
Weather,
|
||||
}
|
||||
|
||||
pub fn run(terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Run the app until the user quits.
|
||||
pub fn run(&mut self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
pub fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
while self.is_running() {
|
||||
self.draw(terminal)?;
|
||||
self.draw(&mut terminal)?;
|
||||
self.handle_events()?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -86,13 +83,21 @@ impl App {
|
||||
/// 1/50th of a second. This was chosen to try to match the default frame rate of a GIF in VHS.
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
let timeout = Duration::from_secs_f64(1.0 / 50.0);
|
||||
match term::next_event(timeout)? {
|
||||
match Self::next_event(timeout)? {
|
||||
Some(Event::Key(key)) if key.kind == KeyEventKind::Press => self.handle_key_press(key),
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_event(timeout: Duration) -> Result<Option<Event>> {
|
||||
if !event::poll(timeout)? {
|
||||
return Ok(None);
|
||||
}
|
||||
let event = event::read()?;
|
||||
Ok(Some(event))
|
||||
}
|
||||
|
||||
fn handle_key_press(&mut self, key: KeyEvent) {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
|
||||
use crate::term;
|
||||
|
||||
pub fn init_hooks() -> Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = term::restore();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = term::restore();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
@@ -23,12 +23,16 @@ mod app;
|
||||
mod big_text;
|
||||
mod colors;
|
||||
mod destroy;
|
||||
mod errors;
|
||||
mod tabs;
|
||||
mod term;
|
||||
mod theme;
|
||||
|
||||
use app::App;
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
layout::Rect,
|
||||
TerminalOptions, Viewport,
|
||||
};
|
||||
|
||||
pub use self::{
|
||||
colors::{color_from_oklab, RgbSwatch},
|
||||
@@ -36,9 +40,11 @@ pub use self::{
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
errors::init_hooks()?;
|
||||
let terminal = &mut term::init()?;
|
||||
app::run(terminal)?;
|
||||
term::restore()?;
|
||||
Ok(())
|
||||
// this size is to match the size of the terminal when running the demo
|
||||
// using vhs in a 1280x640 sized window (github social preview size)
|
||||
let options = TerminalOptions {
|
||||
viewport: Viewport::Fixed(Rect::new(0, 0, 81, 18)),
|
||||
};
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal_with_options(options)?;
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
@@ -13,18 +13,13 @@
|
||||
//! [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::{self, stdout};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
@@ -33,12 +28,9 @@ use ratatui::{
|
||||
///
|
||||
/// When cargo-rdme supports doc comments that import from code, this will be imported
|
||||
/// rather than copied to the lib.rs file.
|
||||
fn main() -> io::Result<()> {
|
||||
fn main() -> Result<()> {
|
||||
let arg = std::env::args().nth(1).unwrap_or_default();
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
let mut should_quit = false;
|
||||
while !should_quit {
|
||||
terminal.draw(match arg.as_str() {
|
||||
@@ -48,9 +40,6 @@ fn main() -> io::Result<()> {
|
||||
})?;
|
||||
should_quit = handle_events()?;
|
||||
}
|
||||
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -61,7 +50,7 @@ fn hello_world(frame: &mut Frame) {
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_events() -> io::Result<bool> {
|
||||
fn handle_events() -> Result<bool> {
|
||||
if event::poll(std::time::Duration::from_millis(50))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == event::KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
|
||||
@@ -13,17 +13,11 @@
|
||||
//! [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::{self, stdout};
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{
|
||||
Alignment,
|
||||
Constraint::{self, Fill, Length, Max, Min, Percentage, Ratio},
|
||||
@@ -157,16 +151,12 @@ enum SelectedTab {
|
||||
fn main() -> Result<()> {
|
||||
// assuming the user changes spacing about a 100 times or so
|
||||
Layout::init_cache(EXAMPLE_DATA.len() * SelectedTab::iter().len() * 100);
|
||||
init_error_hooks()?;
|
||||
let terminal = init_terminal()?;
|
||||
App::default().run(terminal)?;
|
||||
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
self.draw(&mut terminal)?;
|
||||
while self.is_running() {
|
||||
self.handle_events()?;
|
||||
@@ -179,7 +169,7 @@ impl App {
|
||||
self.state == AppState::Running
|
||||
}
|
||||
|
||||
fn draw(self, terminal: &mut Terminal<impl Backend>) -> io::Result<()> {
|
||||
fn draw(self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
terminal.draw(|frame| frame.render_widget(self, frame.size()))?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -527,35 +517,6 @@ const fn color_for_constraint(constraint: Constraint) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn get_description_height(s: &str) -> u16 {
|
||||
if s.is_empty() {
|
||||
|
||||
@@ -13,17 +13,13 @@
|
||||
//! [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::stdout, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{palette::tailwind, Color, Style, Stylize},
|
||||
terminal::Terminal,
|
||||
@@ -56,15 +52,12 @@ enum AppState {
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
init_error_hooks()?;
|
||||
let terminal = init_terminal()?;
|
||||
App::default().run(terminal)?;
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
while self.state != AppState::Quitting {
|
||||
self.draw(&mut terminal)?;
|
||||
self.handle_events()?;
|
||||
@@ -213,32 +206,3 @@ fn title_block(title: &str) -> Block {
|
||||
.title(title)
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> color_eyre::Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> color_eyre::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,20 +13,11 @@
|
||||
//! [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::{self, Stdout},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use color_eyre::{eyre::WrapErr, Result};
|
||||
use crossterm::event::KeyEventKind;
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
terminal::{Frame, Terminal},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
widgets::Paragraph,
|
||||
};
|
||||
|
||||
@@ -34,66 +25,36 @@ use ratatui::{
|
||||
/// this is not meant to be prescriptive. It is only meant to demonstrate the basic setup and
|
||||
/// teardown of a terminal application.
|
||||
///
|
||||
/// A more robust application would probably want to handle errors and ensure that the terminal is
|
||||
/// restored to a sane state before exiting. This example does not do that. It also does not handle
|
||||
/// events or update the application state. It just draws a greeting and exits when the user
|
||||
/// presses 'q'.
|
||||
/// A more robust application would probably want to handle errors more thouroughly. It also does
|
||||
/// not handle events or update any application state. It just draws a greeting and exits when the
|
||||
/// user presses 'q'.
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = setup_terminal().context("setup failed")?;
|
||||
run(&mut terminal).context("app loop failed")?;
|
||||
restore_terminal(&mut terminal).context("restore terminal failed")?;
|
||||
Ok(())
|
||||
}
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.to_terminal()
|
||||
.wrap_err("failed to start terminal")?;
|
||||
|
||||
/// Setup the terminal. This is where you would enable raw mode, enter the alternate screen, and
|
||||
/// hide the cursor. This example does not handle errors. A more robust application would probably
|
||||
/// want to handle errors and ensure that the terminal is restored to a sane state before exiting.
|
||||
fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
|
||||
let mut stdout = io::stdout();
|
||||
enable_raw_mode().context("failed to enable raw mode")?;
|
||||
execute!(stdout, EnterAlternateScreen).context("unable to enter alternate screen")?;
|
||||
Terminal::new(CrosstermBackend::new(stdout)).context("creating terminal failed")
|
||||
}
|
||||
|
||||
/// Restore the terminal. This is where you disable raw mode, leave the alternate screen, and show
|
||||
/// the cursor.
|
||||
fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
||||
disable_raw_mode().context("failed to disable raw mode")?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)
|
||||
.context("unable to switch to main screen")?;
|
||||
terminal.show_cursor().context("unable to show cursor")
|
||||
}
|
||||
|
||||
/// Run the application loop. This is where you would handle events and update the application
|
||||
/// state. This example exits when the user presses 'q'. Other styles of application loops are
|
||||
/// possible, for example, you could have multiple application states and switch between them based
|
||||
/// on events, or you could have a single application state and update it based on events.
|
||||
fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
||||
// Run the application loop. This is where you would handle events and update the application
|
||||
// state. This example exits when the user presses 'q'. Other styles of application loops are
|
||||
// possible, for example, you could have multiple application states and switch between them
|
||||
// based on events, or you could have a single application state and update it based on events.
|
||||
loop {
|
||||
terminal.draw(crate::render_app)?;
|
||||
if should_quit()? {
|
||||
break;
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
// Render the application. This is where you would draw the application UI. This
|
||||
// example just draws a greeting.
|
||||
let greeting = Paragraph::new("Hello World! (press 'q' to quit)");
|
||||
frame.render_widget(greeting, frame.size());
|
||||
})
|
||||
.wrap_err("failed to draw")?;
|
||||
// Check if the user has pressed 'q'. This is where you would handle events. This example
|
||||
// just checks if the user has pressed 'q' and returns true if they have. It does not
|
||||
// handle any other events. This is a very basic event loop. A more robust application
|
||||
// would probably want to handle more events and consider not blocking the thread when
|
||||
// there are no events (by using [`crossterm::event::poll`])
|
||||
if let Event::Key(key) = event::read().wrap_err("failed to read events")? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render the application. This is where you would draw the application UI. This example just
|
||||
/// draws a greeting.
|
||||
fn render_app(frame: &mut Frame) {
|
||||
let greeting = Paragraph::new("Hello World! (press 'q' to quit)");
|
||||
frame.render_widget(greeting, frame.size());
|
||||
}
|
||||
|
||||
/// Check if the user has pressed 'q'. This is where you would handle events. This example just
|
||||
/// checks if the user has pressed 'q' and returns true if they have. It does not handle any other
|
||||
/// events. There is a 250ms timeout on the event poll so that the application can exit in a timely
|
||||
/// manner, and to ensure that the terminal is rendered at least once every 250ms.
|
||||
fn should_quit() -> Result<bool> {
|
||||
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
|
||||
if let Event::Key(key) = event::read().context("event read failed")? {
|
||||
return Ok(KeyCode::Char('q') == key.code);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, VecDeque},
|
||||
error::Error,
|
||||
io,
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
@@ -87,17 +86,11 @@ struct Worker {
|
||||
tx: mpsc::Sender<Download>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
let stdout = io::stdout();
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::with_options(
|
||||
backend,
|
||||
TerminalOptions {
|
||||
viewport: Viewport::Inline(8),
|
||||
},
|
||||
)?;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let options = TerminalOptions {
|
||||
viewport: Viewport::Inline(8),
|
||||
};
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal_with_options(options)?;
|
||||
let (tx, rx) = mpsc::channel();
|
||||
input_handling(tx.clone());
|
||||
let workers = workers(tx);
|
||||
@@ -108,11 +101,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
w.tx.send(d).unwrap();
|
||||
}
|
||||
|
||||
run_app(&mut terminal, workers, downloads, rx)?;
|
||||
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
terminal.clear()?;
|
||||
|
||||
run_app(terminal, workers, downloads, rx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -179,12 +168,12 @@ fn downloads() -> Downloads {
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
fn run_app(
|
||||
mut terminal: Terminal<impl Backend>,
|
||||
workers: Vec<Worker>,
|
||||
mut downloads: Downloads,
|
||||
rx: mpsc::Receiver<Event>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
) -> Result<()> {
|
||||
let mut redraw = true;
|
||||
loop {
|
||||
if redraw {
|
||||
|
||||
@@ -13,58 +13,28 @@
|
||||
//! [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::{error::Error, io};
|
||||
|
||||
use color_eyre::Result;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{
|
||||
Constraint,
|
||||
Constraint::{Length, Max, Min, Percentage, Ratio},
|
||||
Layout, Rect,
|
||||
},
|
||||
style::{Color, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
text::Line,
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let res = run_app(&mut terminal);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
loop {
|
||||
terminal.draw(ui)?;
|
||||
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
|
||||
@@ -13,17 +13,13 @@
|
||||
//! [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::stdout, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{palette::tailwind, Color, Style, Stylize},
|
||||
terminal::Terminal,
|
||||
@@ -48,15 +44,12 @@ enum AppState {
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
init_error_hooks()?;
|
||||
let terminal = init_terminal()?;
|
||||
App::default().run(terminal)?;
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
while self.state != AppState::Quitting {
|
||||
self.draw(&mut terminal)?;
|
||||
self.handle_events()?;
|
||||
@@ -187,32 +180,3 @@ fn title_block(title: &str) -> Block {
|
||||
.fg(CUSTOM_LABEL_COLOR)
|
||||
.padding(Padding::vertical(1))
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> color_eyre::Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> color_eyre::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,17 +13,11 @@
|
||||
//! [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::{error::Error, io, io::stdout};
|
||||
|
||||
use color_eyre::config::HookBuilder;
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Alignment, Constraint, Layout, Rect},
|
||||
style::{palette::tailwind, Color, Modifier, Style, Stylize},
|
||||
terminal::Terminal,
|
||||
@@ -79,46 +73,9 @@ struct App {
|
||||
items: TodoList,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
init_error_hooks()?;
|
||||
let terminal = init_terminal()?;
|
||||
|
||||
// create app and run it
|
||||
App::new().run(terminal)?;
|
||||
|
||||
restore_terminal()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> color_eyre::Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> color_eyre::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
fn main() -> Result<()> {
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::new().run(terminal)
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -155,7 +112,7 @@ impl App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> io::Result<()> {
|
||||
fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
loop {
|
||||
self.draw(&mut terminal)?;
|
||||
|
||||
@@ -178,7 +135,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&mut self, terminal: &mut Terminal<impl Backend>) -> io::Result<()> {
|
||||
fn draw(&mut self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
terminal.draw(|f| f.render_widget(self, f.size()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,15 +13,11 @@
|
||||
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
text::Text,
|
||||
Terminal,
|
||||
};
|
||||
|
||||
/// This is a bare minimum example. There are many approaches to running an application loop, so
|
||||
@@ -30,19 +26,15 @@ use ratatui::{
|
||||
///
|
||||
/// [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
|
||||
/// [hello-world]: https://github.com/ratatui-org/ratatui/blob/main/examples/hello_world.rs
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
|
||||
enable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), EnterAlternateScreen)?;
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
terminal.clear()?;
|
||||
loop {
|
||||
terminal.draw(|frame| frame.render_widget(Text::raw("Hello World!"), frame.size()))?;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
break;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
disable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,42 +17,22 @@
|
||||
// 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},
|
||||
iter::once,
|
||||
result,
|
||||
time::Duration,
|
||||
};
|
||||
use std::{iter::once, time::Duration};
|
||||
|
||||
use color_eyre::Result;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
text::Line,
|
||||
widgets::Paragraph,
|
||||
};
|
||||
|
||||
type Result<T> = result::Result<T, Box<dyn Error>>;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = setup_terminal()?;
|
||||
let res = run_app(&mut terminal);
|
||||
restore_terminal(terminal)?;
|
||||
if let Err(err) = res {
|
||||
eprintln!("{err:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
loop {
|
||||
terminal.draw(ui)?;
|
||||
|
||||
@@ -114,20 +94,3 @@ fn ui(frame: &mut Frame) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.hide_cursor()?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
||||
disable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||
terminal.show_cursor()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,21 +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
|
||||
|
||||
//! How to use a panic hook to reset the terminal before printing the panic to
|
||||
//! the terminal.
|
||||
//! How to use a panic hook to reset the terminal before printing the panic to the terminal.
|
||||
//!
|
||||
//! When exiting normally or when handling `Result::Err`, we can reset the
|
||||
//! terminal manually at the end of `main` just before we print the error.
|
||||
//! When exiting normally or when handling `Result::Err`, we can reset the terminal manually at the
|
||||
//! end of `main` just before we print the error.
|
||||
//!
|
||||
//! Because a panic interrupts the normal control flow, manually resetting the
|
||||
//! terminal at the end of `main` won't do us any good. Instead, we need to
|
||||
//! make sure to set up a panic hook that first resets the terminal before
|
||||
//! handling the panic. This both reuses the standard panic hook to ensure a
|
||||
//! consistent panic handling UX and properly resets the terminal to not
|
||||
//! distort the output.
|
||||
//! Because a panic interrupts the normal control flow, manually resetting the terminal at the end
|
||||
//! of `main` won't do us any good. Instead, we need to make sure to set up a panic hook that first
|
||||
//! resets the terminal before handling the panic. This both reuses the standard panic hook to
|
||||
//! ensure a consistent panic handling UX and properly resets the terminal to not distort the
|
||||
//! output.
|
||||
//!
|
||||
//! That's why this example is set up to show both situations, with and without
|
||||
//! the chained panic hook, to see the difference.
|
||||
//! That's why this example is set up to show both situations, with and without the chained panic
|
||||
//! hook, to see the difference.
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
@@ -44,6 +42,21 @@ use ratatui::{
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = init_terminal()?;
|
||||
|
||||
let mut app = App::default();
|
||||
let res = run_tui(&mut terminal, &mut app);
|
||||
|
||||
reset_terminal()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct App {
|
||||
hook_enabled: bool,
|
||||
@@ -62,26 +75,12 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = init_terminal()?;
|
||||
|
||||
let mut app = App::default();
|
||||
let res = run_tui(&mut terminal, &mut app);
|
||||
|
||||
reset_terminal()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initializes the terminal.
|
||||
fn init_terminal() -> Result<Terminal<CrosstermBackend<io::Stdout>>> {
|
||||
crossterm::execute!(io::stdout(), EnterAlternateScreen)?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let backend = CrosstermBackend::new(io::stdout());
|
||||
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
@@ -99,7 +98,7 @@ fn reset_terminal() -> Result<()> {
|
||||
}
|
||||
|
||||
/// Runs the TUI loop.
|
||||
fn run_tui<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<()> {
|
||||
fn run_tui<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, app))?;
|
||||
|
||||
|
||||
@@ -13,30 +13,26 @@
|
||||
//! [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::{self},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::KeyEventKind;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Stylize},
|
||||
text::{Line, Masked, Span},
|
||||
widgets::{Block, Paragraph, Widget, Wrap},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
use self::common::{init_terminal, install_hooks, restore_terminal, Tui};
|
||||
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
install_hooks()?;
|
||||
let mut terminal = init_terminal()?;
|
||||
let mut app = App::new();
|
||||
app.run(&mut terminal)?;
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
fn main() -> Result<()> {
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
App::new().run(terminal)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -60,9 +56,9 @@ impl App {
|
||||
}
|
||||
|
||||
/// Run the app until the user exits.
|
||||
fn run(&mut self, terminal: &mut Tui) -> io::Result<()> {
|
||||
fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
while !self.should_exit {
|
||||
self.draw(terminal)?;
|
||||
self.draw(&mut terminal)?;
|
||||
self.handle_events()?;
|
||||
if self.last_tick.elapsed() >= Self::TICK_RATE {
|
||||
self.on_tick();
|
||||
@@ -73,13 +69,13 @@ impl App {
|
||||
}
|
||||
|
||||
/// Draw the app to the terminal.
|
||||
fn draw(&mut self, terminal: &mut Tui) -> io::Result<()> {
|
||||
fn draw(&mut self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
terminal.draw(|frame| frame.render_widget(self, frame.size()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle events from the terminal.
|
||||
fn handle_events(&mut self) -> io::Result<()> {
|
||||
fn handle_events(&mut self) -> Result<()> {
|
||||
let timeout = Self::TICK_RATE.saturating_sub(self.last_tick.elapsed());
|
||||
while event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
@@ -159,73 +155,3 @@ fn create_lines(area: Rect) -> Vec<Line<'static>> {
|
||||
]),
|
||||
]
|
||||
}
|
||||
|
||||
/// A module for common functionality used in the examples.
|
||||
mod common {
|
||||
use std::{
|
||||
io::{self, stdout, Stdout},
|
||||
panic,
|
||||
};
|
||||
|
||||
use color_eyre::{
|
||||
config::{EyreHook, HookBuilder, PanicHook},
|
||||
eyre,
|
||||
};
|
||||
use crossterm::ExecutableCommand;
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
},
|
||||
terminal::Terminal,
|
||||
};
|
||||
|
||||
// A simple alias for the terminal type used in this example.
|
||||
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
||||
|
||||
/// Initialize the terminal and enter alternate screen mode.
|
||||
pub fn init_terminal() -> io::Result<Tui> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
Terminal::new(backend)
|
||||
}
|
||||
|
||||
/// Restore the terminal to its original state.
|
||||
pub fn restore_terminal() -> io::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Installs hooks for panic and error handling.
|
||||
///
|
||||
/// Makes the app resilient to panics and errors by restoring the terminal before printing the
|
||||
/// panic or error message. This prevents error messages from being messed up by the terminal
|
||||
/// state.
|
||||
pub fn install_hooks() -> color_eyre::Result<()> {
|
||||
let (panic_hook, eyre_hook) = HookBuilder::default().into_hooks();
|
||||
install_panic_hook(panic_hook);
|
||||
install_error_hook(eyre_hook)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a panic hook that restores the terminal before printing the panic.
|
||||
fn install_panic_hook(panic_hook: PanicHook) {
|
||||
let panic_hook = panic_hook.into_panic_hook();
|
||||
panic::set_hook(Box::new(move |panic_info| {
|
||||
let _ = restore_terminal();
|
||||
panic_hook(panic_info);
|
||||
}));
|
||||
}
|
||||
|
||||
/// Install an error hook that restores the terminal before printing the error.
|
||||
fn install_error_hook(eyre_hook: EyreHook) -> color_eyre::Result<()> {
|
||||
let eyre_hook = eyre_hook.into_eyre_hook();
|
||||
eyre::set_hook(Box::new(move |error| {
|
||||
let _ = restore_terminal();
|
||||
eyre_hook(error)
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,18 +16,13 @@
|
||||
// See also https://github.com/joshka/tui-popup and
|
||||
// https://github.com/sephiroth74/tui-confirm-dialog
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::Stylize,
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
widgets::{Block, Clear, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
@@ -41,35 +36,12 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::new();
|
||||
let res = run_app(&mut terminal, app);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
|
||||
let mut app = App::new();
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &app))?;
|
||||
|
||||
|
||||
@@ -13,25 +13,39 @@
|
||||
//! [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::{self, stdout},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
||||
use indoc::indoc;
|
||||
use itertools::izip;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::terminal::{disable_raw_mode, enable_raw_mode},
|
||||
terminal::{Terminal, Viewport},
|
||||
terminal::Viewport,
|
||||
widgets::Paragraph,
|
||||
TerminalOptions,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal =
|
||||
CrosstermBackend::stdout_with_defaults()?.to_terminal_with_options(TerminalOptions {
|
||||
viewport: Viewport::Inline(3),
|
||||
})?;
|
||||
terminal.draw(|frame| {
|
||||
frame.render_widget(logo(), frame.size());
|
||||
})?;
|
||||
loop {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(); // necessary to avoid the cursor being on the same line as the logo
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A fun example of using half block characters to draw a logo
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn logo() -> String {
|
||||
fn logo() -> Paragraph<'static> {
|
||||
let r = indoc! {"
|
||||
▄▄▄
|
||||
█▄▄▀
|
||||
@@ -57,32 +71,9 @@ fn logo() -> String {
|
||||
█
|
||||
█
|
||||
"};
|
||||
izip!(r.lines(), a.lines(), t.lines(), u.lines(), i.lines())
|
||||
let 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| {
|
||||
frame.render_widget(Paragraph::new(logo()), frame.size());
|
||||
})?;
|
||||
sleep(Duration::from_secs(5));
|
||||
restore()?;
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init() -> io::Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
let options = TerminalOptions {
|
||||
viewport: Viewport::Inline(3),
|
||||
};
|
||||
Terminal::with_options(CrosstermBackend::new(stdout()), options)
|
||||
}
|
||||
|
||||
fn restore() -> io::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
Ok(())
|
||||
.join("\n");
|
||||
Paragraph::new(lines)
|
||||
}
|
||||
|
||||
@@ -15,23 +15,16 @@
|
||||
|
||||
#![warn(clippy::pedantic)]
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Alignment, Constraint, Layout, Margin},
|
||||
style::{Color, Style, Stylize},
|
||||
symbols::scrollbar,
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
text::{Line, Masked, Span},
|
||||
widgets::{Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||
};
|
||||
@@ -44,40 +37,13 @@ struct App {
|
||||
pub horizontal_scroll: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
|
||||
// create app and run it
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
let app = App::default();
|
||||
let res = run_app(&mut terminal, app, tick_rate);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> io::Result<()> {
|
||||
let mut app = App::default();
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &mut app))?;
|
||||
|
||||
@@ -13,26 +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
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use color_eyre::Result;
|
||||
use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
rngs::ThreadRng,
|
||||
};
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Style},
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
widgets::{Block, Borders, Sparkline},
|
||||
};
|
||||
|
||||
@@ -92,40 +85,13 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
|
||||
// create app and run it
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
let app = App::new();
|
||||
let res = run_app(&mut terminal, app, tick_rate);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
tick_rate: Duration,
|
||||
) -> io::Result<()> {
|
||||
let mut app = App::new();
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &app))?;
|
||||
|
||||
@@ -13,26 +13,20 @@
|
||||
//! [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::{error::Error, io};
|
||||
|
||||
use color_eyre::Result;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{self, Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
style::{palette::tailwind, Color, Modifier, Style, Stylize},
|
||||
terminal::Frame,
|
||||
text::{Line, Text},
|
||||
widgets::{
|
||||
Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Scrollbar, ScrollbarOrientation,
|
||||
ScrollbarState, Table, TableState,
|
||||
},
|
||||
};
|
||||
use style::palette::tailwind;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
const PALETTES: [tailwind::Palette; 4] = [
|
||||
@@ -46,31 +40,37 @@ const INFO_TEXT: &str =
|
||||
|
||||
const ITEM_HEIGHT: usize = 4;
|
||||
|
||||
struct TableColors {
|
||||
buffer_bg: Color,
|
||||
header_bg: Color,
|
||||
header_fg: Color,
|
||||
row_fg: Color,
|
||||
selected_style_fg: Color,
|
||||
normal_row_color: Color,
|
||||
alt_row_color: Color,
|
||||
footer_border_color: Color,
|
||||
}
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
|
||||
impl TableColors {
|
||||
const fn new(color: &tailwind::Palette) -> Self {
|
||||
Self {
|
||||
buffer_bg: tailwind::SLATE.c950,
|
||||
header_bg: color.c900,
|
||||
header_fg: tailwind::SLATE.c200,
|
||||
row_fg: tailwind::SLATE.c200,
|
||||
selected_style_fg: color.c400,
|
||||
normal_row_color: tailwind::SLATE.c950,
|
||||
alt_row_color: tailwind::SLATE.c900,
|
||||
footer_border_color: color.c400,
|
||||
let mut app = App::new();
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &mut app))?;
|
||||
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
|
||||
KeyCode::Char('j') | KeyCode::Down => app.next(),
|
||||
KeyCode::Char('k') | KeyCode::Up => app.previous(),
|
||||
KeyCode::Char('l') | KeyCode::Right => app.next_color(),
|
||||
KeyCode::Char('h') | KeyCode::Left => app.previous_color(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct App {
|
||||
state: TableState,
|
||||
items: Vec<Data>,
|
||||
longest_item_lens: (u16, u16, u16), // order is (name, address, email)
|
||||
scroll_state: ScrollbarState,
|
||||
colors: TableColors,
|
||||
color_index: usize,
|
||||
}
|
||||
|
||||
struct Data {
|
||||
name: String,
|
||||
@@ -96,13 +96,30 @@ impl Data {
|
||||
}
|
||||
}
|
||||
|
||||
struct App {
|
||||
state: TableState,
|
||||
items: Vec<Data>,
|
||||
longest_item_lens: (u16, u16, u16), // order is (name, address, email)
|
||||
scroll_state: ScrollbarState,
|
||||
colors: TableColors,
|
||||
color_index: usize,
|
||||
struct TableColors {
|
||||
buffer_bg: Color,
|
||||
header_bg: Color,
|
||||
header_fg: Color,
|
||||
row_fg: Color,
|
||||
selected_style_fg: Color,
|
||||
normal_row_color: Color,
|
||||
alt_row_color: Color,
|
||||
footer_border_color: Color,
|
||||
}
|
||||
|
||||
impl TableColors {
|
||||
const fn new(color: &tailwind::Palette) -> Self {
|
||||
Self {
|
||||
buffer_bg: tailwind::SLATE.c950,
|
||||
header_bg: color.c900,
|
||||
header_fg: tailwind::SLATE.c200,
|
||||
row_fg: tailwind::SLATE.c200,
|
||||
selected_style_fg: color.c400,
|
||||
normal_row_color: tailwind::SLATE.c950,
|
||||
alt_row_color: tailwind::SLATE.c900,
|
||||
footer_border_color: color.c400,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -186,53 +203,6 @@ fn generate_fake_names() -> Vec<Data> {
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::new();
|
||||
let res = run_app(&mut terminal, app);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &mut app))?;
|
||||
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
|
||||
KeyCode::Char('j') | KeyCode::Down => app.next(),
|
||||
KeyCode::Char('k') | KeyCode::Up => app.previous(),
|
||||
KeyCode::Char('l') | KeyCode::Right => app.next_color(),
|
||||
KeyCode::Char('h') | KeyCode::Left => app.previous_color(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ui(f: &mut Frame, app: &mut App) {
|
||||
let rects = Layout::vertical([Constraint::Min(5), Constraint::Length(3)]).split(f.size());
|
||||
|
||||
|
||||
117
examples/tabs.rs
117
examples/tabs.rs
@@ -13,17 +13,13 @@
|
||||
//! [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::stdout;
|
||||
use std::fmt;
|
||||
|
||||
use color_eyre::{config::HookBuilder, Result};
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
buffer::Buffer,
|
||||
crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{palette::tailwind, Color, Stylize},
|
||||
symbols,
|
||||
@@ -31,7 +27,11 @@ use ratatui::{
|
||||
text::Line,
|
||||
widgets::{Block, Padding, Paragraph, Tabs, Widget},
|
||||
};
|
||||
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
App::default().run(terminal)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct App {
|
||||
@@ -46,31 +46,30 @@ enum AppState {
|
||||
Quitting,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Display, FromRepr, EnumIter)]
|
||||
#[derive(Default, Clone, Copy)]
|
||||
enum SelectedTab {
|
||||
#[default]
|
||||
#[strum(to_string = "Tab 1")]
|
||||
Tab1,
|
||||
#[strum(to_string = "Tab 2")]
|
||||
Tab2,
|
||||
#[strum(to_string = "Tab 3")]
|
||||
Tab3,
|
||||
#[strum(to_string = "Tab 4")]
|
||||
Tab4,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
init_error_hooks()?;
|
||||
let mut terminal = init_terminal()?;
|
||||
App::default().run(&mut terminal)?;
|
||||
restore_terminal()?;
|
||||
Ok(())
|
||||
impl fmt::Display for SelectedTab {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Tab1 => write!(f, "Tab 1"),
|
||||
Self::Tab2 => write!(f, "Tab 2"),
|
||||
Self::Tab3 => write!(f, "Tab 3"),
|
||||
Self::Tab4 => write!(f, "Tab 4"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn run(&mut self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
|
||||
fn run(mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
|
||||
while self.state == AppState::Running {
|
||||
self.draw(terminal)?;
|
||||
self.draw(&mut terminal)?;
|
||||
self.handle_events()?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -108,22 +107,6 @@ 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;
|
||||
let previous_index = current_index.saturating_sub(1);
|
||||
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;
|
||||
let next_index = current_index.saturating_add(1);
|
||||
Self::from_repr(next_index).unwrap_or(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
use Constraint::{Length, Min};
|
||||
@@ -142,7 +125,7 @@ impl Widget for &App {
|
||||
|
||||
impl App {
|
||||
fn render_tabs(&self, area: Rect, buf: &mut Buffer) {
|
||||
let titles = SelectedTab::iter().map(SelectedTab::title);
|
||||
let titles = SelectedTab::titles();
|
||||
let highlight_style = (Color::default(), self.selected_tab.palette().c700);
|
||||
let selected_tab_index = self.selected_tab as usize;
|
||||
Tabs::new(titles)
|
||||
@@ -177,6 +160,35 @@ impl Widget for SelectedTab {
|
||||
}
|
||||
|
||||
impl SelectedTab {
|
||||
fn titles() -> [Line<'static>; 4] {
|
||||
[
|
||||
Self::Tab1.title(),
|
||||
Self::Tab2.title(),
|
||||
Self::Tab3.title(),
|
||||
Self::Tab4.title(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Get the previous tab, if there is no previous tab return the current tab.
|
||||
const fn previous(self) -> Self {
|
||||
match self {
|
||||
Self::Tab1 => Self::Tab4,
|
||||
Self::Tab2 => Self::Tab1,
|
||||
Self::Tab3 => Self::Tab2,
|
||||
Self::Tab4 => Self::Tab3,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next tab, if there is no next tab return the current tab.
|
||||
const fn next(self) -> Self {
|
||||
match self {
|
||||
Self::Tab1 => Self::Tab2,
|
||||
Self::Tab2 => Self::Tab3,
|
||||
Self::Tab3 => Self::Tab4,
|
||||
Self::Tab4 => Self::Tab1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return tab's name as a styled `Line`
|
||||
fn title(self) -> Line<'static> {
|
||||
format!(" {self} ")
|
||||
@@ -226,32 +238,3 @@ impl SelectedTab {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_error_hooks() -> color_eyre::Result<()> {
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
color_eyre::eyre::set_hook(Box::new(move |e| {
|
||||
let _ = restore_terminal();
|
||||
error(e)
|
||||
}))?;
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = restore_terminal();
|
||||
panic(info);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_terminal() -> color_eyre::Result<Terminal<impl Backend>> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
fn restore_terminal() -> color_eyre::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -27,18 +27,13 @@
|
||||
//
|
||||
// See also https://github.com/rhysd/tui-textarea and https://github.com/sayanarijit/tui-input/
|
||||
|
||||
use std::{error::Error, io};
|
||||
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||
layout::{Constraint, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
terminal::{Frame, Terminal},
|
||||
terminal::Frame,
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, List, ListItem, Paragraph},
|
||||
};
|
||||
@@ -135,35 +130,12 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
fn main() -> Result<()> {
|
||||
let mut terminal = CrosstermBackend::stdout_with_defaults()?
|
||||
.with_mouse_capture()?
|
||||
.to_terminal()?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::new();
|
||||
let res = run_app(&mut terminal, app);
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
|
||||
let mut app = App::new();
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &app))?;
|
||||
|
||||
|
||||
@@ -10,3 +10,5 @@ Enter
|
||||
Sleep 2s
|
||||
Show
|
||||
Sleep 2s
|
||||
Hide
|
||||
Type "q"
|
||||
|
||||
@@ -104,7 +104,11 @@ use std::io;
|
||||
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::{buffer::Cell, layout::Size, prelude::Rect};
|
||||
use crate::{
|
||||
buffer::Cell,
|
||||
layout::{Rect, Size},
|
||||
Terminal, TerminalOptions,
|
||||
};
|
||||
|
||||
#[cfg(feature = "termion")]
|
||||
mod termion;
|
||||
@@ -298,6 +302,45 @@ pub trait Backend {
|
||||
|
||||
/// Flush any buffered content to the terminal screen.
|
||||
fn flush(&mut self) -> io::Result<()>;
|
||||
|
||||
/// Converts the `Backend` into a [`Terminal`] instance.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::{Backend, CrosstermBackend};
|
||||
/// let terminal = CrosstermBackend::stdout().to_terminal()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
fn to_terminal(self) -> io::Result<Terminal<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Terminal::new(self)
|
||||
}
|
||||
|
||||
/// Converts the `Backend` into a [`Terminal`] instance with options.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::{
|
||||
/// backend::{Backend, CrosstermBackend},
|
||||
/// TerminalOptions, Viewport,
|
||||
/// };
|
||||
///
|
||||
/// let options = TerminalOptions {
|
||||
/// viewport: Viewport::Inline(10),
|
||||
/// };
|
||||
/// let terminal = CrosstermBackend::stdout().to_terminal_with_options(options)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
fn to_terminal_with_options(self, options: TerminalOptions) -> io::Result<Terminal<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Terminal::with_options(self, options)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -2,80 +2,83 @@
|
||||
//! the [Crossterm] crate to interact with the terminal.
|
||||
//!
|
||||
//! [Crossterm]: https://crates.io/crates/crossterm
|
||||
use std::io::{self, Write};
|
||||
use std::io;
|
||||
|
||||
#[cfg(feature = "underline-color")]
|
||||
use crossterm::style::SetUnderlineColor;
|
||||
|
||||
use crate::crossterm::style::SetUnderlineColor;
|
||||
use crate::{
|
||||
backend::{Backend, ClearType, WindowSize},
|
||||
buffer::Cell,
|
||||
crossterm::{
|
||||
cursor::{Hide, MoveTo, Show},
|
||||
event::{
|
||||
DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
|
||||
EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags,
|
||||
PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
|
||||
},
|
||||
execute, queue,
|
||||
style::{
|
||||
Attribute as CAttribute, Attributes as CAttributes, Color as CColor, Colors,
|
||||
ContentStyle, Print, SetAttribute, SetBackgroundColor, SetColors, SetForegroundColor,
|
||||
},
|
||||
terminal::{self, Clear},
|
||||
terminal::{
|
||||
disable_raw_mode, enable_raw_mode, Clear, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
},
|
||||
},
|
||||
layout::Size,
|
||||
prelude::Rect,
|
||||
layout::{Rect, Size},
|
||||
style::{Color, Modifier, Style},
|
||||
};
|
||||
|
||||
/// A [`Backend`] implementation that uses [Crossterm] to render to the terminal.
|
||||
///
|
||||
/// The `CrosstermBackend` struct is a wrapper around a writer implementing [`Write`], which is
|
||||
/// used to send commands to the terminal. It provides methods for drawing content, manipulating
|
||||
/// the cursor, and clearing the terminal screen.
|
||||
/// The `CrosstermBackend` struct is a wrapper around a writer implementing [`Write`], which is used
|
||||
/// to send commands to the terminal. It provides methods for drawing content, manipulating the
|
||||
/// cursor, and clearing the terminal screen.
|
||||
///
|
||||
/// Most applications should not call the methods on `CrosstermBackend` directly, but will instead
|
||||
/// use the [`Terminal`] struct, which provides a more ergonomic interface.
|
||||
/// Convenience methods ([`CrosstermBackend::stdout`] and [`CrosstermBackend::stderr`] are provided
|
||||
/// to create a `CrosstermBackend` with [`std::io::stdout`] or [`std::io::stderr`] as the writer.
|
||||
/// Additionally, these can be created with default settings to enable raw mode and switch to the
|
||||
/// alternate screen using [`CrosstermBackend::stdout_with_defaults`] or
|
||||
/// [`CrosstermBackend::stderr_with_defaults`].
|
||||
///
|
||||
/// Usually applications will enable raw mode and switch to alternate screen mode after creating
|
||||
/// a `CrosstermBackend`. This is done by calling [`crossterm::terminal::enable_raw_mode`] and
|
||||
/// [`crossterm::terminal::EnterAlternateScreen`] (and the corresponding disable/leave functions
|
||||
/// when the application exits). This is not done automatically by the backend because it is
|
||||
/// possible that the application may want to use the terminal for other purposes (like showing
|
||||
/// help text) before entering alternate screen mode.
|
||||
/// If the default settings are not desired, the `CrosstermBackend` can be configured using the
|
||||
/// `with_*` methods. These methods return an [`io::Result`] containing self so that they can be
|
||||
/// chained with other methods. The settings are restored when the `CrosstermBackend` is dropped.
|
||||
/// - [`CrosstermBackend::with_raw_mode`] enables raw mode for the terminal.
|
||||
/// - [`CrosstermBackend::with_alternate_screen`] switches to the alternate screen.
|
||||
/// - [`CrosstermBackend::with_mouse_capture`] enables mouse capture.
|
||||
/// - [`CrosstermBackend::with_bracketed_paste`] enables bracketed paste.
|
||||
/// - [`CrosstermBackend::with_focus_change`] enables focus change.
|
||||
/// - [`CrosstermBackend::with_keyboard_enhancement_flags`] enables keyboard enhancement flags.
|
||||
///
|
||||
/// If a backend is configured using the `with_*` methods, the settings are restored when the
|
||||
/// `CrosstermBackend` is dropped.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use std::io::{stderr, stdout};
|
||||
///
|
||||
/// use ratatui::{
|
||||
/// crossterm::{
|
||||
/// terminal::{
|
||||
/// disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
/// },
|
||||
/// ExecutableCommand,
|
||||
/// },
|
||||
/// prelude::*,
|
||||
/// backend::{Backend, CrosstermBackend},
|
||||
/// crossterm::event::KeyboardEnhancementFlags,
|
||||
/// };
|
||||
///
|
||||
/// let mut backend = CrosstermBackend::new(stdout());
|
||||
/// let mut terminal = CrosstermBackend::stdout_with_defaults()?.to_terminal()?;
|
||||
/// // or
|
||||
/// let backend = CrosstermBackend::new(stderr());
|
||||
/// let mut terminal = Terminal::new(backend)?;
|
||||
///
|
||||
/// enable_raw_mode()?;
|
||||
/// stdout().execute(EnterAlternateScreen)?;
|
||||
///
|
||||
/// terminal.clear()?;
|
||||
/// terminal.draw(|frame| {
|
||||
/// // -- snip --
|
||||
/// })?;
|
||||
///
|
||||
/// stdout().execute(LeaveAlternateScreen)?;
|
||||
/// disable_raw_mode()?;
|
||||
///
|
||||
/// let mut terminal = CrosstermBackend::stderr_with_defaults()?.to_terminal()?;
|
||||
/// // or with custom settings
|
||||
/// let mut terminal = CrosstermBackend::stdout()
|
||||
/// .with_raw_mode()?
|
||||
/// .with_alternate_screen()?
|
||||
/// .with_mouse_capture()?
|
||||
/// .with_bracketed_paste()?
|
||||
/// .with_focus_change()?
|
||||
/// .with_keyboard_enhancement_flags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)?
|
||||
/// .to_terminal()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
///
|
||||
/// See the the [Examples] directory for more examples. See the [`backend`] module documentation
|
||||
/// for more details on raw mode and alternate screen.
|
||||
/// See the the [Examples] directory for more examples. See the [`backend`] module documentation for
|
||||
/// more details on raw mode and alternate screen.
|
||||
///
|
||||
/// [`Write`]: std::io::Write
|
||||
/// [`Terminal`]: crate::terminal::Terminal
|
||||
@@ -83,26 +86,41 @@ use crate::{
|
||||
/// [Crossterm]: https://crates.io/crates/crossterm
|
||||
/// [Examples]: https://github.com/ratatui-org/ratatui/tree/main/examples/README.md
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct CrosstermBackend<W: Write> {
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct CrosstermBackend<W: io::Write> {
|
||||
/// The writer used to send commands to the terminal.
|
||||
writer: W,
|
||||
restore_raw_mode_on_drop: bool,
|
||||
restore_alternate_screen_on_drop: bool,
|
||||
restore_mouse_capture_on_drop: bool,
|
||||
restore_bracketed_paste_on_drop: bool,
|
||||
restore_focus_change_on_drop: bool,
|
||||
restore_keyboard_enhancement_flags_on_drop: bool,
|
||||
}
|
||||
|
||||
impl<W> CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
impl<W: io::Write> CrosstermBackend<W> {
|
||||
/// Creates a new `CrosstermBackend` with the given writer.
|
||||
///
|
||||
/// Applications will typically use [`CrosstermBackend::stdout`] or [`CrosstermBackend::stderr`]
|
||||
/// to create a `CrosstermBackend` with [`std::io::stdout`] or [`std::io::stderr`] as the
|
||||
/// writer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use std::io::stdout;
|
||||
/// # use ratatui::prelude::*;
|
||||
/// let backend = CrosstermBackend::new(stdout());
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::new(std::io::stdout());
|
||||
/// ```
|
||||
pub const fn new(writer: W) -> Self {
|
||||
Self { writer }
|
||||
Self {
|
||||
writer,
|
||||
restore_raw_mode_on_drop: false,
|
||||
restore_alternate_screen_on_drop: false,
|
||||
restore_mouse_capture_on_drop: false,
|
||||
restore_bracketed_paste_on_drop: false,
|
||||
restore_focus_change_on_drop: false,
|
||||
restore_keyboard_enhancement_flags_on_drop: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the writer.
|
||||
@@ -127,10 +145,335 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Write for CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
impl CrosstermBackend<io::Stdout> {
|
||||
/// Creates a new `CrosstermBackend` with `std::io::stdout`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stdout();
|
||||
/// ```
|
||||
pub fn stdout() -> Self {
|
||||
Self::new(io::stdout())
|
||||
}
|
||||
|
||||
/// Creates a new `CrosstermBackend` with `std::io::stdout` and default settings.
|
||||
///
|
||||
/// This enables raw mode and switches to the alternate screen. Mouse support is not enabled.
|
||||
///
|
||||
/// Raw mode and alternate screen are restored when the `CrosstermBackend` is dropped.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stdout_with_defaults()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn stdout_with_defaults() -> io::Result<Self> {
|
||||
Self::stdout().with_defaults()
|
||||
}
|
||||
}
|
||||
|
||||
impl CrosstermBackend<io::Stderr> {
|
||||
/// Creates a new `CrosstermBackend` with `std::io::stderr`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stderr();
|
||||
/// ```
|
||||
pub fn stderr() -> Self {
|
||||
Self::new(io::stderr())
|
||||
}
|
||||
|
||||
/// Creates a new `CrosstermBackend` with `std::io::stderr` and default settings.
|
||||
///
|
||||
/// This enables raw mode and switches to the alternate screen. Mouse support is not enabled.
|
||||
///
|
||||
/// Raw mode and alternate screen are restored when the `CrosstermBackend` is dropped.
|
||||
///
|
||||
/// If the `color-eyre` feature is enabled, the color-eyre panic and error report hooks are
|
||||
/// installed.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stderr_with_defaults()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn stderr_with_defaults() -> io::Result<Self> {
|
||||
Self::stderr().with_defaults()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: io::Write> CrosstermBackend<W> {
|
||||
/// Enables default settings for the terminal backend.
|
||||
///
|
||||
/// This enables raw mode and switches to the alternate screen. Mouse support is not enabled.
|
||||
///
|
||||
/// If the `color-eyre` feature is enabled, the color-eyre panic and error report hooks are
|
||||
/// installed. Otherwise, a panic hook is installed that resets the terminal to its default
|
||||
/// state before panicking.
|
||||
///
|
||||
/// Returns an [`io::Result`] containing self so that it can be chained with other methods.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::backend::CrosstermBackend;
|
||||
///
|
||||
/// let backend = CrosstermBackend::stdout().with_defaults()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn with_defaults(self) -> io::Result<Self> {
|
||||
let backend = self.with_raw_mode()?.with_alternate_screen()?;
|
||||
#[cfg(feature = "color-eyre")]
|
||||
let backend = backend.with_color_eyre_hooks()?;
|
||||
#[cfg(not(feature = "color-eyre"))]
|
||||
let backend = backend.with_panic_hook();
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
/// Enables raw mode for the terminal.
|
||||
///
|
||||
/// Returns an [`io::Result`] containing self so that it can be chained with other methods.
|
||||
///
|
||||
/// Raw mode is restored when the `CrosstermBackend` is dropped.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stdout().with_raw_mode()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn with_raw_mode(mut self) -> io::Result<Self> {
|
||||
enable_raw_mode()?;
|
||||
self.restore_raw_mode_on_drop = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Enables raw mode for the terminal and switches to the alternate screen.
|
||||
///
|
||||
/// Returns an [`io::Result`] containing self so that it can be chained with other methods.
|
||||
///
|
||||
/// Alternate screen is restored when the `CrosstermBackend` is dropped.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stdout().with_alternate_screen()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn with_alternate_screen(mut self) -> io::Result<Self> {
|
||||
execute!(self.writer, EnterAlternateScreen)?;
|
||||
self.restore_alternate_screen_on_drop = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Enables mouse capture for the terminal.
|
||||
///
|
||||
/// Returns an [`io::Result`] containing self so that it can be chained with other methods.
|
||||
///
|
||||
/// Mouse capture is disabled when the `CrosstermBackend` is dropped.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stdout().with_mouse_capture()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn with_mouse_capture(mut self) -> io::Result<Self> {
|
||||
execute!(self.writer, EnableMouseCapture)?;
|
||||
self.restore_mouse_capture_on_drop = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Enables bracketed paste for the terminal.
|
||||
///
|
||||
/// Returns an [`io::Result`] containing self so that it can be chained with other methods.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stdout().with_bracketed_paste()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn with_bracketed_paste(mut self) -> io::Result<Self> {
|
||||
execute!(self.writer, EnableBracketedPaste)?;
|
||||
self.restore_bracketed_paste_on_drop = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Enables focus change for the terminal.
|
||||
///
|
||||
/// Returns an [`io::Result`] containing self so that it can be chained with other methods.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// let backend = CrosstermBackend::stdout().with_focus_change()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
pub fn with_focus_change(mut self) -> io::Result<Self> {
|
||||
execute!(self.writer, EnableFocusChange)?;
|
||||
self.restore_focus_change_on_drop = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Enables keyboard enhancement flags for the terminal.
|
||||
///
|
||||
/// Returns an [`io::Result`] containing self so that it can be chained with other methods.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::{backend::CrosstermBackend, crossterm::event::KeyboardEnhancementFlags};
|
||||
///
|
||||
/// let backend = CrosstermBackend::stdout()
|
||||
/// .with_keyboard_enhancement_flags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn with_keyboard_enhancement_flags(
|
||||
mut self,
|
||||
flags: KeyboardEnhancementFlags,
|
||||
) -> io::Result<Self> {
|
||||
execute!(self.writer, PushKeyboardEnhancementFlags(flags))?;
|
||||
self.restore_keyboard_enhancement_flags_on_drop = true;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Installs a panic hook that resets the terminal to its default state before panicking.
|
||||
///
|
||||
/// This is a convenience method that sets up the panic hook for the terminal backend.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::backend::CrosstermBackend;
|
||||
///
|
||||
/// let backend = CrosstermBackend::stdout().with_panic_hook()?;
|
||||
/// ```
|
||||
#[cfg(not(feature = "color-eyre"))]
|
||||
#[must_use]
|
||||
pub fn with_panic_hook(self) -> Self {
|
||||
use std::panic;
|
||||
|
||||
let hook = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
let _ = CrosstermBackend::reset(io::stderr());
|
||||
hook(info);
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
/// Installs the color-eyre panic and error report hooks.
|
||||
///
|
||||
/// This is a convenience method that sets up the color-eyre hooks for the terminal backend.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use ratatui::backend::CrosstermBackend;
|
||||
///
|
||||
/// let backend = CrosstermBackend::stdout().with_color_eyre_hooks()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
#[cfg(feature = "color-eyre")]
|
||||
pub fn with_color_eyre_hooks(self) -> io::Result<Self> {
|
||||
use std::{io::stderr, panic};
|
||||
|
||||
use color_eyre::{config::HookBuilder, eyre};
|
||||
|
||||
let (panic, error) = HookBuilder::default().into_hooks();
|
||||
let panic = panic.into_panic_hook();
|
||||
let error = error.into_eyre_hook();
|
||||
eyre::set_hook(Box::new(move |e| {
|
||||
// ignore errors here because we are already in an error state
|
||||
let _ = CrosstermBackend::reset(stderr());
|
||||
error(e)
|
||||
}))
|
||||
.map_err(io::Error::other)?;
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
// ignore errors here because we are already in an error state
|
||||
let _ = CrosstermBackend::reset(stderr());
|
||||
panic(info);
|
||||
}));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Resets the terminal to its default state.
|
||||
///
|
||||
/// - Disables raw mode
|
||||
/// - Disables mouse capture
|
||||
/// - Leaves the alternate screen
|
||||
/// - Disables bracketed paste
|
||||
/// - Disables focus change
|
||||
/// - Pops keyboard enhancement flags
|
||||
///
|
||||
/// This method is an associated method rather than an instance method to make it possible to
|
||||
/// call without having a `CrosstermBackend` instance. This is often useful in the context of
|
||||
/// error / panic handling.
|
||||
///
|
||||
/// If you have created a `CrosstermBackend` using the `with_*` methods, the settings are
|
||||
/// restored when the `CrosstermBackend` is dropped, so you do not need to call this method
|
||||
/// manually.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use ratatui::backend::CrosstermBackend;
|
||||
/// CrosstermBackend::reset(std::io::stderr())?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn reset(mut writer: W) -> io::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
writer,
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture,
|
||||
DisableBracketedPaste,
|
||||
DisableFocusChange,
|
||||
PopKeyboardEnhancementFlags
|
||||
)?;
|
||||
writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: io::Write> Drop for CrosstermBackend<W> {
|
||||
fn drop(&mut self) {
|
||||
// note that these are not checked for errors because there is nothing that can be done if
|
||||
// they fail. The terminal is likely in a bad state, and the application is exiting anyway.
|
||||
if self.restore_raw_mode_on_drop {
|
||||
let _ = disable_raw_mode();
|
||||
}
|
||||
if self.restore_mouse_capture_on_drop {
|
||||
let _ = execute!(self.writer, DisableMouseCapture);
|
||||
}
|
||||
if self.restore_alternate_screen_on_drop {
|
||||
let _ = execute!(self.writer, LeaveAlternateScreen);
|
||||
}
|
||||
if self.restore_bracketed_paste_on_drop {
|
||||
let _ = execute!(self.writer, DisableBracketedPaste);
|
||||
}
|
||||
if self.restore_focus_change_on_drop {
|
||||
let _ = execute!(self.writer, DisableFocusChange);
|
||||
}
|
||||
if self.restore_keyboard_enhancement_flags_on_drop {
|
||||
let _ = execute!(self.writer, PopKeyboardEnhancementFlags);
|
||||
}
|
||||
let _ = self.writer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: io::Write> io::Write for CrosstermBackend<W> {
|
||||
/// Writes a buffer of bytes to the underlying buffer.
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.writer.write(buf)
|
||||
@@ -142,10 +485,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Backend for CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
impl<W: io::Write> Backend for CrosstermBackend<W> {
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||
@@ -247,7 +587,7 @@ where
|
||||
}
|
||||
|
||||
fn size(&self) -> io::Result<Rect> {
|
||||
let (width, height) = terminal::size()?;
|
||||
let (width, height) = crossterm::terminal::size()?;
|
||||
Ok(Rect::new(0, 0, width, height))
|
||||
}
|
||||
|
||||
@@ -257,7 +597,7 @@ where
|
||||
rows,
|
||||
width,
|
||||
height,
|
||||
} = terminal::window_size()?;
|
||||
} = crossterm::terminal::window_size()?;
|
||||
Ok(WindowSize {
|
||||
columns_rows: Size {
|
||||
width: columns,
|
||||
@@ -333,11 +673,7 @@ struct ModifierDiff {
|
||||
}
|
||||
|
||||
impl ModifierDiff {
|
||||
fn queue<W>(self, mut w: W) -> io::Result<()>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
//use crossterm::Attribute;
|
||||
fn queue<W: io::Write>(self, mut w: W) -> io::Result<()> {
|
||||
let removed = self.from - self.to;
|
||||
if removed.contains(Modifier::REVERSED) {
|
||||
queue!(w, SetAttribute(CAttribute::NoReverse))?;
|
||||
|
||||
Reference in New Issue
Block a user