Skip to content

Commit

Permalink
improve help window ux
Browse files Browse the repository at this point in the history
  • Loading branch information
radlinskii committed Oct 27, 2024
1 parent 4fcddd7 commit 6629623
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 72 deletions.
77 changes: 56 additions & 21 deletions src/help_window.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{Context, Result};
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
widgets::{Block, Borders, Clear, Paragraph},
};

Expand All @@ -13,32 +13,45 @@ impl HelpWindow {
HelpWindow
}

pub fn render(&self, frame: &mut impl FrameWrapperInterface, area: Rect) {
pub fn render(&self, frame: &mut impl FrameWrapperInterface) -> Result<()> {
let help_text = vec![
"",
" Navigation:",
" 's' - Start/resume the test",
" <Esc> - Pause the test",
" 'q' - Quit",
" '?' - Close this window",
"",
" Configuration:",
" --duration <seconds> - Set test duration",
" --numbers - Include numbers in the test",
" --uppercase - Include uppercase letters",
"",
" Run 'donkeytype help' in your terminal to get more information ",
"",
];

let longest_help_msg_len = help_text
.iter()
.map(|s| s.len())
.max()
.context("Unable to get the length of longest line from help window text")?;
let help_text_lines_count = help_text.len();

let area = Self::centered_rect(
longest_help_msg_len.try_into().unwrap(),
help_text_lines_count.try_into().unwrap(),
frame.size(),
);
// Clear the background area first.
frame.render_widget(Clear, area);

let block = Block::default()
.title("Help")
.borders(Borders::ALL)
.style(Style::default().fg(Color::White).bg(Color::Black));
let block = Block::default().title(" Help ").borders(Borders::ALL);

let inner_area = block.inner(area);

let help_text = vec![
"Navigation:",
"s - Start/unpause the test",
"Esc - Pause the test",
"q - Quit",
"",
"Configuration:",
"--duration <seconds> - Set test duration",
"--numbers - Include numbers in the test",
"--uppercase - Include uppercase letters",
"Run 'donkeytype help' for more options",
];

// Create constraints dynamically based on help_text length
let constraints = vec![Constraint::Length(1); help_text.len()];
let constraints = vec![Constraint::Length(1); help_text_lines_count];

let chunks = Layout::default()
.direction(Direction::Vertical)
Expand All @@ -50,8 +63,30 @@ impl HelpWindow {

// Render text paragraphs
for (i, &text) in help_text.iter().enumerate() {
let paragraph = Paragraph::new(text).style(Style::default().fg(Color::White));
let paragraph = Paragraph::new(text);
frame.render_widget(paragraph, chunks[i]);
}

Ok(())
}

fn centered_rect(window_width: u16, window_height: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(0),
Constraint::Length(window_height + 2),
Constraint::Length(r.height - window_height - 2),
])
.split(r);

Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Length((r.width - window_width - 2) / 2),
Constraint::Length(window_width + 2),
Constraint::Length((r.width - window_width - 2) / 2),
])
.split(popup_layout[1])[1]
}
}
86 changes: 35 additions & 51 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,31 +135,36 @@ impl Runner {
if let Event::Key(key) = event::read().context("Unable to read event")? {
if key.kind == KeyEventKind::Press {
match self.input_mode {
InputMode::Normal => match key.code {
KeyCode::Char('s') => {
// Hide help window if it's shown.
self.show_help = false;

start_time = if self.is_started {
start_time + pause_time.elapsed()
} else {
Instant::now()
};
self.is_started = true;
self.input_mode = InputMode::Editing;
}
KeyCode::Char('q') => {
// todo return canceled test error and handle it in main
return Ok(TestResults::new(
Stats::default(),
self.config.clone(),
false,
));
}
KeyCode::Char('?') => {
self.show_help = !self.show_help;
}
_ => {}
InputMode::Normal => match self.show_help {
true => match key.code {
KeyCode::Char('?') => {
self.show_help = false;
}
_ => {}
},
false => match key.code {
KeyCode::Char('s') => {
start_time = if self.is_started {
start_time + pause_time.elapsed()
} else {
Instant::now()
};
self.is_started = true;
self.input_mode = InputMode::Editing;
}
KeyCode::Char('q') => {
// todo return canceled test error and handle it in main
return Ok(TestResults::new(
Stats::default(),
self.config.clone(),
false,
));
}
KeyCode::Char('?') => {
self.show_help = true;
}
_ => {}
},
},
InputMode::Editing => match key.code {
// Crossterm returns `ctrl+w` or ``ctrl+h` when `ctrl+backspace` is pressed
Expand Down Expand Up @@ -250,14 +255,13 @@ impl Runner {
let full_area = frame.size();
frame.render_widget(
Paragraph::new("")
.style(Style::default().bg(Color::Black).fg(Color::White))
.style(Style::default().bg(Color::Black).fg(Color::DarkGray))
.block(Block::default()),
full_area,
);

// Render the help window in the center
let help_area = centered_rect(60, 60, frame.size());
self.help_window.render(frame, help_area);
let _ = self.help_window.render(frame);
}
}

Expand Down Expand Up @@ -343,12 +347,12 @@ impl Runner {
let help_message = match self.input_mode {
InputMode::Normal => {
if self.is_started {
"press 's' to unpause the test, 'q' to quit, '?' for help"
"press 's' to resume the test, 'q' to quit, '?' for help"
} else {
"press 's' to start the test, 'q' to quit, '?' for help"
}
}
InputMode::Editing => "press 'Esc' to pause the test",
InputMode::Editing => "press '<Esc>' to pause the test",
};
self.print_block_of_text(
frame,
Expand Down Expand Up @@ -655,7 +659,7 @@ mod test {
vec![
("30 seconds left", Color::Yellow),
(" ", Color::Reset),
("press 'Esc' to pause the test", Color::Yellow),
("press '<Esc>' to pause the test", Color::Yellow),
],
vec![
("foobar", Color::Green),
Expand Down Expand Up @@ -790,23 +794,3 @@ mod test {
runner.move_cursor(&mut frame, area, input_current_line_len, current_line_index)
}
}

fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);

Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1]
}

0 comments on commit 6629623

Please sign in to comment.