aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/rust/main.rs142
1 files changed, 133 insertions, 9 deletions
diff --git a/src/main/rust/main.rs b/src/main/rust/main.rs
index 77ab480..4796c46 100644
--- a/src/main/rust/main.rs
+++ b/src/main/rust/main.rs
@@ -1,19 +1,143 @@
-use ratatui::{DefaultTerminal, Frame};
+use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
+use ratatui::{
+ DefaultTerminal, Frame,
+ buffer::Buffer,
+ layout::Rect,
+ style::Stylize,
+ symbols::border,
+ text::{Line, Text},
+ widgets::{Block, Paragraph, Widget},
+};
use std::io::Result;
+#[derive(Debug, Default)]
+pub struct App {
+ counter: u8,
+ exit: bool,
+}
+
fn main() -> Result<()> {
- ratatui::run(app)
+ ratatui::run(|term| App::default().run(term))
}
-fn app(terminal: &mut DefaultTerminal) -> Result<()> {
- loop {
- terminal.draw(render)?;
- if crossterm::event::read()?.is_key_press() {
- break Ok(());
+impl App {
+ /// runs the application's main loop until the user quits
+ pub fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<()> {
+ while !self.exit {
+ terminal.draw(|frame| self.draw(frame))?;
+ self.handle_events()?;
}
+ Ok(())
+ }
+
+ fn draw(&self, frame: &mut Frame) {
+ frame.render_widget(self, frame.area());
+ }
+
+ fn handle_events(&mut self) -> Result<()> {
+ match event::read()? {
+ // it's important to check that the event is a key press event as
+ // crossterm also emits key release and repeat events on Windows.
+ 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 decrement_counter(&mut self) {
+ self.counter -= 1;
+ }
+
+ fn increment_counter(&mut self) {
+ self.counter += 1;
}
}
-fn render(frame: &mut Frame) {
- frame.render_widget("hello world", frame.area());
+impl Widget for &App {
+ fn render(self, area: Rect, buf: &mut Buffer) {
+ let title = Line::from(" Counter App Tutorial ".bold());
+ let instructions = Line::from(vec![
+ " Decrement ".into(),
+ "<Left>".blue().bold(),
+ " Increment ".into(),
+ "<Right>".blue().bold(),
+ " Quit ".into(),
+ "<Q> ".blue().bold(),
+ ]);
+ let block = Block::bordered()
+ .title(title.centered())
+ .title_bottom(instructions.centered())
+ .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);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ 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);
+
+ assert_eq!(buf, expected);
+ }
+
+ #[test]
+ fn handle_key_event() {
+ 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!(app.exit);
+ }
}