Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 3 additions & 21 deletions tokio-console/src/view/async_ops.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) use crate::view::table::view_controls;
use crate::{
state::{
async_ops::{AsyncOp, SortBy},
Expand All @@ -6,7 +7,7 @@ use crate::{
},
view::{
self, bold,
table::{self, TableList, TableListState},
table::{TableList, TableListState},
DUR_LEN, DUR_TABLE_PRECISION,
},
};
Expand Down Expand Up @@ -193,24 +194,6 @@ impl TableList<9> for AsyncOpsTable {
table_list_state.len()
))]);

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

let controls = table::Controls::for_area(&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 @@ -231,8 +214,7 @@ 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, async_ops_area, &mut table_list_state.table_state);
frame.render_widget(controls.paragraph, controls_area);
frame.render_stateful_widget(table, area, &mut table_list_state.table_state);

table_list_state
.sorted_items
Expand Down
141 changes: 141 additions & 0 deletions tokio-console/src/view/controls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use crate::view::{self, bold};

use once_cell::sync::OnceCell;
use tui::{
layout,
text::{Span, Spans, Text},
widgets::{Paragraph, Widget},
};

/// Construct a widget to display the controls available to the user in the
/// current view.
pub(crate) struct Controls {
paragraph: Paragraph<'static>,
height: u16,
}

impl Controls {
pub(in crate::view) fn new(
view_controls: &Vec<ControlDisplay>,
area: &layout::Rect,
styles: &view::Styles,
) -> Self {
let universal_controls = universal_controls();

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)));

let mut lines = vec![Spans::from(vec![Span::from("controls: ")])];
let mut current_line = lines.last_mut().expect("This vector is never empty");
let separator = Span::from(", ");

let controls_count: usize = spans_controls.len();
for (idx, spans) in spans_controls.into_iter().enumerate() {
if idx == 0 || current_line.width() == 0 {
current_line.0.extend(spans.0);
} else {
let needed_trailing_separator_width = if idx == controls_count + 1 {
separator.width()
} else {
0
};

if current_line.width()
+ separator.width()
+ spans.width()
+ needed_trailing_separator_width
<= area.width as usize
{
current_line.0.push(separator.clone());
current_line.0.extend(spans.0);
} else {
current_line.0.push(separator.clone());
lines.push(spans);
current_line = lines.last_mut().expect("This vector is never empty");
}
}
}

let height = lines.len() as u16;
let text = Text::from(lines);

Self {
paragraph: Paragraph::new(text),
height,
}
}

pub(crate) fn height(&self) -> u16 {
self.height
}

pub(crate) fn into_widget(self) -> impl Widget {
self.paragraph
}
}

/// Construct span to display a control.
///
/// A control is made up of an action and one or more keys that will trigger
/// that action.
#[derive(Clone)]
pub(crate) struct ControlDisplay {
pub(crate) action: &'static str,
pub(crate) keys: Vec<KeyDisplay>,
}

/// A key or keys which will be displayed to the user as part of spans
/// constructed by `ControlDisplay`.
///
/// The `base` description of the key should be ASCII only, more advanced
/// descriptions can be supplied for that key in the `utf8` field. This
/// allows the application to pick the best one to display at runtime
/// based on the termainal being used.
#[derive(Clone)]
pub(crate) struct KeyDisplay {
pub(crate) base: &'static str,
pub(crate) utf8: Option<&'static str>,
}

impl ControlDisplay {
pub(crate) fn new_simple(action: &'static str, key: &'static str) -> Self {
ControlDisplay {
action,
keys: vec![KeyDisplay {
base: key,
utf8: None,
}],
}
}

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

spans.push(Span::from(self.action));
spans.push(Span::from(" = "));
for (idx, key_display) in self.keys.iter().enumerate() {
if idx > 0 {
spans.push(Span::from(" or "))
}
spans.push(bold(match key_display.utf8 {
Some(utf8) => styles.if_utf8(utf8, key_display.base),
None => key_display.base,
}));
}

Spans::from(spans)
}
}

/// Returns a list of controls which are available in all views.
pub(crate) fn universal_controls() -> &'static Vec<ControlDisplay> {
static UNIVERSAL_CONTROLS: OnceCell<Vec<ControlDisplay>> = OnceCell::new();

