Initial testing
This commit is contained in:
commit
16ce05f554
6 changed files with 992 additions and 0 deletions
125
src/main.rs
Normal file
125
src/main.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use std::panic;
|
||||
use std::io;
|
||||
use std::io::Result;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{
|
||||
self,
|
||||
Event,
|
||||
KeyCode,
|
||||
KeyEvent,
|
||||
KeyEventKind
|
||||
},
|
||||
layout::{Alignment, Rect},
|
||||
style::Stylize,
|
||||
symbols::border,
|
||||
text::{Line, Text},
|
||||
widgets::{
|
||||
block::{Position, Title},
|
||||
Block, Paragraph, Widget,
|
||||
},
|
||||
Frame,
|
||||
};
|
||||
|
||||
mod tui;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct App {
|
||||
counter: u8,
|
||||
exit: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Setup panic handling
|
||||
let default_panic = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
let _= tui::restore();
|
||||
default_panic(info);
|
||||
}));
|
||||
|
||||
let mut terminal = tui::init()?;
|
||||
App::default().run(&mut terminal)?;
|
||||
tui::restore()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl App {
|
||||
// Main app loop until quit
|
||||
pub fn run(&mut self, terminal: &mut tui::Tui) -> Result<()> {
|
||||
while !self.exit {
|
||||
terminal.draw(|frame| self.render_frame(frame))?;
|
||||
self.handle_events()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_frame(&self, frame: &mut Frame) {
|
||||
frame.render_widget(self, frame.size());
|
||||
}
|
||||
|
||||
fn handle_events(&mut self) -> io::Result<()> {
|
||||
match event::read()? {
|
||||
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
|
||||
self.handle_key_event(key_event)
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
match key_event.code {
|
||||
KeyCode::Char('q') => self.exit(),
|
||||
KeyCode::Left => self.decrement_counter(),
|
||||
KeyCode::Right => self.increment_counter(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
self.exit = true;
|
||||
}
|
||||
|
||||
fn increment_counter(&mut self) {
|
||||
self.counter += 1;
|
||||
}
|
||||
|
||||
fn decrement_counter(&mut self) {
|
||||
self.counter -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let title = Title::from(" Counter App Tutorial ".bold());
|
||||
let instructions = Title::from(Line::from(vec![
|
||||
" Decrement ".into(),
|
||||
"<Left>".blue().bold(),
|
||||
" Increment ".into(),
|
||||
"<Right>".blue().bold(),
|
||||
" Quit ".into(),
|
||||
"<Q> ".blue().bold(),
|
||||
]));
|
||||
let block = Block::bordered()
|
||||
.title(title.alignment(Alignment::Center))
|
||||
.title(
|
||||
instructions
|
||||
.alignment(Alignment::Center)
|
||||
.position(Position::Bottom),
|
||||
)
|
||||
.border_set(border::THICK);
|
||||
|
||||
let counter_text = Text::from(vec![Line::from(vec![
|
||||
"Value: ".into(),
|
||||
self.counter.to_string().yellow(),
|
||||
])]);
|
||||
|
||||
Paragraph::new(counter_text)
|
||||
.centered()
|
||||
.block(block)
|
||||
.render(area, buf);
|
||||
}
|
||||
}
|
45
src/test.rs
Normal file
45
src/test.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use super::*;
|
||||
use ratatui::style::Style;
|
||||
|
||||
#[test]
|
||||
fn render() {
|
||||
let app = App::default();
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 50, 4));
|
||||
|
||||
app.render(buf.area, &mut buf);
|
||||
|
||||
let mut expected = Buffer::with_lines(vec![
|
||||
"┏━━━━━━━━━━━━━ Counter App Tutorial ━━━━━━━━━━━━━┓",
|
||||
"┃ Value: 0 ┃",
|
||||
"┃ ┃",
|
||||
"┗━ Decrement <Left> Increment <Right> Quit <Q> ━━┛",
|
||||
]);
|
||||
let title_style = Style::new().bold();
|
||||
let counter_style = Style::new().yellow();
|
||||
let key_style = Style::new().blue().bold();
|
||||
expected.set_style(Rect::new(14, 0, 22, 1), title_style);
|
||||
expected.set_style(Rect::new(28, 1, 1, 1), counter_style);
|
||||
expected.set_style(Rect::new(13, 3, 6, 1), key_style);
|
||||
expected.set_style(Rect::new(30, 3, 7, 1), key_style);
|
||||
expected.set_style(Rect::new(43, 3, 4, 1), key_style);
|
||||
|
||||
// note ratatui also has an assert_buffer_eq! macro that can be used to
|
||||
// compare buffers and display the differences in a more readable way
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_key_event() -> io::Result<()> {
|
||||
let mut app = App::default();
|
||||
app.handle_key_event(KeyCode::Right.into());
|
||||
assert_eq!(app.counter, 1);
|
||||
|
||||
app.handle_key_event(KeyCode::Left.into());
|
||||
assert_eq!(app.counter, 0);
|
||||
|
||||
let mut app = App::default();
|
||||
app.handle_key_event(KeyCode::Char('q').into());
|
||||
assert_eq!(app.exit, true);
|
||||
|
||||
Ok(())
|
||||
}
|
35
src/tui.rs
Normal file
35
src/tui.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use std::io::{self, stdout, Stdout};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
crossterm::{
|
||||
execute,
|
||||
terminal::{
|
||||
disable_raw_mode,
|
||||
enable_raw_mode,
|
||||
EnterAlternateScreen,
|
||||
LeaveAlternateScreen,
|
||||
},
|
||||
event::{
|
||||
EnableMouseCapture,
|
||||
DisableMouseCapture,
|
||||
},
|
||||
},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
||||
|
||||
pub fn init() -> io::Result<Tui> {
|
||||
execute!(stdout(), EnterAlternateScreen)?;
|
||||
execute!(stdout(), EnableMouseCapture)?;
|
||||
enable_raw_mode()?;
|
||||
Terminal::new(CrosstermBackend::new(stdout()))
|
||||
}
|
||||
|
||||
pub fn restore() -> io::Result<()> {
|
||||
execute!(stdout(), LeaveAlternateScreen)?;
|
||||
execute!(stdout(), DisableMouseCapture)?;
|
||||
disable_raw_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue