Skip to content

Commit f1af005

Browse files
committed
refactor(tui): use built-in list and remove mouse events
1 parent 8fdf418 commit f1af005

File tree

4 files changed

+59
-131
lines changed

4 files changed

+59
-131
lines changed

git-cliff-tui/src/event.rs

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ use ratatui::crossterm::event::{
1010
KeyEvent,
1111
KeyEventKind,
1212
KeyModifiers,
13-
MouseButton,
1413
MouseEvent,
15-
MouseEventKind,
1614
};
17-
use ratatui::layout::Position;
1815
use std::sync::mpsc;
1916
use std::thread;
2017
use std::time::{
@@ -132,19 +129,10 @@ pub fn handle_key_events(
132129
}
133130
}
134131
KeyCode::Char('k') | KeyCode::Char('K') | KeyCode::Up => {
135-
state.selected_index = if state.selected_index == 0 {
136-
state.configs.len() - 1
137-
} else {
138-
state.selected_index - 1
139-
}
132+
state.list_state.select_previous();
140133
}
141134
KeyCode::Char('j') | KeyCode::Char('J') | KeyCode::Down => {
142-
state.selected_index = if state.selected_index >= state.configs.len() - 1
143-
{
144-
0
145-
} else {
146-
state.selected_index + 1
147-
}
135+
state.list_state.select_next();
148136
}
149137
KeyCode::Char('h') | KeyCode::Char('H') | KeyCode::Left => {
150138
state.markdown.scroll_index =
@@ -159,10 +147,7 @@ pub fn handle_key_events(
159147
sender.send(Event::Generate)?;
160148
}
161149
}
162-
KeyCode::Enter => {
163-
state.markdown.config_index = state.selected_index;
164-
sender.send(Event::Generate)?
165-
}
150+
KeyCode::Enter => sender.send(Event::Generate)?,
166151
KeyCode::Char('a') | KeyCode::Char('A') => {
167152
state.autoload = !state.autoload;
168153
}
@@ -177,35 +162,3 @@ pub fn handle_key_events(
177162
}
178163
Ok(())
179164
}
180-
181-
/// Handles the mouse events and updates the state.
182-
pub(crate) fn handle_mouse_events(
183-
mouse_event: MouseEvent,
184-
sender: mpsc::Sender<Event>,
185-
state: &mut State,
186-
) -> Result<()> {
187-
match mouse_event.kind {
188-
MouseEventKind::Moved => {
189-
let position = Position::new(mouse_event.column, mouse_event.row);
190-
state.configs.iter_mut().for_each(|config| {
191-
config.is_hovered = config.area.contains(position);
192-
})
193-
}
194-
MouseEventKind::Down(MouseButton::Left) => {
195-
if let Some(i) = state.configs.iter().position(|p| p.is_hovered) {
196-
state.selected_index = i;
197-
sender.send(Event::Generate)?;
198-
}
199-
}
200-
MouseEventKind::ScrollUp => {
201-
state.markdown.scroll_index =
202-
state.markdown.scroll_index.saturating_sub(1);
203-
}
204-
MouseEventKind::ScrollDown => {
205-
state.markdown.scroll_index =
206-
state.markdown.scroll_index.saturating_add(1);
207-
}
208-
_ => {}
209-
}
210-
Ok(())
211-
}

