Skip to content

Commit 359a4e7

Browse files
hdshawkw
authored andcommitted
feat(console): help view modal (#432)
1 parent 577c3c4 commit 359a4e7

File tree

8 files changed

+184
-12
lines changed

8 files changed

+184
-12
lines changed

tokio-console/src/input.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,23 @@ pub(crate) fn is_space(input: &Event) -> bool {
3333
})
3434
)
3535
}
36+
37+
pub(crate) fn is_help_toggle(event: &Event) -> bool {
38+
matches!(
39+
event,
40+
Event::Key(KeyEvent {
41+
code: KeyCode::Char('?'),
42+
..
43+
})
44+
)
45+
}
46+
47+
pub(crate) fn is_esc(event: &Event) -> bool {
48+
matches!(
49+
event,
50+
Event::Key(KeyEvent {
51+
code: KeyCode::Esc,
52+
..
53+
})
54+
)
55+
}

tokio-console/src/view/async_ops.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
},
88
view::{
99
self, bold,
10+
controls::Controls,
1011
table::{TableList, TableListState},
1112
DUR_LEN, DUR_TABLE_PRECISION,
1213
},
@@ -194,6 +195,24 @@ impl TableList<9> for AsyncOpsTable {
194195
table_list_state.len()
195196
))]);
196197

198+
let layout = layout::Layout::default()
199+
.direction(layout::Direction::Vertical)
200+
.margin(0);
201+
202+
let controls = Controls::new(view_controls(), &area, styles);
203+
let chunks = layout
204+
.constraints(
205+
[
206+
layout::Constraint::Length(controls.height()),
207+
layout::Constraint::Max(area.height),
208+
]
209+
.as_ref(),
210+
)
211+
.split(area);
212+
213+
let controls_area = chunks[0];
214+
let async_ops_area = chunks[1];
215+
197216
let attributes_width = layout::Constraint::Percentage(100);
198217
let widths = &[
199218
id_width.constraint(),
@@ -214,7 +233,8 @@ impl TableList<9> for AsyncOpsTable {
214233
.highlight_symbol(view::TABLE_HIGHLIGHT_SYMBOL)
215234
.highlight_style(Style::default().add_modifier(style::Modifier::BOLD));
216235

217-
frame.render_stateful_widget(table, area, &mut table_list_state.table_state);
236+
frame.render_stateful_widget(table, async_ops_area, &mut table_list_state.table_state);
237+
frame.render_widget(controls.into_widget(), controls_area);
218238

219239
table_list_state
220240
.sorted_items

tokio-console/src/view/controls.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ impl Controls {
3838
styles: &view::Styles,
3939
) -> Self {
4040
let mut spans_controls = Vec::with_capacity(view_controls.len() + UNIVERSAL_CONTROLS.len());
41-
spans_controls.extend(view_controls.iter().map(|c| c.to_spans(styles)));
42-
spans_controls.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles)));
41+
spans_controls.extend(view_controls.iter().map(|c| c.to_spans(styles, 0)));
42+
spans_controls.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles, 0)));
4343

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

104+
pub(crate) fn controls_paragraph<'a>(
105+
view_controls: &[ControlDisplay],
106+
styles: &view::Styles,
107+
) -> Paragraph<'a> {
108+
let mut spans = Vec::with_capacity(1 + view_controls.len() + UNIVERSAL_CONTROLS.len());
109+
spans.push(Spans::from(vec![Span::raw("controls:")]));
110+
spans.extend(view_controls.iter().map(|c| c.to_spans(styles, 2)));
111+
spans.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles, 2)));
112+
113+
Paragraph::new(spans)
114+
}
115+
104116
/// Construct span to display a control.
105117
///
106118
/// A control is made up of an action and one or more keys that will trigger
@@ -125,9 +137,10 @@ pub(crate) struct KeyDisplay {
125137
}
126138

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

