Skip to content

Commit

Permalink
feat(console): help view modal (#432)
Browse files Browse the repository at this point in the history
  • Loading branch information
hds authored and hawkw committed Sep 29, 2023
1 parent 577c3c4 commit 359a4e7
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 12 deletions.
20 changes: 20 additions & 0 deletions tokio-console/src/input.rs
Expand Up @@ -33,3 +33,23 @@ pub(crate) fn is_space(input: &Event) -> bool {
})
)
}

pub(crate) fn is_help_toggle(event: &Event) -> bool {
matches!(
event,
Event::Key(KeyEvent {
code: KeyCode::Char('?'),
..
})
)
}

pub(crate) fn is_esc(event: &Event) -> bool {
matches!(
event,
Event::Key(KeyEvent {
code: KeyCode::Esc,
..
})
)
}
22 changes: 21 additions & 1 deletion tokio-console/src/view/async_ops.rs
Expand Up @@ -7,6 +7,7 @@ use crate::{
},
view::{
self, bold,
controls::Controls,
table::{TableList, TableListState},
DUR_LEN, DUR_TABLE_PRECISION,
},
Expand Down Expand Up @@ -194,6 +195,24 @@ impl TableList<9> for AsyncOpsTable {
table_list_state.len()
))]);

let layout = layout::Layout::default()
.direction(layout::Direction::Vertical)
.margin(0);

let controls = Controls::new(view_controls(), &area, styles);
let chunks = layout
.constraints(
[
layout::Constraint::Length(controls.height()),
layout::Constraint::Max(area.height),
]
.as_ref(),
)
.split(area);

let controls_area = chunks[0];
let async_ops_area = chunks[1];

let attributes_width = layout::Constraint::Percentage(100);
let widths = &[
id_width.constraint(),
Expand All @@ -214,7 +233,8 @@ impl TableList<9> for AsyncOpsTable {
.highlight_symbol(view::TABLE_HIGHLIGHT_SYMBOL)
.highlight_style(Style::default().add_modifier(style::Modifier::BOLD));

frame.render_stateful_widget(table, area, &mut table_list_state.table_state);
frame.render_stateful_widget(table, async_ops_area, &mut table_list_state.table_state);
frame.render_widget(controls.into_widget(), controls_area);

table_list_state
.sorted_items
Expand Down
19 changes: 16 additions & 3 deletions tokio-console/src/view/controls.rs
Expand Up @@ -38,8 +38,8 @@ impl Controls {
styles: &view::Styles,
) -> Self {
let mut spans_controls = Vec::with_capacity(view_controls.len() + UNIVERSAL_CONTROLS.len());
spans_controls.extend(view_controls.iter().map(|c| c.to_spans(styles)));
spans_controls.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles)));
spans_controls.extend(view_controls.iter().map(|c| c.to_spans(styles, 0)));
spans_controls.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles, 0)));

let mut lines = vec![Spans::from(vec![Span::from("controls: ")])];
let mut current_line = lines.last_mut().expect("This vector is never empty");
Expand Down Expand Up @@ -101,6 +101,18 @@ impl Controls {
}
}

pub(crate) fn controls_paragraph<'a>(
view_controls: &[ControlDisplay],
styles: &view::Styles,
) -> Paragraph<'a> {
let mut spans = Vec::with_capacity(1 + view_controls.len() + UNIVERSAL_CONTROLS.len());
spans.push(Spans::from(vec![Span::raw("controls:")]));
spans.extend(view_controls.iter().map(|c| c.to_spans(styles, 2)));
spans.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles, 2)));

Paragraph::new(spans)
}

/// Construct span to display a control.
///
/// A control is made up of an action and one or more keys that will trigger
Expand All @@ -125,9 +137,10 @@ pub(crate) struct KeyDisplay {
}

impl ControlDisplay {
pub(crate) fn to_spans(&self, styles: &view::Styles) -> Spans<'static> {
pub(crate) fn to_spans(&self, styles: &view::Styles, indent: usize) -> Spans<'static> {
let mut spans = Vec::new();

spans.push(Span::from(" ".repeat(indent)));
spans.push(Span::from(self.action));
spans.push(Span::from(" = "));
for (idx, key_display) in self.keys.iter().enumerate() {
Expand Down
67 changes: 67 additions & 0 deletions tokio-console/src/view/help.rs
@@ -0,0 +1,67 @@
use ratatui::{
layout::{self, Constraint, Direction, Layout},
widgets::{Clear, Paragraph},
};

use crate::{state::State, view};

pub(crate) trait HelpText {
fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static>;
}

/// Simple view for help popup
pub(crate) struct HelpView<'a> {
help_text: Option<Paragraph<'a>>,
}

impl<'a> HelpView<'a> {
pub(super) fn new(help_text: Paragraph<'a>) -> Self {
HelpView {
help_text: Some(help_text),
}
}

pub(crate) fn render<B: ratatui::backend::Backend>(
&mut self,
styles: &view::Styles,
frame: &mut ratatui::terminal::Frame<B>,
_area: layout::Rect,
_state: &mut State,
) {
let r = frame.size();
let content = self
.help_text
.take()
.expect("help_text should be initialized");

let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage(20),
Constraint::Min(15),
Constraint::Percentage(20),
]
.as_ref(),
)
.split(r);

let popup_area = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage(20),
Constraint::Percentage(60),
Constraint::Percentage(20),
]
.as_ref(),
)
.split(popup_layout[1])[1];

let display_text = content.block(styles.border_block().title("Help"));

