Skip to content

Commit c38ec5f

Browse files
joshkasayanarijit
authored andcommitted
feat: rewrite ratatui example
Simplify the ratatui example: - replace project with single file - simplify app logic - extract small functions - update to latest ratatui approaches (terminal, styles, layout, conversions etc.) - use crossterm from direct import rather than from ratatui::crossterm
1 parent 0cf21a1 commit c38ec5f

File tree

7 files changed

+148
-204
lines changed

7 files changed

+148
-204
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"rust-analyzer.cargo.features": ["crossterm"]
3+
}

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ crossterm = { version = "0.28.1" }
2929

3030
[[example]]
3131
name = "crossterm_input"
32-
path = "./examples/crossterm_input.rs"
3332
required-features = ["crossterm"]
3433

3534
[[example]]
3635
name = "termion_input"
37-
path = "./examples/termion_input.rs"
3836
required-features = ["termion"]
37+
38+
[[example]]
39+
name = "ratatui_input"
40+
required-features = ["crossterm"]

examples/ratatui-input/.gitignore

Lines changed: 0 additions & 2 deletions
This file was deleted.

examples/ratatui-input/Cargo.toml

Lines changed: 0 additions & 12 deletions
This file was deleted.

examples/ratatui-input/rustfmt.toml

Lines changed: 0 additions & 4 deletions
This file was deleted.

examples/ratatui-input/src/main.rs

Lines changed: 0 additions & 184 deletions
This file was deleted.

examples/ratatui_input.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use std::io;
2+
3+
use crossterm::event::{self, Event, KeyCode};
4+
use ratatui::{
5+
layout::{Constraint, Layout, Rect},
6+
style::{Color, Style, Stylize},
7+
text::{Line, ToSpan},
8+
widgets::{Block, List, Paragraph},
9+
DefaultTerminal, Frame,
10+
};
11+
use tui_input::backend::crossterm::EventHandler;
12+
use tui_input::Input;
13+
14+
fn main() -> io::Result<()> {
15+
let mut terminal = ratatui::init();
16+
let result = App::default().run(&mut terminal);
17+
ratatui::restore();
18+
result
19+
}
20+
21+
/// App holds the state of the application
22+
#[derive(Debug, Default)]
23+
struct App {
24+
/// Current value of the input box
25+
input: Input,
26+
/// Current input mode
27+
input_mode: InputMode,
28+
/// History of recorded messages
29+
messages: Vec<String>,
30+
}
31+
32+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
33+
enum InputMode {
34+
#[default]
35+
Normal,
36+
Editing,
37+
}
38+
39+
impl App {
40+
fn run(mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
41+
loop {
42+
terminal.draw(|frame| self.render(frame))?;
43+
44+
let event = event::read()?;
45+
if let Event::Key(key) = event {
46+
match self.input_mode {
47+
InputMode::Normal => match key.code {
48+
KeyCode::Char('e') => self.start_editing(),
49+
KeyCode::Char('q') => return Ok(()), // exit
50+
_ => {}
51+
},
52+
InputMode::Editing => match key.code {
53+
KeyCode::Enter => self.push_message(),
54+
KeyCode::Esc => self.stop_editing(),
55+
_ => {
56+
self.input.handle_event(&event);
57+
}
58+
},
59+
}
60+
}
61+
}
62+
}
63+
64+
fn start_editing(&mut self) {
65+
self.input_mode = InputMode::Editing
66+
}
67+
68+
fn stop_editing(&mut self) {
69+
self.input_mode = InputMode::Normal
70+
}
71+
72+
fn push_message(&mut self) {
73+
self.messages.push(self.input.value().into());
74+
self.input.reset();
75+
}
76+
77+
fn render(&self, frame: &mut Frame) {
78+
let [header_area, input_area, messages_area] = Layout::vertical([
79+
Constraint::Length(1),
80+
Constraint::Length(3),
81+
Constraint::Min(1),
82+
])
83+
.areas(frame.area());
84+
85+
self.render_help_message(frame, header_area);
86+
self.render_input(frame, input_area);
87+
self.render_messages(frame, messages_area);
88+
}
89+
90+
fn render_help_message(&self, frame: &mut Frame, area: Rect) {
91+
let help_message = Line::from_iter(match self.input_mode {
92+
InputMode::Normal => [
93+
"Press ".to_span(),
94+
"q".bold(),
95+
" to exit, ".to_span(),
96+
"e".bold(),
97+
" to start editing.".to_span(),
98+
],
99+
InputMode::Editing => [
100+
"Press ".to_span(),
101+
"Esc".bold(),
102+
" to stop editing, ".to_span(),
103+
"Enter".bold(),
104+
" to record the message".to_span(),
105+
],
106+
});
107+
frame.render_widget(help_message, area);
108+
}
109+
110+
fn render_input(&self, frame: &mut Frame, area: Rect) {
111+
// keep 2 for borders and 1 for cursor
112+
let width = area.width.max(3) - 3;
113+
let scroll = self.input.visual_scroll(width as usize);
114+
let style = match self.input_mode {
115+
InputMode::Normal => Style::default(),
116+
InputMode::Editing => Color::Yellow.into(),
117+
};
118+
let input = Paragraph::new(self.input.value())
119+
.style(style)
120+
.scroll((0, scroll as u16))
121+
.block(Block::bordered().title("Input"));
122+
frame.render_widget(input, area);
123+
124+
if self.input_mode == InputMode::Editing {
125+
// Ratatui hides the cursor unless it's explicitly set. Position the cursor past the
126+
// end of the input text and one line down from the border to the input line
127+
let x = self.input.visual_cursor().max(scroll) - scroll + 1;
128+
frame.set_cursor_position((area.x + x as u16, area.y + 1))
129+
}
130+
}
131+
132+
fn render_messages(&self, frame: &mut Frame, area: Rect) {
133+
let messages = self
134+
.messages
135+
.iter()
136+
.enumerate()
137+
.map(|(i, message)| format!("{}: {}", i, message));
138+
let messages = List::new(messages).block(Block::bordered().title("Messages"));
139+
frame.render_widget(messages, area);
140+
}
141+
}

0 commit comments

Comments
 (0)