git-cliff-tui/src/main.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,7 @@ fn main() -> Result<()> {
101101
events.sender.clone(),
102102
&mut state,
103103
)?,
104-
Event::Mouse(mouse_event) => event::handle_mouse_events(
105-
mouse_event,
106-
events.sender.clone(),
107-
&mut state,
108-
)?,
104+
Event::Mouse(_) => {}
109105
Event::Resize(_, _) => {}
110106
Event::Generate | Event::AutoGenerate => {
111107
if event == Event::AutoGenerate && !state.autoload {
@@ -114,8 +110,11 @@ fn main() -> Result<()> {
114110
let sender = events.sender.clone();
115111
let args = state.args.clone();
116112
state.is_generating = true;
117-
state.args.config =
118-
PathBuf::from(state.configs[state.selected_index].file.clone());
113+
state.args.config = PathBuf::from(
114+
state.configs[state.list_state.selected().unwrap_or_default()]
115+
.file
116+
.clone(),
117+
);
119118
thread::spawn(move || {
120119
let mut output = Vec::new();
121120
sender

git-cliff-tui/src/state.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use git_cliff::args::Args;
33
use git_cliff::core::embed::BuiltinConfig;
44
use md_tui::nodes::root::ComponentRoot;
55
use ratatui::layout::Rect;
6+
use ratatui::widgets::ListState;
67
use std::error;
78
use throbber_widgets_tui::ThrobberState;
89

@@ -13,11 +14,7 @@ pub type Result<T> = std::result::Result<T, Box<dyn error::Error>>;
1314
#[derive(Debug, Default)]
1415
pub struct Config {
1516
/// Name/path of the configuration.
16-
pub file: String,
17-
/// Widget area.
18-
pub area: Rect,
19-
/// Is the widget hovered?
20-
pub is_hovered: bool,
17+
pub file: String,
2118
}
2219

2320
/// Markdown content.
@@ -42,8 +39,8 @@ pub struct State {
4239
pub is_running: bool,
4340
/// Configuration files.
4441
pub configs: Vec<Config>,
45-
/// Index of the selected configuration.
46-
pub selected_index: usize,
42+
/// The state of the list.
43+
pub list_state: ListState,
4744
/// Changelog contents.
4845
pub changelog: String,
4946
/// Error message.
@@ -67,9 +64,7 @@ impl State {
6764
pub fn new(args: Args) -> Result<Self> {
6865
let configs = BuiltinConfig::iter()
6966
.map(|file| Config {
70-
file: file.to_string(),
71-
area: Rect::default(),
72-
is_hovered: false,
67+
file: file.to_string(),
7368
})
7469
.collect();
7570
Ok(Self {
@@ -78,7 +73,11 @@ impl State {
7873
is_toggled: true,
7974
is_generating: false,
8075
configs,
81-
selected_index: 0,
76+
list_state: {
77+
let mut list_state = ListState::default();
78+
list_state.select_first();
79+
list_state
80+
},
8281
changelog: String::new(),
8382
error: None,
8483
markdown: Markdown::default(),

git-cliff-tui/src/ui.rs

Lines changed: 40 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use ratatui::{
2121
widgets::{
2222
Block,
2323
BorderType,
24+
List,
25+
ListItem,
2426
Paragraph,
2527
Scrollbar,
2628
ScrollbarOrientation,
@@ -73,7 +75,7 @@ pub fn render(state: &mut State, frame: &mut Frame) {
7375
}
7476
}
7577

76-
fn render_key_bindings(frame: &mut Frame, rect: Rect) {
78+
fn render_key_bindings(frame: &mut Frame, area: Rect) {
7779
frame.render_widget(
7880
Paragraph::new(
7981
Line::default()
@@ -93,69 +95,44 @@ fn render_key_bindings(frame: &mut Frame, rect: Rect) {
9395
)
9496
.alignment(Alignment::Center),
9597
),
96-
rect,
98+
area,
9799
);
98100
}
99101

100-
fn render_list(state: &mut State, frame: &mut Frame, rect: Rect) {
101-
frame.render_widget(
102-
Block::bordered()
103-
.title_top("|Config|".yellow())
104-
.title_alignment(Alignment::Center)
105-
.border_type(BorderType::Rounded)
106-
.border_style(Style::default().fg(Color::Rgb(100, 100, 100))),
107-
rect,
108-
);
102+
fn render_list(state: &mut State, frame: &mut Frame, area: Rect) {
109103
if !state.configs.is_empty() {
110-
let rect =
111-
Layout::vertical([Constraint::Min(1), Constraint::Percentage(100)])
112-
.split(rect)[1];
113-
let borders_line = 2;
114-
let item_count = (rect.height - borders_line) as usize;
115-
let start_offset = (state.selected_index + 1).saturating_sub(item_count);
116-
let rects =
117-
Layout::vertical([Constraint::Length(1)].repeat(item_count)).split(rect);
118-
for (i, config) in state
104+
let items = state
119105
.configs
120-
.iter_mut()
121-
.skip(start_offset)
122-
.take(item_count)
123-
.enumerate()
124-
{
125-
let mut style = Style::new();
126-
if config.is_hovered {
127-
style = style.yellow()
128-
} else if state.selected_index == i + start_offset {
129-
style = style.yellow();
130-
}
131-
let item = Layout::horizontal([
132-
Constraint::Min(1),
133-
Constraint::Percentage(100),
134-
])
135-
.split(rects[i]);
136-
config.area = rects[i];
137-
frame.render_widget(
138-
Paragraph::new(Line::from(config.file.clone()).style(style)),
139-
item[1],
140-
);
141-
}
142-
if state.configs.len() > rect.height as usize - 2 {
143-
frame.render_stateful_widget(
144-
Scrollbar::new(ScrollbarOrientation::VerticalRight)
145-
.begin_symbol(Some("↑"))
146-
.end_symbol(Some("↓")),
147-
rect.inner(Margin {
148-
vertical: 1,
149-
horizontal: 0,
150-
}),
151-
&mut ScrollbarState::new(item_count).position(state.selected_index),
152-
);
153-
}
106+
.iter()
107+
.map(|c| ListItem::new(c.file.to_string()))
108+
.collect::<Vec<ListItem>>();
109+
let list = List::new(items)
110+
.block(
111+
Block::bordered()
112+
.title_top("|Config|".yellow())
113+
.title_alignment(Alignment::Center)
114+
.border_type(BorderType::Rounded)
115+
.border_style(Style::default().fg(Color::Rgb(100, 100, 100))),
116+
)
117+
.style(Style::new().white())
118+
.highlight_style(Style::new().reversed());
119+
frame.render_stateful_widget(list, area, &mut state.list_state);
120+
frame.render_stateful_widget(
121+
Scrollbar::new(ScrollbarOrientation::VerticalRight)
122+
.begin_symbol(Some("↑"))
123+
.end_symbol(Some("↓")),
124+
area.inner(Margin {
125+
vertical: 1,
126+
horizontal: 0,
127+
}),
128+
&mut ScrollbarState::new(state.configs.len())
129+
.position(state.list_state.selected().unwrap_or_default()),
130+
);
154131
}
155132
}
156133

157-
fn render_changelog(state: &mut State, frame: &mut Frame, rect: Rect) {
158-
state.markdown.area = rect.inner(Margin {
134+
fn render_changelog(state: &mut State, frame: &mut Frame, area: Rect) {
135+
state.markdown.area = area.inner(Margin {
159136
horizontal: 1,
160137
vertical: 1,
161138
});
@@ -222,7 +199,7 @@ fn render_changelog(state: &mut State, frame: &mut Frame, rect: Rect) {
222199
Line::from(format!("|{}|", env!("CARGO_PKG_VERSION")))
223200
.right_aligned(),
224201
),
225-
rect,
202+
area,
226203
);
227204
if let Some(component) = &mut state.markdown.component {
228205
let mut height = 2;
@@ -241,7 +218,7 @@ fn render_changelog(state: &mut State, frame: &mut Frame, rect: Rect) {
241218
Scrollbar::new(ScrollbarOrientation::VerticalRight)
242219
.begin_symbol(Some("↑"))
243220
.end_symbol(Some("↓")),
244-
rect.inner(Margin {
221+
area.inner(Margin {
245222
vertical: 1,
246223
horizontal: 0,
247224
}),
@@ -252,8 +229,8 @@ fn render_changelog(state: &mut State, frame: &mut Frame, rect: Rect) {
252229

253230
if state.is_generating {
254231
let throbber_area = Rect::new(
255-
rect.left().saturating_add(2),
256-
rect.bottom().saturating_sub(1),
232+
area.left().saturating_add(2),
233+
area.bottom().saturating_sub(1),
257234
1,
258235
1,
259236
);
@@ -273,20 +250,20 @@ fn render_changelog(state: &mut State, frame: &mut Frame, rect: Rect) {
273250
}
274251
}
275252

276-
fn render_error(state: &mut State, frame: &mut Frame, rect: Rect) {
253+
fn render_error(state: &mut State, frame: &mut Frame, area: Rect) {
277254
if let Some(error) = &state.error {
278255
frame.render_widget(
279256
Block::bordered()
280257
.title_top("|Error|".red().into_centered_line())
281258
.border_type(BorderType::Rounded)
282259
.border_style(Style::default().fg(Color::Rgb(100, 100, 100))),
283-
rect,
260+
area,
284261
);
285262
frame.render_widget(
286263
Paragraph::new(Line::from(error.clone()))
287264
.alignment(Alignment::Center)
288265
.wrap(Wrap { trim: false }),
289-
rect.inner(Margin {
266+
area.inner(Margin {
290267
horizontal: 1,
291268
vertical: 1,
292269
}),

0 commit comments

Comments
 (0)