// Clear the help block area and render the popup
frame.render_widget(Clear, popup_area);
frame.render_widget(display_text, popup_area);
}
}
31 changes: 28 additions & 3 deletions tokio-console/src/view/mod.rs
@@ -1,4 +1,7 @@
use crate::view::{resources::ResourcesTable, table::TableListState, tasks::TasksTable};
use crate::view::help::HelpView;
use crate::view::{
help::HelpText, resources::ResourcesTable, table::TableListState, tasks::TasksTable,
};
use crate::{input, state::State};
use ratatui::{
layout,
Expand All @@ -10,6 +13,7 @@ use std::{borrow::Cow, cmp};
mod async_ops;
mod controls;
mod durations;
mod help;
mod mini_histogram;
mod percentiles;
mod resource;
Expand Down Expand Up @@ -43,6 +47,7 @@ pub struct View {
tasks_list: TableListState<TasksTable, 12>,
resources_list: TableListState<ResourcesTable, 9>,
state: ViewState,
show_help_modal: bool,
pub(crate) styles: Styles,
}

Expand Down Expand Up @@ -96,6 +101,7 @@ impl View {
state: ViewState::TasksList,
tasks_list: TableListState::<TasksTable, 12>::default(),
resources_list: TableListState::<ResourcesTable, 9>::default(),
show_help_modal: false,
styles,
}
}
Expand All @@ -104,6 +110,11 @@ impl View {
use ViewState::*;
let mut update_kind = UpdateKind::Other;

if self.should_toggle_help_modal(&event) {
self.show_help_modal = !self.show_help_modal;
return update_kind;
}

if matches!(event, key!(Char('t'))) {
self.state = TasksList;
return update_kind;
Expand Down Expand Up @@ -180,32 +191,46 @@ impl View {
update_kind
}

/// The help modal should toggle on the `?` key and should exit on `Esc`
fn should_toggle_help_modal(&mut self, event: &crossterm::event::Event) -> bool {
input::is_help_toggle(event) || (self.show_help_modal && input::is_esc(event))
}

pub(crate) fn render<B: ratatui::backend::Backend>(
&mut self,
frame: &mut ratatui::terminal::Frame<B>,
area: layout::Rect,
state: &mut State,
) {
match self.state {
let help_text: &dyn HelpText = match self.state {
ViewState::TasksList => {
self.tasks_list.render(&self.styles, frame, area, state, ());
&self.tasks_list
}
ViewState::ResourcesList => {
self.resources_list
.render(&self.styles, frame, area, state, ());
&self.resources_list
}
ViewState::TaskInstance(ref mut view) => {
let now = state
.last_updated_at()
.expect("task view implies we've received an update");
view.render(&self.styles, frame, area, now);
view
}
ViewState::ResourceInstance(ref mut view) => {
view.render(&self.styles, frame, area, state);
view
}
}
};

state.retain_active();

if self.show_help_modal {
let mut help_view = HelpView::new(help_text.render_help_content(&self.styles));
help_view.render(&self.styles, frame, area, state);
}
}

pub(crate) fn current_view(&self) -> &ViewState {
Expand Down
9 changes: 8 additions & 1 deletion tokio-console/src/view/resource.rs
Expand Up @@ -6,7 +6,8 @@ use crate::{
self,
async_ops::{self, AsyncOpsTable, AsyncOpsTableCtx},
bold,
controls::{ControlDisplay, Controls, KeyDisplay},
controls::{controls_paragraph, ControlDisplay, Controls, KeyDisplay},
help::HelpText,
TableListState,
},
};
Expand Down Expand Up @@ -116,6 +117,12 @@ impl ResourceView {
}
}

impl HelpText for ResourceView {
fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> {
controls_paragraph(view_controls(), styles)
}
}

fn view_controls() -> &'static [ControlDisplay] {
static VIEW_CONTROLS: OnceCell<Vec<ControlDisplay>> = OnceCell::new();

Expand Down
19 changes: 16 additions & 3 deletions tokio-console/src/view/table.rs
Expand Up @@ -2,13 +2,17 @@ use crate::{
input, state,
view::{
self,
controls::{ControlDisplay, KeyDisplay},
controls::{controls_paragraph, ControlDisplay, KeyDisplay},
help::HelpText,
},
};
use ratatui::{
layout,
widgets::{Paragraph, TableState},
};
use std::convert::TryFrom;

use ratatui::{layout, widgets::TableState};
use std::cell::RefCell;
use std::convert::TryFrom;
use std::rc::Weak;

pub(crate) trait TableList<const N: usize> {
Expand Down Expand Up @@ -195,6 +199,15 @@ where
}
}

impl<T, const N: usize> HelpText for TableListState<T, N>
where
T: TableList<N>,
{
fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> {
controls_paragraph(view_controls(), styles)
}
}

pub(crate) const fn view_controls() -> &'static [ControlDisplay] {
&[
ControlDisplay {
Expand Down
9 changes: 8 additions & 1 deletion tokio-console/src/view/task.rs
Expand Up @@ -4,8 +4,9 @@ use crate::{
util::Percentage,
view::{
self, bold,
controls::{ControlDisplay, Controls, KeyDisplay},
controls::{controls_paragraph, ControlDisplay, Controls, KeyDisplay},
durations::Durations,
help::HelpText,
},
};
use ratatui::{
Expand Down Expand Up @@ -253,6 +254,12 @@ impl TaskView {
}
}

impl HelpText for TaskView {
fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> {
controls_paragraph(view_controls(), styles)
}
}

const fn view_controls() -> &'static [ControlDisplay] {
&[ControlDisplay {
action: "return to task list",
Expand Down

0 comments on commit 359a4e7

Please sign in to comment.