143+
spans.push(Span::from(" ".repeat(indent)));
131144
spans.push(Span::from(self.action));
132145
spans.push(Span::from(" = "));
133146
for (idx, key_display) in self.keys.iter().enumerate() {

tokio-console/src/view/help.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use ratatui::{
2+
layout::{self, Constraint, Direction, Layout},
3+
widgets::{Clear, Paragraph},
4+
};
5+
6+
use crate::{state::State, view};
7+
8+
pub(crate) trait HelpText {
9+
fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static>;
10+
}
11+
12+
/// Simple view for help popup
13+
pub(crate) struct HelpView<'a> {
14+
help_text: Option<Paragraph<'a>>,
15+
}
16+
17+
impl<'a> HelpView<'a> {
18+
pub(super) fn new(help_text: Paragraph<'a>) -> Self {
19+
HelpView {
20+
help_text: Some(help_text),
21+
}
22+
}
23+
24+
pub(crate) fn render<B: ratatui::backend::Backend>(
25+
&mut self,
26+
styles: &view::Styles,
27+
frame: &mut ratatui::terminal::Frame<B>,
28+
_area: layout::Rect,
29+
_state: &mut State,
30+
) {
31+
let r = frame.size();
32+
let content = self
33+
.help_text
34+
.take()
35+
.expect("help_text should be initialized");
36+
37+
let popup_layout = Layout::default()
38+
.direction(Direction::Vertical)
39+
.constraints(
40+
[
41+
Constraint::Percentage(20),
42+
Constraint::Min(15),
43+
Constraint::Percentage(20),
44+
]
45+
.as_ref(),
46+
)
47+
.split(r);
48+
49+
let popup_area = Layout::default()
50+
.direction(Direction::Horizontal)
51+
.constraints(
52+
[
53+
Constraint::Percentage(20),
54+
Constraint::Percentage(60),
55+
Constraint::Percentage(20),
56+
]
57+
.as_ref(),
58+
)
59+
.split(popup_layout[1])[1];
60+
61+
let display_text = content.block(styles.border_block().title("Help"));
62+
63+
// Clear the help block area and render the popup
64+
frame.render_widget(Clear, popup_area);
65+
frame.render_widget(display_text, popup_area);
66+
}
67+
}