UNIVERSAL_CONTROLS.get_or_init(|| {
vec![
ControlDisplay::new_simple("toggle pause", "space"),
ControlDisplay::new_simple("quit", "q"),
]
})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we changed the ControlDisplay type to own an &'static [KeyDisplay] rather than a Vec, like I described above, UNIVERSAL_CONTROLS could just be a constant, rather than needing to be lazily initialized.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Although in resource.rs I had to keep the OnceCell because I'm concatenating 2 arrays which it seems can't be dont in const.

If you know a way of doing this, that would be really cool.

1 change: 1 addition & 0 deletions tokio-console/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use tui::{
};

mod async_ops;
mod controls;
mod durations;
mod mini_histogram;
mod percentiles;
Expand Down
39 changes: 26 additions & 13 deletions tokio-console/src/view/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ use crate::{
state::State,
view::{
self,
async_ops::{AsyncOpsTable, AsyncOpsTableCtx},
bold, TableListState,
async_ops::{self, AsyncOpsTable, AsyncOpsTableCtx},
bold,
controls::{ControlDisplay, Controls, KeyDisplay},
TableListState,
},
};
use once_cell::sync::OnceCell;
use std::{cell::RefCell, rc::Rc};
use tui::{
layout::{self, Layout},
text::{Span, Spans, Text},
widgets::{Block, Paragraph},
widgets::Paragraph,
};

pub(crate) struct ResourceView {
Expand Down Expand Up @@ -42,14 +45,15 @@ impl ResourceView {
state: &mut State,
) {
let resource = &*self.resource.borrow();
let controls = Controls::new(view_controls(), &area, styles);

let (controls_area, stats_area, async_ops_area) = {
let chunks = Layout::default()
.direction(layout::Direction::Vertical)
.constraints(
[
// controls
layout::Constraint::Length(1),
layout::Constraint::Length(controls.height()),
// resource stats
layout::Constraint::Length(8),
// async ops
Expand All @@ -72,14 +76,6 @@ impl ResourceView {
)
.split(stats_area);

let controls = Spans::from(vec![
Span::raw("controls: "),
bold(styles.if_utf8("\u{238B} esc", "esc")),
Span::raw(" = return to task list, "),
bold("q"),
Span::raw(" = quit"),
]);

let overview = vec![
Spans::from(vec![bold("ID: "), Span::raw(resource.id_str())]),
Spans::from(vec![bold("Parent ID: "), Span::raw(resource.parent())]),
Expand Down Expand Up @@ -107,7 +103,7 @@ impl ResourceView {
Paragraph::new(overview).block(styles.border_block().title("Resource"));
let fields_widget = Paragraph::new(fields).block(styles.border_block().title("Attributes"));

frame.render_widget(Block::default().title(controls), controls_area);
frame.render_widget(controls.into_widget(), controls_area);
frame.render_widget(resource_widget, stats_area[0]);
frame.render_widget(fields_widget, stats_area[1]);
let ctx = AsyncOpsTableCtx {
Expand All @@ -119,3 +115,20 @@ impl ResourceView {
self.initial_render = false;
}
}

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

VIEW_CONTROLS.get_or_init(|| {
let mut resource_controls = vec![ControlDisplay {
action: "return to task list",
keys: vec![KeyDisplay {
base: "esc",
utf8: Some("\u{238B} esc"),
}],
}];
resource_controls.extend(async_ops::view_controls().to_owned());

resource_controls
})
}
9 changes: 5 additions & 4 deletions tokio-console/src/view/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use crate::{
},
view::{
self, bold,
table::{self, TableList, TableListState},
controls::Controls,
table::{view_controls, TableList, TableListState},
DUR_LEN, DUR_TABLE_PRECISION,
},
};
Expand Down Expand Up @@ -163,7 +164,7 @@ impl TableList<9> for ResourcesTable {
table_list_state.len()
))]);

let controls = table::Controls::for_area(&area, styles);
let controls = Controls::new(view_controls(), &area, styles);

let layout = layout::Layout::default()
.direction(layout::Direction::Vertical)
Expand All @@ -172,7 +173,7 @@ impl TableList<9> for ResourcesTable {
let chunks = layout
.constraints(
[
layout::Constraint::Length(controls.height),
layout::Constraint::Length(controls.height()),
layout::Constraint::Max(area.height),
]
.as_ref(),
Expand Down Expand Up @@ -202,7 +203,7 @@ impl TableList<9> for ResourcesTable {
.highlight_style(Style::default().add_modifier(style::Modifier::BOLD));

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

table_list_state
.sorted_items
Expand Down
Loading