This commit is contained in:
ClementTsang 2021-12-28 23:46:55 -05:00
parent 3dbe788920
commit 65e36901c0
10 changed files with 132 additions and 474 deletions

View File

@ -29,7 +29,6 @@ use frozen_state::FrozenState;
use crate::{
canvas::Painter,
constants,
data_conversion::ConvertedData,
tuine::{Application, Element, Status, ViewContext},
units::data_units::DataUnit,
Pid,
@ -135,6 +134,7 @@ pub enum AppMessages {
to_kill: Vec<Pid>,
signal: Option<i32>,
},
Expand,
ToggleFreeze,
Reset,
Clean,
@ -150,23 +150,16 @@ pub struct AppState {
frozen_state: FrozenState,
current_screen: CurrentScreen,
pub painter: Painter,
layout: WidgetLayoutNode,
terminator: Arc<AtomicBool>,
}
impl AppState {
/// Creates a new [`AppState`].
pub fn new(
app_config: AppConfig, filters: DataFilters, layout_tree_output: LayoutCreationOutput,
painter: Painter,
app_config: AppConfig, filters: DataFilters, layout: WidgetLayoutNode,
used_widgets: UsedWidgets, painter: Painter,
) -> Result<Self> {
let LayoutCreationOutput {
layout_tree: _,
root: _,
widget_lookup_map,
selected: _,
used_widgets,
} = layout_tree_output;
Ok(Self {
app_config,
filters,
@ -177,7 +170,7 @@ impl AppState {
data_collection: Default::default(),
frozen_state: Default::default(),
current_screen: Default::default(),
layout,
terminator: Self::register_terminator()?,
})
}
@ -221,6 +214,10 @@ impl Application for AppState {
// FIXME: Handle process termination
true
}
AppMessages::Expand => {
// FIXME: Expand current widget
true
}
AppMessages::ToggleFreeze => {
self.frozen_state.toggle(&self.data_collection);
true
@ -246,7 +243,27 @@ impl Application for AppState {
}
fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element<Self::Message> {
todo!()
match self.current_screen {
CurrentScreen::Main => {
// The main screen.
todo!()
}
CurrentScreen::Expanded => {
// Displayed when a user "expands" a widget
todo!()
}
CurrentScreen::Help => {
// The help dialog.
todo!()
}
CurrentScreen::Delete => {
// The delete dialog.
todo!()
}
}
}
fn destructor(&mut self) {

View File

@ -1,14 +1,7 @@
use crate::{
app::{
BasicCpu, BasicMem, BasicNet, BatteryTable, Carousel, DiskTable, Empty, MemGraph, NetGraph,
OldNetGraph, ProcessManager, SelectableType, TempTable,
},
app::SelectableType,
error::{BottomError, Result},
options::{
layout_options::{LayoutRow, LayoutRowChild, LayoutRule},
ProcessDefaults,
},
tuine::{Element, Flex},
options::layout_options::{FinalWidget, LayoutRow, LayoutRowChild, LayoutRule},
};
use indextree::{Arena, NodeId};
use rustc_hash::FxHashMap;
@ -17,9 +10,7 @@ use tui::layout::Rect;
use crate::app::widgets::Widget;
use super::{
event::SelectionAction, AppConfig, AppState, CpuGraph, OldBottomWidget, TimeGraph, UsedWidgets,
};
use super::{event::SelectionAction, OldBottomWidget, UsedWidgets};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum BottomWidgetType {
@ -41,12 +32,6 @@ pub enum BottomWidgetType {
Carousel,
}
impl Default for BottomWidgetType {
fn default() -> Self {
BottomWidgetType::Empty
}
}
impl FromStr for BottomWidgetType {
type Err = BottomError;
@ -122,16 +107,6 @@ pub struct RowLayout {
pub bound: Rect,
}
impl RowLayout {
fn new(parent_rule: LayoutRule) -> Self {
Self {
last_selected: None,
parent_rule,
bound: Rect::default(),
}
}
}
/// Represents a column in the layout tree.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ColLayout {
@ -140,16 +115,6 @@ pub struct ColLayout {
pub bound: Rect,
}
impl ColLayout {
fn new(parent_rule: LayoutRule) -> Self {
Self {
last_selected: None,
parent_rule,
bound: Rect::default(),
}
}
}
/// Represents a widget in the layout tree.
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct WidgetLayout {
@ -178,327 +143,125 @@ pub enum MovementDirection {
Down,
}
pub fn initialize_widget_layout<Message>(
layout_rows: &[LayoutRow], app: &AppState,
) -> anyhow::Result<Element<Message>> {
let mut root = Flex::column();
for layout_row in layout_rows {
let mut row = Flex::row();
if let Some(children) = &layout_row.child {
for child in children {
match child {
LayoutRowChild::Widget(widget) => {}
LayoutRowChild::Carousel {
carousel_children,
default,
} => {}
LayoutRowChild::LayoutCol {
ratio,
child: children,
} => for child in children {},
}
}
}
root = root.with_child(row);
}
Ok(root.into())
/// An intermediate representation of the widget layout.
pub enum WidgetLayoutNode {
Row {
children: Vec<WidgetLayoutNode>,
parent_rule: LayoutRule,
},
Col {
children: Vec<WidgetLayoutNode>,
parent_rule: LayoutRule,
},
Carousel {
children: Vec<BottomWidgetType>,
selected: bool,
},
Widget {
widget_type: BottomWidgetType,
selected: bool,
width_rule: LayoutRule,
height_rule: LayoutRule,
},
}
/// A wrapper struct to simplify the output of [`create_layout_tree`].
pub struct LayoutCreationOutput {
pub layout_tree: Arena<LayoutNode>,
pub root: NodeId,
pub widget_lookup_map: FxHashMap<NodeId, OldBottomWidget>,
pub selected: NodeId,
pub used_widgets: UsedWidgets,
}
/// Creates a new [`Arena<LayoutNode>`] from the given config and returns it, along with the [`NodeId`] representing
/// the root of the newly created [`Arena`], a mapping from [`NodeId`]s to [`BottomWidget`]s, and optionally, a default
/// selected [`NodeId`].
// FIXME: [AFTER REFACTOR] This is currently jury-rigged "glue" just to work with the existing config system! We are NOT keeping it like this, it's too awful to keep like this!
pub fn create_layout_tree(
rows: &[LayoutRow], process_defaults: ProcessDefaults, app_config_fields: &AppConfig,
) -> Result<LayoutCreationOutput> {
fn add_widget_to_map(
widget_lookup_map: &mut FxHashMap<NodeId, OldBottomWidget>, widget_type: BottomWidgetType,
widget_id: NodeId, process_defaults: &ProcessDefaults, app_config_fields: &AppConfig,
width: LayoutRule, height: LayoutRule,
) -> Result<()> {
match widget_type {
BottomWidgetType::Cpu => {
widget_lookup_map.insert(
widget_id,
CpuGraph::from_config(app_config_fields)
.width(width)
.height(height)
.into(),
);
}
BottomWidgetType::Mem => {
let graph = TimeGraph::from_config(app_config_fields);
widget_lookup_map.insert(
widget_id,
MemGraph::new(graph).width(width).height(height).into(),
);
}
BottomWidgetType::Net => {
if app_config_fields.use_old_network_legend {
widget_lookup_map.insert(
widget_id,
OldNetGraph::from_config(app_config_fields)
.width(width)
.height(height)
.into(),
);
} else {
widget_lookup_map.insert(
widget_id,
NetGraph::from_config(app_config_fields)
.width(width)
.height(height)
.into(),
);
}
}
BottomWidgetType::Proc => {
widget_lookup_map.insert(
widget_id,
ProcessManager::new(process_defaults, app_config_fields)
.width(width)
.height(height)
.basic_mode(app_config_fields.use_basic_mode)
.show_scroll_index(app_config_fields.show_table_scroll_position)
.into(),
);
}
BottomWidgetType::Temp => {
widget_lookup_map.insert(
widget_id,
TempTable::from_config(app_config_fields)
.set_temp_type(app_config_fields.temperature_type.clone())
.width(width)
.height(height)
.basic_mode(app_config_fields.use_basic_mode)
.show_scroll_index(app_config_fields.show_table_scroll_position)
.into(),
);
}
BottomWidgetType::Disk => {
widget_lookup_map.insert(
widget_id,
DiskTable::from_config(app_config_fields)
.width(width)
.height(height)
.basic_mode(app_config_fields.use_basic_mode)
.show_scroll_index(app_config_fields.show_table_scroll_position)
.into(),
);
}
BottomWidgetType::Battery => {
widget_lookup_map.insert(
widget_id,
BatteryTable::default()
.width(width)
.height(height)
.basic_mode(app_config_fields.use_basic_mode)
.into(),
);
}
BottomWidgetType::BasicCpu => {
widget_lookup_map.insert(
widget_id,
BasicCpu::from_config(app_config_fields).width(width).into(),
);
}
BottomWidgetType::BasicMem => {
widget_lookup_map.insert(widget_id, BasicMem::default().width(width).into());
}
BottomWidgetType::BasicNet => {
widget_lookup_map.insert(
widget_id,
BasicNet::from_config(app_config_fields).width(width).into(),
);
}
BottomWidgetType::Empty => {
widget_lookup_map.insert(
widget_id,
Empty::default().width(width).height(height).into(),
);
}
_ => {}
}
Ok(())
}
let mut arena = Arena::new();
let root_id = arena.new_node(LayoutNode::Col(ColLayout::new(LayoutRule::Expand {
ratio: 1,
})));
let mut widget_lookup_map = FxHashMap::default();
let mut first_selected = None;
let mut first_widget_seen = None; // Backup selected widget
/// Parses the layout in the config into an intermediate representation.
pub fn parse_widget_layout(
layout_rows: &[LayoutRow],
) -> anyhow::Result<(WidgetLayoutNode, UsedWidgets)> {
let mut root_children = Vec::with_capacity(layout_rows.len());
let mut used_widgets = UsedWidgets::default();
for row in rows {
let row_id = arena.new_node(LayoutNode::Row(RowLayout::new(
row.ratio
.map(|ratio| LayoutRule::Expand { ratio })
.unwrap_or(LayoutRule::Child),
)));
root_id.append(row_id, &mut arena);
for layout_row in layout_rows {
if let Some(children) = &layout_row.child {
let mut row_children = Vec::with_capacity(children.len());
if let Some(children) = &row.child {
for child in children {
match child {
LayoutRowChild::Widget(widget) => {
let widget_id = arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
row_id.append(widget_id, &mut arena);
let FinalWidget {
rule,
widget_type,
default,
} = widget;
if let Some(true) = widget.default {
first_selected = Some(widget_id);
}
if first_widget_seen.is_none() {
first_widget_seen = Some(widget_id);
}
let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
let widget_type = widget_type.parse::<BottomWidgetType>()?;
used_widgets.add(&widget_type);
add_widget_to_map(
&mut widget_lookup_map,
row_children.push(WidgetLayoutNode::Widget {
widget_type,
widget_id,
&process_defaults,
app_config_fields,
widget.rule.unwrap_or_default(),
LayoutRule::default(),
)?;
selected: default.unwrap_or(false),
width_rule: rule.unwrap_or_default(),
height_rule: LayoutRule::default(),
});
}
LayoutRowChild::Carousel {
carousel_children,
default,
} => {
if !carousel_children.is_empty() {
let mut child_ids = Vec::with_capacity(carousel_children.len());
let carousel_widget_id =
arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
row_id.append(carousel_widget_id, &mut arena);
let mut car_children = Vec::with_capacity(carousel_children.len());
for widget_type in carousel_children {
let widget_type = widget_type.parse::<BottomWidgetType>()?;
used_widgets.add(&widget_type);
if let Some(true) = default {
first_selected = Some(carousel_widget_id);
}
if first_widget_seen.is_none() {
first_widget_seen = Some(carousel_widget_id);
}
// Handle the rest of the children.
for child in carousel_children {
let widget_id =
arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
carousel_widget_id.append(widget_id, &mut arena);
let widget_type = child.parse::<BottomWidgetType>()?;
used_widgets.add(&widget_type);
add_widget_to_map(
&mut widget_lookup_map,
widget_type,
widget_id,
&process_defaults,
app_config_fields,
LayoutRule::default(),
LayoutRule::default(),
)?;
child_ids.push(widget_id);
}
widget_lookup_map.insert(
carousel_widget_id,
Carousel::new(
child_ids
.into_iter()
.filter_map(|child_id| {
widget_lookup_map
.get(&child_id)
.map(|w| (child_id, w.get_pretty_name().into()))
})
.collect(),
)
.into(),
);
car_children.push(widget_type);
}
row_children.push(WidgetLayoutNode::Carousel {
children: car_children,
selected: default.unwrap_or(false),
});
}
LayoutRowChild::LayoutCol {
ratio,
child: col_child,
child: children,
} => {
let col_id = arena.new_node(LayoutNode::Col(ColLayout::new(
ratio
.map(|ratio| LayoutRule::Expand { ratio })
.unwrap_or(LayoutRule::Child),
)));
row_id.append(col_id, &mut arena);
for widget in col_child {
let widget_id =
arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
col_id.append(widget_id, &mut arena);
if let Some(true) = widget.default {
first_selected = Some(widget_id);
}
if first_widget_seen.is_none() {
first_widget_seen = Some(widget_id);
}
let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
let mut col_children = Vec::with_capacity(children.len());
for widget in children {
let FinalWidget {
rule,
widget_type,
default,
} = widget;
let widget_type = widget_type.parse::<BottomWidgetType>()?;
used_widgets.add(&widget_type);
add_widget_to_map(
&mut widget_lookup_map,
col_children.push(WidgetLayoutNode::Widget {
widget_type,
widget_id,
&process_defaults,
app_config_fields,
LayoutRule::default(),
widget.rule.unwrap_or_default(),
)?;
selected: default.unwrap_or(false),
width_rule: LayoutRule::default(),
height_rule: rule.unwrap_or_default(),
});
}
row_children.push(WidgetLayoutNode::Col {
children: col_children,
parent_rule: match ratio {
Some(ratio) => LayoutRule::Expand { ratio: *ratio },
None => LayoutRule::Child,
},
});
}
}
}
let row = WidgetLayoutNode::Row {
children: row_children,
parent_rule: match layout_row.ratio {
Some(ratio) => LayoutRule::Expand { ratio },
None => LayoutRule::Child,
},
};
root_children.push(row);
}
}
let selected: NodeId;
if let Some(first_selected) = first_selected {
selected = first_selected;
} else if let Some(first_widget_seen) = first_widget_seen {
selected = first_widget_seen;
} else {
return Err(BottomError::ConfigError(
"A layout cannot contain zero widgets!".to_string(),
));
}
let root = WidgetLayoutNode::Col {
children: root_children,
parent_rule: LayoutRule::Expand { ratio: 1 },
};
correct_layout_last_selections(&mut arena, selected);
Ok(LayoutCreationOutput {
layout_tree: arena,
root: root_id,
widget_lookup_map,
selected,
used_widgets,
})
Ok((root, used_widgets))
}
/// We may have situations where we also have to make sure the correct layout indices are selected.

View File

@ -119,7 +119,6 @@ pub struct SimpleSortableColumn {
original_name: Cow<'static, str>,
pub shortcut: Option<(KeyEvent, String)>,
pub default_descending: bool,
x_bounds: Option<(u16, u16)>,
pub internal: SimpleColumn,
@ -138,7 +137,6 @@ impl SimpleSortableColumn {
original_name,
shortcut,
default_descending,
x_bounds: None,
internal: SimpleColumn::new(full_name, desired_width),
sorting_status: SortStatus::NotSorting,
}

View File

@ -225,7 +225,7 @@ pub const HELP_CONTENTS_TEXT: [&str; 8] = [
];
pub const GENERAL_HELP_TITLE: &str = "General";
pub const GENERAL_HELP_TEXT: [[&str; 2]; 21] = [
pub const GENERAL_HELP_TEXT: [[&str; 2]; 23] = [
["q, Ctrl-c", "Quit"],
[
"Esc",
@ -251,6 +251,8 @@ pub const GENERAL_HELP_TEXT: [[&str; 2]; 21] = [
["+", "Zoom in on chart (decrease time range)"],
["-", "Zoom out on chart (increase time range)"],
["=", "Reset zoom"],
["Page Up", "Move up one page in a table"],
["Page Down", "Move down one page in a table"],
[
"Mouse scroll",
"Scroll through the tables or zoom in/out of charts by scrolling up/down",

View File

@ -241,7 +241,7 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
&rows
};
let layout_tree_output = create_layout_tree(row_ref, process_defaults, &app_config_fields)?;
let (layout, used_widgets) = parse_widget_layout(row_ref)?;
let disk_filter =
get_ignore_list(&config.disk_filter).context("Update 'disk_filter' in your config file")?;
@ -259,7 +259,13 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
};
let painter = Painter::init(&config, get_color_scheme(&matches, &config)?)?;
AppState::new(app_config_fields, data_filter, layout_tree_output, painter)
AppState::new(
app_config_fields,
data_filter,
layout,
used_widgets,
painter,
)
}
fn get_update_rate_in_milliseconds(

View File

@ -1,6 +1,3 @@
use anyhow::{anyhow, Result};
use enum_dispatch::enum_dispatch;
pub mod simple_table;
pub use simple_table::*;

View File

@ -1,10 +1,9 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use tui::style::Style;
use crate::tuine::{
self, block,
text_table::{self, DataRow, SortType, TextTableProps},
Block, Event, Shortcut, StatefulComponent, Status, TextTable, TmpComponent, ViewContext,
Block, Shortcut, StatefulComponent, TextTable, TmpComponent, ViewContext,
};
/// A set of styles for a [`SimpleTable`].

View File

@ -3,8 +3,8 @@ use crate::{
canvas::Painter,
data_conversion::ConvertedData,
tuine::{
Bounds, DataRow, DrawContext, LayoutNode, SimpleTable, Size, StateContext, Status,
TmpComponent, ViewContext,
Bounds, DrawContext, LayoutNode, SimpleTable, Size, StateContext, Status, TmpComponent,
ViewContext,
},
};

View File

@ -1,121 +0,0 @@
use tui::{
layout::Rect,
text::Span,
widgets::{Block, Borders},
};
use crate::canvas::Painter;
/// A factory pattern builder for a tui [`Block`].
pub struct BlockBuilder {
borders: Borders,
selected: bool,
show_esc: bool,
name: &'static str,
hide_title: bool,
extra_text: Option<String>,
}
impl BlockBuilder {
/// Creates a new [`BlockBuilder`] with the name of block.
pub fn new(name: &'static str) -> Self {
Self {
borders: Borders::ALL,
selected: false,
show_esc: false,
name,
hide_title: false,
extra_text: None,
}
}
/// Indicates that this block is currently selected, and should be drawn as such.
pub fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
/// Indicates that this block should show esc, and should be drawn as such.
pub fn show_esc(mut self, show_esc: bool) -> Self {
self.show_esc = show_esc;
self
}
/// Indicates that this block has some extra text beyond the name.
pub fn extra_text(mut self, extra_text: Option<String>) -> Self {
self.extra_text = extra_text;
self
}
/// Determines the borders of the built [`Block`].
pub fn borders(mut self, borders: Borders) -> Self {
self.borders = borders;
self
}
/// Forcibly hides the title of the built [`Block`].
pub fn hide_title(mut self, hide_title: bool) -> Self {
self.hide_title = hide_title;
self
}
/// Converts the [`BlockBuilder`] into an actual [`Block`].
pub fn build(self, painter: &Painter, area: Rect) -> Block<'static> {
let has_title = !self.hide_title
&& (self.borders.contains(Borders::TOP) || self.borders.contains(Borders::BOTTOM));
let border_style = if self.selected {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let block = Block::default()
.border_style(border_style)
.borders(self.borders);
let inner_width = block.inner(area).width as usize;
if has_title {
let name = Span::styled(
format!(" {} ", self.name),
painter.colours.widget_title_style,
);
let mut title_len = name.width();
let mut title = vec![name, Span::from(""), Span::from(""), Span::from("")];
if self.show_esc {
const EXPAND_TEXT: &str = " Esc to go back ";
const EXPAND_TEXT_LEN: usize = EXPAND_TEXT.len();
let expand_span = Span::styled(EXPAND_TEXT, border_style);
if title_len + EXPAND_TEXT_LEN <= inner_width {
title_len += EXPAND_TEXT_LEN;
title[3] = expand_span;
}
}
if let Some(extra_text) = self.extra_text {
let extra_span = Span::styled(
format!("{} ", extra_text),
painter.colours.widget_title_style,
);
let width = extra_span.width();
if title_len + width <= inner_width {
title_len += width;
title[1] = extra_span;
}
}
if self.show_esc {
let difference = inner_width.saturating_sub(title_len);
title[2] = Span::styled("".repeat(difference), border_style);
}
block.title(title)
} else {
block
}
}
}

View File

@ -3,6 +3,3 @@ pub use custom_legend_chart::TimeChart;
pub mod pipe_gauge;
pub use pipe_gauge::PipeGauge;
pub mod block_builder;
pub use block_builder::BlockBuilder;