tokio-console/src/view/mod.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::view::{resources::ResourcesTable, table::TableListState, tasks::TasksTable};
1+
use crate::view::help::HelpView;
2+
use crate::view::{
3+
help::HelpText, resources::ResourcesTable, table::TableListState, tasks::TasksTable,
4+
};
25
use crate::{input, state::State};
36
use ratatui::{
47
layout,
@@ -10,6 +13,7 @@ use std::{borrow::Cow, cmp};
1013
mod async_ops;
1114
mod controls;
1215
mod durations;
16+
mod help;
1317
mod mini_histogram;
1418
mod percentiles;
1519
mod resource;
@@ -43,6 +47,7 @@ pub struct View {
4347
tasks_list: TableListState<TasksTable, 12>,
4448
resources_list: TableListState<ResourcesTable, 9>,
4549
state: ViewState,
50+
show_help_modal: bool,
4651
pub(crate) styles: Styles,
4752
}
4853

@@ -96,6 +101,7 @@ impl View {
96101
state: ViewState::TasksList,
97102
tasks_list: TableListState::<TasksTable, 12>::default(),
98103
resources_list: TableListState::<ResourcesTable, 9>::default(),
104+
show_help_modal: false,
99105
styles,
100106
}
101107
}
@@ -104,6 +110,11 @@ impl View {
104110
use ViewState::*;
105111
let mut update_kind = UpdateKind::Other;
106112

113+
if self.should_toggle_help_modal(&event) {
114+
self.show_help_modal = !self.show_help_modal;
115+
return update_kind;
116+
}
117+
107118
if matches!(event, key!(Char('t'))) {
108119
self.state = TasksList;
109120
return update_kind;
@@ -180,32 +191,46 @@ impl View {
180191
update_kind
181192
}
182193

194+
/// The help modal should toggle on the `?` key and should exit on `Esc`
195+
fn should_toggle_help_modal(&mut self, event: &crossterm::event::Event) -> bool {
196+
input::is_help_toggle(event) || (self.show_help_modal && input::is_esc(event))
197+
}
198+
183199
pub(crate) fn render<B: ratatui::backend::Backend>(
184200
&mut self,
185201
frame: &mut ratatui::terminal::Frame<B>,
186202
area: layout::Rect,
187203
state: &mut State,
188204
) {
189-
match self.state {
205+
let help_text: &dyn HelpText = match self.state {
190206
ViewState::TasksList => {
191207
self.tasks_list.render(&self.styles, frame, area, state, ());
208+
&self.tasks_list
192209
}
193210
ViewState::ResourcesList => {
194211
self.resources_list
195212
.render(&self.styles, frame, area, state, ());
213+
&self.resources_list
196214
}
197215
ViewState::TaskInstance(ref mut view) => {
198216
let now = state
199217
.last_updated_at()
200218
.expect("task view implies we've received an update");
201219
view.render(&self.styles, frame, area, now);
220+
view
202221
}
203222
ViewState::ResourceInstance(ref mut view) => {
204223
view.render(&self.styles, frame, area, state);
224+
view
205225
}
206-
}
226+
};
207227

208228
state.retain_active();
229+
230+
if self.show_help_modal {
231+
let mut help_view = HelpView::new(help_text.render_help_content(&self.styles));
232+
help_view.render(&self.styles, frame, area, state);
233+
}
209234
}
210235

211236
pub(crate) fn current_view(&self) -> &ViewState {

tokio-console/src/view/resource.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use crate::{
66
self,
77
async_ops::{self, AsyncOpsTable, AsyncOpsTableCtx},
88
bold,
9-
controls::{ControlDisplay, Controls, KeyDisplay},
9+
controls::{controls_paragraph, ControlDisplay, Controls, KeyDisplay},
10+
help::HelpText,
1011
TableListState,
1112
},
1213
};
@@ -116,6 +117,12 @@ impl ResourceView {
116117
}
117118
}
118119

120+
impl HelpText for ResourceView {
121+
fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> {
122+
controls_paragraph(view_controls(), styles)
123+
}
124+
}
125+
119126
fn view_controls() -> &'static [ControlDisplay] {
120127
static VIEW_CONTROLS: OnceCell<Vec<ControlDisplay>> = OnceCell::new();
121128

tokio-console/src/view/table.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ use crate::{
22
input, state,
33
view::{
44
self,
5-
controls::{ControlDisplay, KeyDisplay},
5+
controls::{controls_paragraph, ControlDisplay, KeyDisplay},
6+
help::HelpText,
67
},
78
};
9+
use ratatui::{
10+
layout,
11+
widgets::{Paragraph, TableState},
12+
};
13+
use std::convert::TryFrom;
814

9-
use ratatui::{layout, widgets::TableState};
1015
use std::cell::RefCell;
11-
use std::convert::TryFrom;
1216
use std::rc::Weak;
1317

1418
pub(crate) trait TableList<const N: usize> {
@@ -195,6 +199,15 @@ where
195199
}
196200
}
197201

202+
impl<T, const N: usize> HelpText for TableListState<T, N>
203+
where
204+
T: TableList<N>,
205+
{
206+
fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> {
207+
controls_paragraph(view_controls(), styles)
208+
}
209+
}
210+
198211
pub(crate) const fn view_controls() -> &'static [ControlDisplay] {
199212
&[
200213
ControlDisplay {

tokio-console/src/view/task.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use crate::{
44
util::Percentage,
55
view::{
66
self, bold,
7-
controls::{ControlDisplay, Controls, KeyDisplay},
7+
controls::{controls_paragraph, ControlDisplay, Controls, KeyDisplay},
88
durations::Durations,
9+
help::HelpText,
910
},
1011
};
1112
use ratatui::{
@@ -253,6 +254,12 @@ impl TaskView {
253254
}
254255
}
255256

257+
impl HelpText for TaskView {
258+
fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> {
259+
controls_paragraph(view_controls(), styles)
260+
}
261+
}
262+
256263
const fn view_controls() -> &'static [ControlDisplay] {
257264
&[ControlDisplay {
258265
action: "return to task list",

0 commit comments

Comments
 (0)