Skip to content

Commit

Permalink
feat(console): add icon representing column sorting state (#301)
Browse files Browse the repository at this point in the history
This branch adds an `▵` or `▿` icon (or `+`/`-` in ASCII-only mode) that
represents how the selected table column is sorted (ascending or
descending).

|  |  |
| ---- | ---- |
| ![Screenshot_20220304_183030](https://user-images.githubusercontent.com/10936241/156740512-05fa5be1-c9fa-43a5-adf7-fd8d99ea7e14.png) | ![Screenshot_20220304_183046](https://user-images.githubusercontent.com/10936241/156740520-a73c25e1-5aca-40f0-963d-966d50009bf4.png) |

Relates to #300
  • Loading branch information
nrskt authored Mar 8, 2022
1 parent be8bd8f commit b9e0a22
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 68 deletions.
46 changes: 29 additions & 17 deletions tokio-console/src/view/async_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ pub(crate) struct AsyncOpsTableCtx {
pub(crate) resource_id: u64,
}

impl TableList for AsyncOpsTable {
impl TableList<9> for AsyncOpsTable {
type Row = AsyncOp;
type Sort = SortBy;
type Context = AsyncOpsTableCtx;

const HEADER: &'static [&'static str] = &[
const HEADER: &'static [&'static str; 9] = &[
"ID",
"Parent",
"Task",
Expand All @@ -42,8 +42,20 @@ impl TableList for AsyncOpsTable {
"Attributes",
];

const WIDTHS: &'static [usize; 9] = &[
Self::HEADER[0].len() + 1,
Self::HEADER[1].len() + 1,
Self::HEADER[2].len() + 1,
Self::HEADER[3].len() + 1,
Self::HEADER[4].len() + 1,
Self::HEADER[5].len() + 1,
Self::HEADER[6].len() + 1,
Self::HEADER[7].len() + 1,
Self::HEADER[8].len() + 1,
];

fn render<B: tui::backend::Backend>(
table_list_state: &mut TableListState<Self>,
table_list_state: &mut TableListState<Self, 9>,
styles: &view::Styles,
frame: &mut tui::terminal::Frame<B>,
area: layout::Rect,
Expand Down Expand Up @@ -86,11 +98,11 @@ impl TableList for AsyncOpsTable {
.sort_by
.sort(now, &mut table_list_state.sorted_items);

let mut id_width = view::Width::new(Self::HEADER[0].len() as u16);
let mut parent_width = view::Width::new(Self::HEADER[1].len() as u16);
let mut task_width = view::Width::new(Self::HEADER[2].len() as u16);
let mut source_width = view::Width::new(Self::HEADER[3].len() as u16);
let mut polls_width = view::Width::new(Self::HEADER[7].len() as u16);
let mut id_width = view::Width::new(Self::WIDTHS[0] as u16);
let mut parent_width = view::Width::new(Self::WIDTHS[1] as u16);
let mut task_width = view::Width::new(Self::WIDTHS[2] as u16);
let mut source_width = view::Width::new(Self::WIDTHS[3] as u16);
let mut polls_width = view::Width::new(Self::WIDTHS[7] as u16);

let dur_cell = |dur: std::time::Duration| -> Cell<'static> {
Cell::from(styles.time_units(format!(
Expand Down Expand Up @@ -153,22 +165,22 @@ impl TableList for AsyncOpsTable {
})
};

let (selected_style, header_style) = if let Some(cyan) = styles.color(Color::Cyan) {
(Style::default().fg(cyan), Style::default())
let header_style = if styles.color(Color::Cyan).is_some() {
Style::default()
} else {
(
Style::default().remove_modifier(style::Modifier::REVERSED),
Style::default().add_modifier(style::Modifier::REVERSED),
)
Style::default().add_modifier(style::Modifier::REVERSED)
};
let header_style = header_style.add_modifier(style::Modifier::BOLD);

let header = Row::new(Self::HEADER.iter().enumerate().map(|(idx, &value)| {
let cell = Cell::from(value);
if idx == table_list_state.selected_column {
cell.style(selected_style)
if table_list_state.sort_descending {
Cell::from(styles.ascending(value))
} else {
Cell::from(styles.descending(value))
}
} else {
cell
Cell::from(value)
}
}))
.height(1)
Expand Down
8 changes: 4 additions & 4 deletions tokio-console/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ pub struct View {
/// details view), we want to leave the task list's state the way we left it
/// --- e.g., if the user previously selected a particular sorting, we want
/// it to remain sorted that way when we return to it.
tasks_list: TableListState<TasksTable>,
resources_list: TableListState<ResourcesTable>,
tasks_list: TableListState<TasksTable, 11>,
resources_list: TableListState<ResourcesTable, 9>,
state: ViewState,
pub(crate) styles: Styles,
}
Expand Down Expand Up @@ -87,8 +87,8 @@ impl View {
pub fn new(styles: Styles) -> Self {
Self {
state: ViewState::TasksList,
tasks_list: TableListState::<TasksTable>::default(),
resources_list: TableListState::<ResourcesTable>::default(),
tasks_list: TableListState::<TasksTable, 11>::default(),
resources_list: TableListState::<ResourcesTable, 9>::default(),
styles,
}
}
Expand Down
4 changes: 2 additions & 2 deletions tokio-console/src/view/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ use tui::{

pub(crate) struct ResourceView {
resource: Rc<RefCell<Resource>>,
async_ops_table: TableListState<AsyncOpsTable>,
async_ops_table: TableListState<AsyncOpsTable, 9>,
initial_render: bool,
}

impl ResourceView {
pub(super) fn new(resource: Rc<RefCell<Resource>>) -> Self {
ResourceView {
resource,
async_ops_table: TableListState::<AsyncOpsTable>::default(),
async_ops_table: TableListState::<AsyncOpsTable, 9>::default(),
initial_render: true,
}
}
Expand Down
50 changes: 31 additions & 19 deletions tokio-console/src/view/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ use tui::{
#[derive(Debug, Default)]
pub(crate) struct ResourcesTable {}

impl TableList for ResourcesTable {
impl TableList<9> for ResourcesTable {
type Row = Resource;
type Sort = SortBy;
type Context = ();

const HEADER: &'static [&'static str] = &[
const HEADER: &'static [&'static str; 9] = &[
"ID",
"Parent",
"Kind",
Expand All @@ -37,8 +37,20 @@ impl TableList for ResourcesTable {
"Attributes",
];

const WIDTHS: &'static [usize; 9] = &[
Self::HEADER[0].len() + 1,
Self::HEADER[1].len() + 1,
Self::HEADER[2].len() + 1,
Self::HEADER[3].len() + 1,
Self::HEADER[4].len() + 1,
Self::HEADER[5].len() + 1,
Self::HEADER[6].len() + 1,
Self::HEADER[7].len() + 1,
Self::HEADER[8].len() + 1,
];

fn render<B: tui::backend::Backend>(
table_list_state: &mut TableListState<Self>,
table_list_state: &mut TableListState<Self, 9>,
styles: &view::Styles,
frame: &mut tui::terminal::Frame<B>,
area: layout::Rect,
Expand All @@ -59,15 +71,15 @@ impl TableList for ResourcesTable {
.sort_by
.sort(now, &mut table_list_state.sorted_items);

let viz_len: u16 = Self::HEADER[6].len() as u16;
let viz_len: u16 = Self::WIDTHS[6] as u16;

let mut id_width = view::Width::new(Self::HEADER[0].len() as u16);
let mut parent_width = view::Width::new(Self::HEADER[1].len() as u16);
let mut id_width = view::Width::new(Self::WIDTHS[0] as u16);
let mut parent_width = view::Width::new(Self::WIDTHS[1] as u16);

let mut kind_width = view::Width::new(Self::HEADER[2].len() as u16);
let mut target_width = view::Width::new(Self::HEADER[4].len() as u16);
let mut type_width = view::Width::new(Self::HEADER[5].len() as u16);
let mut location_width = view::Width::new(Self::HEADER[7].len() as u16);
let mut kind_width = view::Width::new(Self::WIDTHS[2] as u16);
let mut target_width = view::Width::new(Self::WIDTHS[4] as u16);
let mut type_width = view::Width::new(Self::WIDTHS[5] as u16);
let mut location_width = view::Width::new(Self::WIDTHS[7] as u16);

let rows = {
let id_width = &mut id_width;
Expand Down Expand Up @@ -120,22 +132,22 @@ impl TableList for ResourcesTable {
})
};

let (selected_style, header_style) = if let Some(cyan) = styles.color(Color::Cyan) {
(Style::default().fg(cyan), Style::default())
let header_style = if styles.color(Color::Cyan).is_some() {
Style::default()
} else {
(
Style::default().remove_modifier(style::Modifier::REVERSED),
Style::default().add_modifier(style::Modifier::REVERSED),
)
Style::default().add_modifier(style::Modifier::REVERSED)
};
let header_style = header_style.add_modifier(style::Modifier::BOLD);

let header = Row::new(Self::HEADER.iter().enumerate().map(|(idx, &value)| {
let cell = Cell::from(value);
if idx == table_list_state.selected_column {
cell.style(selected_style)
if table_list_state.sort_descending {
Cell::from(styles.ascending(value))
} else {
Cell::from(styles.descending(value))
}
} else {
cell
Cell::from(value)
}
}))
.height(1)
Expand Down
19 changes: 19 additions & 0 deletions tokio-console/src/view/styles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ impl Styles {
)
}

pub fn selected(&self, value: &str) -> Span<'static> {
let style = if let Some(cyan) = self.color(Color::Cyan) {
Style::default().fg(cyan)
} else {
Style::default().remove_modifier(Modifier::REVERSED)
};
Span::styled(value.to_string(), style)
}

pub fn ascending(&self, value: &str) -> Span<'static> {
let value = format!("{}{}", value, self.if_utf8("▵", "+"));
self.selected(&value)
}

pub fn descending(&self, value: &str) -> Span<'static> {
let value = format!("{}{}", value, self.if_utf8("▿", "-"));
self.selected(&value)
}

pub fn color(&self, color: Color) -> Option<Color> {
use Palette::*;
match (self.palette, color) {
Expand Down
15 changes: 8 additions & 7 deletions tokio-console/src/view/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ use tui::{
use std::cell::RefCell;
use std::rc::Weak;

pub(crate) trait TableList {
pub(crate) trait TableList<const N: usize> {
type Row;
type Sort: SortBy + TryFrom<usize>;
type Context;

const HEADER: &'static [&'static str];
const HEADER: &'static [&'static str; N];
const WIDTHS: &'static [usize; N];

fn render<B: tui::backend::Backend>(
state: &mut TableListState<Self>,
state: &mut TableListState<Self, N>,
styles: &view::Styles,
frame: &mut tui::terminal::Frame<B>,
area: layout::Rect,
Expand All @@ -34,7 +35,7 @@ pub(crate) trait SortBy {
fn as_column(&self) -> usize;
}

pub(crate) struct TableListState<T: TableList> {
pub(crate) struct TableListState<T: TableList<N>, const N: usize> {
pub(crate) sorted_items: Vec<Weak<RefCell<T::Row>>>,
pub(crate) sort_by: T::Sort,
pub(crate) selected_column: usize,
Expand All @@ -49,7 +50,7 @@ pub(crate) struct Controls {
pub(crate) height: u16,
}

impl<T: TableList> TableListState<T> {
impl<T: TableList<N>, const N: usize> TableListState<T, N> {
pub(in crate::view) fn len(&self) -> usize {
self.sorted_items.len()
}
Expand Down Expand Up @@ -181,9 +182,9 @@ impl<T: TableList> TableListState<T> {
}
}

impl<T> Default for TableListState<T>
impl<T, const N: usize> Default for TableListState<T, N>
where
T: TableList,
T: TableList<N>,
T::Sort: Default,
{
fn default() -> Self {
Expand Down
53 changes: 34 additions & 19 deletions tokio-console/src/view/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,39 @@ use tui::{
#[derive(Debug, Default)]
pub(crate) struct TasksTable {}

impl TableList for TasksTable {
impl TableList<11> for TasksTable {
type Row = Task;
type Sort = SortBy;
type Context = ();

const HEADER: &'static [&'static str] = &[
const HEADER: &'static [&'static str; 11] = &[
"Warn", "ID", "State", "Name", "Total", "Busy", "Idle", "Polls", "Target", "Location",
"Fields",
];

const WIDTHS: &'static [usize; 11] = &[
Self::HEADER[0].len() + 1,
Self::HEADER[1].len() + 1,
Self::HEADER[2].len() + 1,
Self::HEADER[3].len() + 1,
Self::HEADER[4].len() + 1,
Self::HEADER[5].len() + 1,
Self::HEADER[6].len() + 1,
Self::HEADER[7].len() + 1,
Self::HEADER[8].len() + 1,
Self::HEADER[9].len() + 1,
Self::HEADER[10].len() + 1,
];

fn render<B: tui::backend::Backend>(
table_list_state: &mut TableListState<Self>,
table_list_state: &mut TableListState<Self, 11>,
styles: &view::Styles,
frame: &mut tui::terminal::Frame<B>,
area: layout::Rect,
state: &mut State,
_: Self::Context,
) {
let state_len: u16 = Self::HEADER[2].len() as u16;
let state_len: u16 = Self::WIDTHS[2] as u16;
let now = if let Some(now) = state.last_updated_at() {
now
} else {
Expand All @@ -63,15 +77,16 @@ impl TableList for TasksTable {
};

// Start out wide enough to display the column headers...
let mut warn_width = view::Width::new(Self::HEADER[0].len() as u16);
let mut id_width = view::Width::new(Self::HEADER[1].len() as u16);
let mut name_width = view::Width::new(Self::HEADER[3].len() as u16);
let mut polls_width = view::Width::new(Self::HEADER[7].len() as u16);
let mut target_width = view::Width::new(Self::HEADER[8].len() as u16);
let mut location_width = view::Width::new(Self::HEADER[9].len() as u16);
let mut warn_width = view::Width::new(Self::WIDTHS[0] as u16);
let mut id_width = view::Width::new(Self::WIDTHS[1] as u16);
let mut name_width = view::Width::new(Self::WIDTHS[3] as u16);
let mut polls_width = view::Width::new(Self::WIDTHS[7] as u16);
let mut target_width = view::Width::new(Self::WIDTHS[8] as u16);
let mut location_width = view::Width::new(Self::WIDTHS[9] as u16);

let mut num_idle = 0;
let mut num_running = 0;

let rows = {
let id_width = &mut id_width;
let target_width = &mut target_width;
Expand Down Expand Up @@ -138,22 +153,22 @@ impl TableList for TasksTable {
})
};

let (selected_style, header_style) = if let Some(cyan) = styles.color(Color::Cyan) {
(Style::default().fg(cyan), Style::default())
let header_style = if styles.color(Color::Cyan).is_some() {
Style::default()
} else {
(
Style::default().remove_modifier(style::Modifier::REVERSED),
Style::default().add_modifier(style::Modifier::REVERSED),
)
Style::default().add_modifier(style::Modifier::REVERSED)
};
let header_style = header_style.add_modifier(style::Modifier::BOLD);

let header = Row::new(Self::HEADER.iter().enumerate().map(|(idx, &value)| {
let cell = Cell::from(value);
if idx == table_list_state.selected_column {
cell.style(selected_style)
if table_list_state.sort_descending {
Cell::from(styles.ascending(value))
} else {
Cell::from(styles.descending(value))
}
} else {
cell
Cell::from(value)
}
}))
.height(1)
Expand Down

0 comments on commit b9e0a22

Please sign in to comment.