feat: style help info box
Style each button comman in the help information window, instead of just one giant string. Now uses a HelpInfo struct, which contains the content, as well as widths + height
This commit is contained in:
+221
-53
@@ -481,93 +481,249 @@ pub fn heading_bar<B: Backend>(
|
||||
|
||||
/// From a given &str, return the maximum number of chars on a single line
|
||||
fn max_line_width(text: &str) -> usize {
|
||||
let mut max_line_width = 0;
|
||||
text.lines().into_iter().for_each(|line| {
|
||||
let width = line.chars().count();
|
||||
if width > max_line_width {
|
||||
max_line_width = width;
|
||||
text.lines()
|
||||
.into_iter()
|
||||
.map(|i| i.chars().count())
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Help popup box needs these three pieces of information
|
||||
struct HelpInfo {
|
||||
spans: Vec<Spans<'static>>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl HelpInfo {
|
||||
/// Find the max width of a Span in &[Spans], although it isn't calulating it correctly
|
||||
fn calc_width(spans: &[Spans]) -> usize {
|
||||
spans
|
||||
.iter()
|
||||
.flat_map(|x| x.0.iter())
|
||||
.map(tui::text::Span::width)
|
||||
.max()
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
/// Just an empty span, i.e. a new line
|
||||
fn empty_span<'a>() -> Spans<'a> {
|
||||
Spans::from(String::new())
|
||||
}
|
||||
|
||||
/// generate a span, of given &str and given color
|
||||
fn span<'a>(input: &str, color: Color) -> Span<'a> {
|
||||
Span::styled(input.to_owned(), Style::default().fg(color))
|
||||
}
|
||||
|
||||
/// Span to black text span
|
||||
fn black_span<'a>(input: &str) -> Span<'a> {
|
||||
Self::span(input, Color::Black)
|
||||
}
|
||||
|
||||
/// Span to white text span
|
||||
fn white_span<'a>(input: &str) -> Span<'a> {
|
||||
Self::span(input, Color::White)
|
||||
}
|
||||
|
||||
fn gen_name() -> Self {
|
||||
let mut spans = NAME_TEXT
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|i| Spans::from(Self::white_span(i)))
|
||||
.collect::<Vec<_>>();
|
||||
spans.insert(0, Self::empty_span());
|
||||
let width = Self::calc_width(&spans);
|
||||
let height = spans.len();
|
||||
Self {
|
||||
spans,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_description() -> Self {
|
||||
let spans = [
|
||||
Self::empty_span(),
|
||||
Spans::from(Self::white_span(DESCRIPTION)),
|
||||
Self::empty_span(),
|
||||
];
|
||||
let width = Self::calc_width(&spans);
|
||||
let height = spans.len();
|
||||
Self {
|
||||
spans: spans.to_vec(),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_button() -> Self {
|
||||
let button_item = |x: &str| Self::white_span(&format!(" {x} "));
|
||||
let button_desc = |x: &str| Self::black_span(x);
|
||||
let or = || button_desc("or");
|
||||
let space = || button_desc(" ");
|
||||
|
||||
let spans = [
|
||||
Spans::from(vec![
|
||||
space(),
|
||||
button_item("( tab )"),
|
||||
or(),
|
||||
button_item("( shift+tab )"),
|
||||
button_desc("to change panels"),
|
||||
]),
|
||||
Spans::from(vec![
|
||||
space(),
|
||||
button_item("( ↑ ↓ )"),
|
||||
or(),
|
||||
button_item("( j k )"),
|
||||
or(),
|
||||
button_item("( PgUp PgDown )"),
|
||||
or(),
|
||||
button_item("( Home End )"),
|
||||
button_desc("to change selected line"),
|
||||
]),
|
||||
Spans::from(vec![
|
||||
space(),
|
||||
button_item("( enter )"),
|
||||
button_desc("to send docker container command"),
|
||||
]),
|
||||
Spans::from(vec![
|
||||
space(),
|
||||
button_item("( h )"),
|
||||
button_desc("to toggle this help information"),
|
||||
]),
|
||||
Spans::from(vec![
|
||||
space(),
|
||||
button_item("( 0 )"),
|
||||
button_desc("to stop sort"),
|
||||
]),
|
||||
Spans::from(vec![
|
||||
space(),
|
||||
button_item("( 1 - 9 )"),
|
||||
button_desc("sort by header - or click header"),
|
||||
]),
|
||||
Spans::from(vec![
|
||||
space(),
|
||||
button_item("( m )"),
|
||||
button_desc(
|
||||
"to toggle mouse capture - if disabled, text on screen can be selected & copied",
|
||||
),
|
||||
]),
|
||||
Spans::from(vec![
|
||||
space(),
|
||||
button_item("( q )"),
|
||||
button_desc("to quit at any time"),
|
||||
]),
|
||||
];
|
||||
|
||||
let height = spans.len();
|
||||
let width = Self::calc_width(&spans);
|
||||
Self {
|
||||
spans: spans.to_vec(),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_final() -> Self {
|
||||
let spans = [
|
||||
Self::empty_span(),
|
||||
Spans::from(vec![Self::black_span(
|
||||
"currently an early work in progress, all and any input appreciated",
|
||||
)]),
|
||||
Spans::from(vec![Span::styled(
|
||||
REPO.to_owned(),
|
||||
Style::default()
|
||||
.bg(Color::Magenta)
|
||||
.fg(Color::Black)
|
||||
.add_modifier(Modifier::UNDERLINED),
|
||||
)]),
|
||||
];
|
||||
let height = spans.len();
|
||||
let width = Self::calc_width(&spans);
|
||||
Self {
|
||||
spans: spans.to_vec(),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
});
|
||||
max_line_width
|
||||
}
|
||||
|
||||
/// Draw the help box in the centre of the screen
|
||||
/// TODO should make every line it's own renderable span
|
||||
pub fn help_box<B: Backend>(f: &mut Frame<'_, B>) {
|
||||
let title = format!(" {VERSION} ");
|
||||
|
||||
let description_text = format!("\n{DESCRIPTION}");
|
||||
let name_info = HelpInfo::gen_name();
|
||||
let description_info = HelpInfo::gen_description();
|
||||
let button_info = HelpInfo::gen_button();
|
||||
let final_info = HelpInfo::gen_final();
|
||||
|
||||
let mut help_text = String::from("\n ( tab ) or ( shift+tab ) to change panels");
|
||||
help_text
|
||||
.push_str("\n ( ↑ ↓ ) or ( j k ) or (PgUp PgDown) or (Home End) to change selected line");
|
||||
help_text.push_str("\n ( enter ) to send docker container commands");
|
||||
help_text.push_str("\n ( h ) to toggle this help information");
|
||||
help_text.push_str("\n ( 0 ) stop sort");
|
||||
help_text.push_str("\n ( 1 - 9 ) sort by header - or click header");
|
||||
help_text.push_str(
|
||||
"\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied",
|
||||
// have to add 10, but shouldn't need to, is an error somewhere
|
||||
let max_line_width = [
|
||||
name_info.width,
|
||||
description_info.width,
|
||||
button_info.width,
|
||||
final_info.width,
|
||||
]
|
||||
.into_iter()
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
+ 10;
|
||||
let max_height =
|
||||
name_info.height + description_info.height + button_info.height + final_info.height + 2;
|
||||
|
||||
let area = popup(
|
||||
max_height,
|
||||
max_line_width,
|
||||
f.size(),
|
||||
BoxLocation::MiddleCentre,
|
||||
);
|
||||
help_text.push_str("\n ( q ) to quit at any time");
|
||||
help_text.push_str("\n mouse scrolling & clicking also available");
|
||||
help_text.push_str("\n\n currently an early work in progress, all and any input appreciated");
|
||||
help_text.push_str(format!("\n {}", REPO.trim()).as_str());
|
||||
|
||||
// Find the maximum line widths & height
|
||||
let all_text = format!("{NAME_TEXT}{description_text}{help_text}");
|
||||
let mut max_line_width = max_line_width(&all_text);
|
||||
let mut lines = all_text.lines().count();
|
||||
let split_popup = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Max(name_info.height.try_into().unwrap_or_default()),
|
||||
Constraint::Max(description_info.height.try_into().unwrap_or_default()),
|
||||
Constraint::Max(button_info.height.try_into().unwrap_or_default()),
|
||||
Constraint::Max(final_info.height.try_into().unwrap_or_default()),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(area);
|
||||
|
||||
// Add some vertical and horizontal padding to the info box
|
||||
lines += 3;
|
||||
max_line_width += 4;
|
||||
|
||||
let name_paragraph = Paragraph::new(NAME_TEXT)
|
||||
let name_paragraph = Paragraph::new(name_info.spans)
|
||||
.style(Style::default().bg(Color::Magenta).fg(Color::White))
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
let description_paragraph = Paragraph::new(description_text.as_str())
|
||||
let description_paragraph = Paragraph::new(description_info.spans)
|
||||
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
let help_paragraph = Paragraph::new(help_text.as_str())
|
||||
let help_paragraph = Paragraph::new(button_info.spans)
|
||||
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Left);
|
||||
|
||||
let final_paragraph = Paragraph::new(final_info.spans)
|
||||
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
let block = Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(Color::Black));
|
||||
|
||||
let area = popup(lines, max_line_width, f.size(), BoxLocation::MiddleCentre);
|
||||
|
||||
let split_popup = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Max(NAME_TEXT.lines().count().try_into().unwrap_or_default()),
|
||||
Constraint::Max(
|
||||
description_text
|
||||
.lines()
|
||||
.count()
|
||||
.try_into()
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
Constraint::Max(help_text.lines().count().try_into().unwrap_or_default()),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(area);
|
||||
|
||||
// Order is important here
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(name_paragraph, split_popup[0]);
|
||||
f.render_widget(description_paragraph, split_popup[1]);
|
||||
f.render_widget(help_paragraph, split_popup[2]);
|
||||
f.render_widget(final_paragraph, split_popup[3]);
|
||||
f.render_widget(block, area);
|
||||
}
|
||||
|
||||
@@ -668,3 +824,15 @@ fn popup(text_lines: usize, text_width: usize, r: Rect, box_location: BoxLocatio
|
||||
.constraints(h_constraints)
|
||||
.split(popup_layout[indexes.0])[indexes.1]
|
||||
}
|
||||
|
||||
// Draw nothing, as in a blank screen
|
||||
// pub fn nothing<B: Backend>(f: &mut Frame<'_, B>) {
|
||||
// let whole_layout = Layout::default()
|
||||
// .direction(Direction::Vertical)
|
||||
// .constraints([Constraint::Min(100)].as_ref())
|
||||
// .split(f.size());
|
||||
|
||||
// let block = Block::default()
|
||||
// .borders(Borders::NONE);
|
||||
// f.render_widget(block, whole_layout[0]);
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user