Skip to content

Commit

Permalink
improve help window ux (#72)
Browse files Browse the repository at this point in the history
* improve help window ux

* add size constraints to rendering help window
  • Loading branch information
radlinskii authored Nov 5, 2024
1 parent 506a10c commit c5f2443
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 84 deletions.
100 changes: 79 additions & 21 deletions src/help_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,103 @@ impl HelpWindow {
HelpWindow
}

pub fn render(&self, frame: &mut impl FrameWrapperInterface, area: Rect) {
// Clear the background area first.
frame.render_widget(Clear, area);
pub fn render(&self, frame: &mut impl FrameWrapperInterface) {
let frame_rect = frame.size();

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

let inner_area = block.inner(area);
if frame_rect.height < 3 {
frame.render_widget(Clear, frame_rect);
return;
}

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",
" Navigation:",
" 's' - Start/resume the test",
" <Esc> - Pause the test",
" 'q' - Quit",
" '?' - Toggle 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().unwrap();
let help_text_lines_count = help_text.len();

// check if there is enough space vertically to display the help message
if frame_rect.height <= help_text_lines_count as u16 {
let paragraph =
Paragraph::new( "Terminal window is too short to display the help window\nresize the terminal or press \"?\" to return to the test")
.style(Style::default().fg(Color::Red).bg(Color::Black));

frame.render_widget(Clear, frame_rect);
frame.render_widget(paragraph, frame_rect);

return;
}

// check if there is enough space horizontally to display the help message
if frame_rect.width - 2 <= longest_help_msg_len as u16 {
let paragraph = Paragraph::new(
"Terminal window is too narrow\nto display the help window\nresize the terminal\nor press the \"?\" key\nto return to the test",
)
.style(Style::default().fg(Color::Red).bg(Color::Black));

frame.render_widget(Clear, frame_rect);
frame.render_widget(paragraph, frame_rect);

return;
}

// Create a clear overlay to dim the background
frame.render_widget(
Paragraph::new("")
.style(Style::default().bg(Color::Black).fg(Color::DarkGray))
.block(Block::default()),
frame_rect,
);

let area = Self::get_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);

let inner_area = block.inner(area);

// 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)
.constraints(constraints)
.split(inner_area);

// Render block
frame.render_widget(block, area);

// 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]);
}
}

fn get_centered_rect(window_width: u16, window_height: u16, r: Rect) -> Rect {
let x = r.x + (r.width.saturating_sub(window_width + 2)) / 2;
let y = if r.height > window_height + 4 { 3 } else { 0 };

Rect::new(x, y, window_width + 2, window_height + 1)
}
}

// TODO: add tests
100 changes: 37 additions & 63 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use crate::expected_input::ExpectedInputInterface;
use crate::help_window::HelpWindow;
use crate::helpers::split_by_char_index;
use crate::test_results::{Stats, TestResults};
use ratatui::widgets::Block;
use ratatui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
Expand Down Expand Up @@ -135,31 +134,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 @@ -246,18 +250,7 @@ impl Runner {

// Then render help window on top if needed
if self.show_help {
// Create a clear overlay to dim the background
let full_area = frame.size();
frame.render_widget(
Paragraph::new("")
.style(Style::default().bg(Color::Black).fg(Color::White))
.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);
self.help_window.render(frame)
}
}

Expand Down Expand Up @@ -343,12 +336,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 @@ -654,8 +647,8 @@ mod test {
vec![
vec![
("30 seconds left", Color::Yellow),
(" ", Color::Reset),
("press 'Esc' to pause the test", Color::Yellow),
(" ", Color::Reset),
("press '<Esc>' to pause the test", Color::Yellow),
],
vec![
("foobar", Color::Green),
Expand All @@ -669,7 +662,8 @@ mod test {
);

test_runner(&mut runner, buffer, |frame, runner| {
runner.render(frame, 30);
let duration = 30;
runner.render(frame, duration);
});
}

Expand Down Expand Up @@ -790,23 +784,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 c5f2443

Please sign in to comment.