Layout WIP

This commit is contained in:
ClementTsang 2021-12-28 20:40:55 -05:00
parent 95318bb3e4
commit 3dbe788920
29 changed files with 417 additions and 478 deletions

View File

@ -30,14 +30,12 @@ use crate::{
canvas::Painter,
constants,
data_conversion::ConvertedData,
tuine::{Application, Element, Flex, Status, ViewContext},
tuine::{Application, Element, Status, ViewContext},
units::data_units::DataUnit,
Pid,
};
use anyhow::Result;
use indextree::{Arena, NodeId};
use rustc_hash::FxHashMap;
// FIXME: Move this!
#[derive(Debug, Clone)]
@ -86,10 +84,10 @@ impl UsedWidgets {
}
}
/// AppConfigFields is meant to cover basic fields that would normally be set
/// [`AppConfig`] is meant to cover basic fields that would normally be set
/// by config files or launch options.
#[derive(Debug)]
pub struct AppConfigFields {
pub struct AppConfig {
pub update_rate_in_milliseconds: u64,
pub temperature_type: temperature::TemperatureType,
pub use_dot: bool,
@ -145,34 +143,32 @@ pub enum AppMessages {
pub struct AppState {
pub data_collection: DataCollection,
pub used_widgets: UsedWidgets,
pub filters: DataFilters,
pub app_config_fields: AppConfigFields,
pub app_config: AppConfig,
// --- NEW STUFF ---
frozen_state: FrozenState,
current_screen: CurrentScreen,
painter: Painter,
pub painter: Painter,
terminator: Arc<AtomicBool>,
}
impl AppState {
/// Creates a new [`AppState`].
pub fn new(
app_config_fields: AppConfigFields, filters: DataFilters,
layout_tree_output: LayoutCreationOutput, painter: Painter,
app_config: AppConfig, filters: DataFilters, layout_tree_output: LayoutCreationOutput,
painter: Painter,
) -> Result<Self> {
let LayoutCreationOutput {
layout_tree,
root: layout_tree_root,
layout_tree: _,
root: _,
widget_lookup_map,
selected: selected_widget,
selected: _,
used_widgets,
} = layout_tree_output;
Ok(Self {
app_config_fields,
app_config,
filters,
used_widgets,
painter,
@ -250,48 +246,7 @@ impl Application for AppState {
}
fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element<Self::Message> {
use crate::tuine::FlexElement;
use crate::tuine::StatefulComponent;
use crate::tuine::{TempTable, TextTable, TextTableProps};
let data = match &self.frozen_state {
FrozenState::NotFrozen => &self.data_collection,
FrozenState::Frozen(frozen_data_collection) => &frozen_data_collection,
};
let mut converted_data = ConvertedData::default();
Flex::column()
.with_flex_child(
Flex::row_with_children(vec![
FlexElement::new(TempTable::build(
ctx,
&self.painter,
converted_data.temp_table(data, self.app_config_fields.temperature_type),
)),
FlexElement::new(TextTable::build(
ctx,
TextTableProps::new(vec!["D", "E", "F"]),
)),
]),
1,
)
.with_flex_child(
Flex::row_with_children(vec![
FlexElement::new(TextTable::build(
ctx,
TextTableProps::new(vec!["G", "H", "I", "J"]),
)),
FlexElement::new(TextTable::build(
ctx,
TextTableProps::new(vec!["L", "EM", "NO", "PQ"])
.rows(vec![vec![1, 2, 3, 4], vec![4, 3, 2, 1]])
.default_sort(crate::tuine::SortType::Descending(0)),
)),
]),
2,
)
.into()
todo!()
}
fn destructor(&mut self) {

View File

@ -5,19 +5,20 @@ use crate::{
},
error::{BottomError, Result},
options::{
layout_options::{LayoutRule, Row, RowChildren},
layout_options::{LayoutRow, LayoutRowChild, LayoutRule},
ProcessDefaults,
},
tuine::{Element, Flex},
};
use indextree::{Arena, NodeId};
use rustc_hash::FxHashMap;
use std::cmp::min;
use std::str::FromStr;
use tui::layout::Rect;
use crate::app::widgets::Widget;
use super::{
event::SelectionAction, AppConfigFields, BottomWidget, CpuGraph, TimeGraph, UsedWidgets,
event::SelectionAction, AppConfig, AppState, CpuGraph, OldBottomWidget, TimeGraph, UsedWidgets,
};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
@ -46,7 +47,7 @@ impl Default for BottomWidgetType {
}
}
impl std::str::FromStr for BottomWidgetType {
impl FromStr for BottomWidgetType {
type Err = BottomError;
fn from_str(s: &str) -> Result<Self> {
@ -169,22 +170,6 @@ pub enum LayoutNode {
Widget(WidgetLayout),
}
impl LayoutNode {
fn set_bound(&mut self, bound: Rect) {
match self {
LayoutNode::Row(row) => {
row.bound = bound;
}
LayoutNode::Col(col) => {
col.bound = bound;
}
LayoutNode::Widget(widget) => {
widget.bound = bound;
}
}
}
}
/// Relative movement direction from the currently selected widget.
pub enum MovementDirection {
Left,
@ -193,11 +178,40 @@ 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())
}
/// 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, BottomWidget>,
pub widget_lookup_map: FxHashMap<NodeId, OldBottomWidget>,
pub selected: NodeId,
pub used_widgets: UsedWidgets,
}
@ -207,11 +221,11 @@ pub struct LayoutCreationOutput {
/// 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: &[Row], process_defaults: ProcessDefaults, app_config_fields: &AppConfigFields,
rows: &[LayoutRow], process_defaults: ProcessDefaults, app_config_fields: &AppConfig,
) -> Result<LayoutCreationOutput> {
fn add_widget_to_map(
widget_lookup_map: &mut FxHashMap<NodeId, BottomWidget>, widget_type: BottomWidgetType,
widget_id: NodeId, process_defaults: &ProcessDefaults, app_config_fields: &AppConfigFields,
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 {
@ -341,7 +355,7 @@ pub fn create_layout_tree(
if let Some(children) = &row.child {
for child in children {
match child {
RowChildren::Widget(widget) => {
LayoutRowChild::Widget(widget) => {
let widget_id = arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
row_id.append(widget_id, &mut arena);
@ -366,7 +380,7 @@ pub fn create_layout_tree(
LayoutRule::default(),
)?;
}
RowChildren::Carousel {
LayoutRowChild::Carousel {
carousel_children,
default,
} => {
@ -422,7 +436,7 @@ pub fn create_layout_tree(
);
}
}
RowChildren::Col {
LayoutRowChild::LayoutCol {
ratio,
child: col_child,
} => {
@ -518,7 +532,7 @@ pub enum MoveWidgetResult {
/// A more restricted movement, only within a single widget.
pub fn move_expanded_widget_selection(
widget_lookup_map: &mut FxHashMap<NodeId, BottomWidget>, current_widget_id: NodeId,
widget_lookup_map: &mut FxHashMap<NodeId, OldBottomWidget>, current_widget_id: NodeId,
direction: MovementDirection,
) -> MoveWidgetResult {
if let Some(current_widget) = widget_lookup_map.get_mut(&current_widget_id) {
@ -542,8 +556,9 @@ pub fn move_expanded_widget_selection(
/// - Only [`LayoutNode::Widget`]s are leaves.
/// - Only [`LayoutNode::Row`]s or [`LayoutNode::Col`]s are non-leaves.
pub fn move_widget_selection(
layout_tree: &mut Arena<LayoutNode>, widget_lookup_map: &mut FxHashMap<NodeId, BottomWidget>,
current_widget_id: NodeId, direction: MovementDirection,
layout_tree: &mut Arena<LayoutNode>,
widget_lookup_map: &mut FxHashMap<NodeId, OldBottomWidget>, current_widget_id: NodeId,
direction: MovementDirection,
) -> MoveWidgetResult {
// We first give our currently-selected widget a chance to react to the movement - it may handle it internally!
let handled = {
@ -816,317 +831,3 @@ pub fn move_widget_selection(
}
}
}
/// Generates the bounds for each node in the `arena, taking into account per-leaf desires,
/// and finally storing the calculated bounds in the given `arena`.
///
/// Stored bounds are given in *relative* coordinates - they are relative to their parents.
/// That is, you may have a child widget "start" at (0, 0), but its parent is actually at x = 5,s
/// so the absolute coordinate of the child widget is actually (5, 0).
///
/// The algorithm is mostly based on the algorithm used by Flutter, adapted to work for
/// our use case. For more information, check out both:
///
/// - [How the constraint system works in Flutter](https://flutter.dev/docs/development/ui/layout/constraints)
/// - [How Flutter does sublinear layout](https://flutter.dev/docs/resources/inside-flutter#sublinear-layout)
pub fn generate_layout(
root: NodeId, arena: &mut Arena<LayoutNode>, area: Rect,
lookup_map: &FxHashMap<NodeId, BottomWidget>,
) {
// TODO: [Optimization, Layout] Add some caching/dirty mechanisms to reduce calls.
/// A [`Size`] is a set of widths and heights that a node in our layout wants to be.
#[derive(Default, Clone, Copy, Debug)]
struct Size {
width: u16,
height: u16,
}
/// A [`LayoutConstraint`] is just a set of maximal widths/heights.
#[derive(Clone, Copy, Debug)]
struct LayoutConstraints {
max_width: u16,
max_height: u16,
}
impl LayoutConstraints {
fn new(max_width: u16, max_height: u16) -> Self {
Self {
max_width,
max_height,
}
}
/// Shrinks the width of itself given another width.
fn shrink_width(&mut self, width: u16) {
self.max_width = self.max_width.saturating_sub(width);
}
/// Shrinks the height of itself given another height.
fn shrink_height(&mut self, height: u16) {
self.max_height = self.max_height.saturating_sub(height);
}
/// Returns a new [`LayoutConstraints`] with a new width given a ratio.
fn ratio_width(&self, numerator: u32, denominator: u32) -> Self {
Self {
max_width: (self.max_width as u32 * numerator / denominator) as u16,
max_height: self.max_height,
}
}
/// Returns a new [`LayoutConstraints`] with a new height given a ratio.
fn ratio_height(&self, numerator: u32, denominator: u32) -> Self {
Self {
max_width: self.max_width,
max_height: (self.max_height as u32 * numerator / denominator) as u16,
}
}
}
/// The internal recursive call to build a layout. Builds off of `arena` and stores bounds inside it.
fn layout(
node: NodeId, arena: &mut Arena<LayoutNode>, lookup_map: &FxHashMap<NodeId, BottomWidget>,
mut constraints: LayoutConstraints,
) -> Size {
if let Some(layout_node) = arena.get(node).map(|n| n.get()) {
match layout_node {
LayoutNode::Row(row) => {
let children = node.children(arena).collect::<Vec<_>>();
let mut row_bounds = vec![Size::default(); children.len()];
if let LayoutRule::Length { length } = row.parent_rule {
constraints.max_height = length;
}
let (flexible_indices, inflexible_indices): (Vec<_>, Vec<_>) = children
.iter()
.enumerate()
.filter_map(|(itx, node)| {
if let Some(layout_node) = arena.get(*node).map(|n| n.get()) {
match layout_node {
LayoutNode::Row(RowLayout { parent_rule, .. })
| LayoutNode::Col(ColLayout { parent_rule, .. }) => {
match parent_rule {
LayoutRule::Expand { ratio } => {
Some((itx, true, *ratio))
}
LayoutRule::Child => Some((itx, false, 0)),
LayoutRule::Length { .. } => Some((itx, false, 0)),
}
}
LayoutNode::Widget(_) => {
if let Some(widget) = lookup_map.get(node) {
match widget.width() {
LayoutRule::Expand { ratio } => {
Some((itx, true, ratio))
}
LayoutRule::Child => Some((itx, false, 0)),
LayoutRule::Length { .. } => Some((itx, false, 0)),
}
} else {
None
}
}
}
} else {
None
}
})
.partition(|(_itx, is_flex, _ratio)| *is_flex);
// First handle non-flexible children.
for (index, _, _) in inflexible_indices {
// The unchecked get is safe, since the index is obtained by iterating through the children
// vector in the first place.
let child = unsafe { children.get_unchecked(index) };
let desired_size = layout(*child, arena, lookup_map, constraints);
constraints.shrink_width(desired_size.width);
// This won't panic, since the two vectors are the same length.
row_bounds[index] = desired_size;
}
// Handle flexible children now.
let denominator: u32 = flexible_indices.iter().map(|(_, _, ratio)| ratio).sum();
let original_constraints = constraints;
let mut split_constraints = flexible_indices
.iter()
.map(|(_, _, numerator)| {
let constraint =
original_constraints.ratio_width(*numerator, denominator);
constraints.shrink_width(constraint.max_width);
constraint
})
.collect::<Vec<_>>();
(0..constraints.max_width)
.zip(&mut split_constraints)
.for_each(|(_, split_constraint)| {
split_constraint.max_width += 1;
});
for ((index, _, _), constraint) in
flexible_indices.into_iter().zip(split_constraints)
{
// The unchecked get is safe, since the index is obtained by iterating through the children
// vector in the first place.
let child = unsafe { children.get_unchecked(index) };
let desired_size = layout(*child, arena, lookup_map, constraint);
// This won't panic, since the two vectors are the same length.
row_bounds[index] = desired_size;
}
// Now let's turn each Size into a relative Rect!
let mut current_x = 0;
row_bounds.iter().zip(children).for_each(|(size, child)| {
let bound = Rect::new(current_x, 0, size.width, size.height);
current_x += size.width;
if let Some(node) = arena.get_mut(child) {
node.get_mut().set_bound(bound);
}
});
Size {
height: row_bounds.iter().map(|size| size.height).max().unwrap_or(0),
width: row_bounds.into_iter().map(|size| size.width).sum(),
}
}
LayoutNode::Col(col) => {
let children = node.children(arena).collect::<Vec<_>>();
let mut col_bounds = vec![Size::default(); children.len()];
if let LayoutRule::Length { length } = col.parent_rule {
constraints.max_width = length;
}
let (flexible_indices, inflexible_indices): (Vec<_>, Vec<_>) = children
.iter()
.enumerate()
.filter_map(|(itx, node)| {
if let Some(layout_node) = arena.get(*node).map(|n| n.get()) {
match layout_node {
LayoutNode::Row(RowLayout { parent_rule, .. })
| LayoutNode::Col(ColLayout { parent_rule, .. }) => {
match parent_rule {
LayoutRule::Expand { ratio } => {
Some((itx, true, *ratio))
}
LayoutRule::Child => Some((itx, false, 0)),
LayoutRule::Length { .. } => Some((itx, false, 0)),
}
}
LayoutNode::Widget(_) => {
if let Some(widget) = lookup_map.get(node) {
match widget.height() {
LayoutRule::Expand { ratio } => {
Some((itx, true, ratio))
}
LayoutRule::Child => Some((itx, false, 0)),
LayoutRule::Length { length: _ } => {
Some((itx, false, 0))
}
}
} else {
None
}
}
}
} else {
None
}
})
.partition(|(_itx, is_flex, _ratio)| *is_flex);
for (index, _, _) in inflexible_indices {
// The unchecked get is safe, since the index is obtained by iterating through the children
// vector in the first place.
let child = unsafe { children.get_unchecked(index) };
let desired_size = layout(*child, arena, lookup_map, constraints);
constraints.shrink_height(desired_size.height);
// This won't panic, since the two vectors are the same length.
col_bounds[index] = desired_size;
}
let denominator: u32 = flexible_indices.iter().map(|(_, _, ratio)| ratio).sum();
let original_constraints = constraints;
let mut split_constraints = flexible_indices
.iter()
.map(|(_, _, numerator)| {
let new_constraint =
original_constraints.ratio_height(*numerator, denominator);
constraints.shrink_height(new_constraint.max_height);
new_constraint
})
.collect::<Vec<_>>();
(0..constraints.max_height)
.zip(&mut split_constraints)
.for_each(|(_, split_constraint)| {
split_constraint.max_height += 1;
});
for ((index, _, _), constraint) in
flexible_indices.into_iter().zip(split_constraints)
{
// The unchecked get is safe, since the index is obtained by iterating through the children
// vector in the first place.
let child = unsafe { children.get_unchecked(index) };
let desired_size = layout(*child, arena, lookup_map, constraint);
// This won't panic, since the two vectors are the same length.
col_bounds[index] = desired_size;
}
// Now let's turn each Size into a relative Rect!
let mut current_y = 0;
col_bounds.iter().zip(children).for_each(|(size, child)| {
let bound = Rect::new(0, current_y, size.width, size.height);
current_y += size.height;
if let Some(node) = arena.get_mut(child) {
node.get_mut().set_bound(bound);
}
});
Size {
width: col_bounds.iter().map(|size| size.width).max().unwrap_or(0),
height: col_bounds.into_iter().map(|size| size.height).sum(),
}
}
LayoutNode::Widget(_) => {
if let Some(widget) = lookup_map.get(&node) {
let width = match widget.width() {
LayoutRule::Expand { ratio: _ } => constraints.max_width,
LayoutRule::Length { length } => min(length, constraints.max_width),
LayoutRule::Child => constraints.max_width,
};
let height = match widget.height() {
LayoutRule::Expand { ratio: _ } => constraints.max_height,
LayoutRule::Length { length } => min(length, constraints.max_height),
LayoutRule::Child => constraints.max_height,
};
Size { width, height }
} else {
Size::default()
}
}
}
} else {
Size::default()
}
}
// And this is all you need to call, the layout function will do it all~
layout(
root,
arena,
lookup_map,
LayoutConstraints::new(area.width, area.height),
);
}

View File

@ -156,7 +156,7 @@ pub enum SelectableType {
/// The "main" widgets that are used by bottom to display information!
#[allow(clippy::large_enum_variant)]
#[enum_dispatch(Component, Widget)]
pub enum BottomWidget {
pub enum OldBottomWidget {
MemGraph,
TempTable,
DiskTable,
@ -172,7 +172,7 @@ pub enum BottomWidget {
Empty,
}
impl Debug for BottomWidget {
impl Debug for OldBottomWidget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MemGraph(_) => write!(f, "MemGraph"),

View File

@ -20,7 +20,7 @@ use crate::{
custom_legend_chart::{Axis, Dataset},
TimeChart,
},
AppConfigFields, Component,
AppConfig, Component,
},
canvas::Painter,
constants::{
@ -139,7 +139,7 @@ impl TimeGraph {
}
/// Creates a new [`TimeGraph`] given an [`AppConfigFields`].
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
pub fn from_config(app_config_fields: &AppConfig) -> Self {
Self::new(
app_config_fields.default_time_value,
if app_config_fields.hide_time {

View File

@ -8,7 +8,7 @@ use tui::{
};
use crate::{
app::{widgets::tui_stuff::PipeGauge, AppConfigFields, Component, DataCollection, Widget},
app::{widgets::tui_stuff::PipeGauge, AppConfig, Component, DataCollection, Widget},
canvas::Painter,
constants::SIDE_BORDERS,
options::layout_options::LayoutRule,
@ -26,7 +26,7 @@ pub struct BasicCpu {
impl BasicCpu {
/// Creates a new [`BasicCpu`] given a [`AppConfigFields`].
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
pub fn from_config(app_config_fields: &AppConfig) -> Self {
Self {
bounds: Default::default(),
display_data: Default::default(),

View File

@ -7,7 +7,7 @@ use tui::{
};
use crate::{
app::{AppConfigFields, AxisScaling, Component, DataCollection, Widget},
app::{AppConfig, AxisScaling, Component, DataCollection, Widget},
canvas::Painter,
constants::SIDE_BORDERS,
data_conversion::convert_network_data_points,
@ -31,7 +31,7 @@ pub struct BasicNet {
impl BasicNet {
/// Creates a new [`BasicNet`] given a [`AppConfigFields`].
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
pub fn from_config(app_config_fields: &AppConfig) -> Self {
Self {
bounds: Default::default(),
width: Default::default(),

View File

@ -13,7 +13,7 @@ use crate::{
text_table::SimpleColumn,
time_graph::TimeGraphData,
widgets::tui_stuff::BlockBuilder,
AppConfigFields, Component, DataCollection, TextTable, TimeGraph, Widget,
AppConfig, Component, DataCollection, TextTable, TimeGraph, Widget,
},
canvas::Painter,
data_conversion::{convert_cpu_data_points, ConvertedCpuData},
@ -51,7 +51,7 @@ pub struct CpuGraph {
impl CpuGraph {
/// Creates a new [`CpuGraph`] from a config.
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
pub fn from_config(app_config_fields: &AppConfig) -> Self {
let graph = TimeGraph::from_config(app_config_fields);
let legend = TextTable::new(vec![
SimpleColumn::new_flex("CPU".into(), 0.5),

View File

@ -4,7 +4,7 @@ use tui::{backend::Backend, layout::Rect, widgets::Borders, Frame};
use crate::{
app::{
data_farmer::DataCollection, event::ComponentEventResult,
sort_text_table::SimpleSortableColumn, text_table::TextTableData, AppConfigFields,
sort_text_table::SimpleSortableColumn, text_table::TextTableData, AppConfig,
Component, TextTable, Widget,
},
canvas::Painter,
@ -27,7 +27,7 @@ pub struct DiskTable {
impl DiskTable {
/// Creates a [`DiskTable`] from a config.
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
pub fn from_config(app_config_fields: &AppConfig) -> Self {
let table = TextTable::new(vec![
SimpleSortableColumn::new_flex("Disk".into(), None, false, 0.2),
SimpleSortableColumn::new_flex("Mount".into(), None, false, 0.2),

View File

@ -10,7 +10,7 @@ use tui::{
use crate::{
app::{
data_farmer::DataCollection, event::ComponentEventResult, text_table::SimpleColumn,
time_graph::TimeGraphData, widgets::tui_stuff::BlockBuilder, AppConfigFields, AxisScaling,
time_graph::TimeGraphData, widgets::tui_stuff::BlockBuilder, AppConfig, AxisScaling,
Component, TextTable, TimeGraph, Widget,
},
canvas::Painter,
@ -387,7 +387,7 @@ pub struct NetGraph {
impl NetGraph {
/// Creates a new [`NetGraph`] given a [`AppConfigFields`].
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
pub fn from_config(app_config_fields: &AppConfig) -> Self {
let graph = TimeGraph::from_config(app_config_fields);
Self {
@ -590,7 +590,7 @@ pub struct OldNetGraph {
impl OldNetGraph {
/// Creates a new [`OldNetGraph`] from a [`AppConfigFields`].
pub fn from_config(config: &AppConfigFields) -> Self {
pub fn from_config(config: &AppConfig) -> Self {
Self {
net_graph: NetGraph::from_config(config).hide_legend(),
table: TextTable::new(vec![

View File

@ -22,7 +22,7 @@ use crate::{
query::*,
text_table::{DesiredColumnWidth, TextTableRow},
widgets::tui_stuff::BlockBuilder,
AppConfigFields, DataCollection, ProcessData,
AppConfig, DataCollection, ProcessData,
},
canvas::Painter,
data_conversion::{get_string_with_bytes, get_string_with_bytes_per_second},
@ -278,7 +278,7 @@ pub struct ProcessManager {
impl ProcessManager {
/// Creates a new [`ProcessManager`].
pub fn new(process_defaults: &ProcessDefaults, config: &AppConfigFields) -> Self {
pub fn new(process_defaults: &ProcessDefaults, config: &AppConfig) -> Self {
let process_table_columns = vec![
ProcessSortColumn::new(ProcessSortType::Pid),
ProcessSortColumn::new(ProcessSortType::Name),

View File

@ -5,7 +5,7 @@ use crate::{
app::{
data_farmer::DataCollection, data_harvester::temperature::TemperatureType,
event::ComponentEventResult, sort_text_table::SimpleSortableColumn,
text_table::TextTableData, AppConfigFields, Component, TextTable, Widget,
text_table::TextTableData, AppConfig, Component, TextTable, Widget,
},
canvas::Painter,
data_conversion::convert_temp_row,
@ -26,7 +26,7 @@ pub struct TempTable {
impl TempTable {
/// Creates a [`TempTable`] from a config.
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
pub fn from_config(app_config_fields: &AppConfig) -> Self {
let table = TextTable::new(vec![
SimpleSortableColumn::new_flex("Sensor".into(), None, false, 0.8),
SimpleSortableColumn::new_hard("Temp".into(), None, false, Some(5)),

View File

@ -77,7 +77,7 @@ fn main() -> Result<()> {
collection_thread_ctrl_receiver,
thread_termination_lock.clone(),
thread_termination_cvar.clone(),
&app.app_config_fields,
&app.app_config,
app.filters.clone(),
app.used_widgets.clone(),
);

View File

@ -1,29 +1,8 @@
use std::str::FromStr;
use indextree::{Arena, NodeId};
use rustc_hash::FxHashMap;
use tui::{
backend::Backend,
layout::{Constraint, Layout, Rect},
text::Span,
widgets::Paragraph,
Frame, Terminal,
};
use canvas_colours::*;
use crate::{
app::{
self,
layout_manager::{generate_layout, ColLayout, LayoutNode, RowLayout},
widgets::{Component, Widget},
BottomWidget,
},
constants::*,
options::Config,
utils::error,
utils::error::BottomError,
};
use crate::{constants::*, options::Config, utils::error, utils::error::BottomError};
mod canvas_colours;

View File

@ -8,19 +8,24 @@ use crate::{app::AxisScaling, units::data_units::DataUnit};
use std::borrow::Cow;
/// Stores converted data, and caches results.
#[derive(Default)]
pub struct ConvertedData {
pub struct ConvertedData<'a> {
data: &'a DataCollection,
temp_table: Option<Vec<Vec<Cow<'static, str>>>>,
}
impl ConvertedData {
pub fn temp_table(
&mut self, data: &DataCollection, temp_type: TemperatureType,
) -> Vec<Vec<Cow<'static, str>>> {
impl<'a> ConvertedData<'a> {
pub fn new(data: &'a DataCollection) -> Self {
Self {
data,
temp_table: None,
}
}
pub fn temp_table(&mut self, temp_type: TemperatureType) -> Vec<Vec<Cow<'static, str>>> {
match &self.temp_table {
Some(temp_table) => temp_table.clone(),
None => {
let temp_table = if data.temp_harvest.is_empty() {
let temp_table = if self.data.temp_harvest.is_empty() {
vec![vec!["No Sensors Found".into(), "".into()]]
} else {
let unit = match temp_type {
@ -29,7 +34,8 @@ impl ConvertedData {
data_harvester::temperature::TemperatureType::Fahrenheit => "°F",
};
data.temp_harvest
self.data
.temp_harvest
.iter()
.map(|temp_harvest| {
let val = temp_harvest.temperature.ceil().to_string();

View File

@ -60,7 +60,7 @@ pub type Pid = libc::pid_t;
#[derive(Debug)]
pub enum ThreadControlEvent {
Reset,
UpdateConfig(Box<app::AppConfigFields>),
UpdateConfig(Box<app::AppConfig>),
UpdateUsedWidgets(Box<UsedWidgets>),
UpdateUpdateTime(u64),
}
@ -247,7 +247,7 @@ pub fn create_collection_thread(
sender: std::sync::mpsc::Sender<RuntimeEvent<AppMessages>>,
control_receiver: std::sync::mpsc::Receiver<ThreadControlEvent>,
termination_ctrl_lock: Arc<Mutex<bool>>, termination_ctrl_cvar: Arc<Condvar>,
app_config_fields: &app::AppConfigFields, filters: app::DataFilters,
app_config_fields: &app::AppConfig, filters: app::DataFilters,
used_widget_set: UsedWidgets,
) -> std::thread::JoinHandle<()> {
let temp_type = app_config_fields.temperature_type.clone();

View File

@ -20,7 +20,7 @@ use anyhow::{Context, Result};
pub struct Config {
pub flags: Option<ConfigFlags>,
pub colors: Option<ConfigColours>,
pub row: Option<Vec<Row>>,
pub row: Option<Vec<LayoutRow>>,
pub disk_filter: Option<IgnoreList>,
pub mount_filter: Option<IgnoreList>,
pub temp_filter: Option<IgnoreList>,
@ -191,7 +191,7 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
let network_scale_type = get_network_scale_type(matches, config);
let network_use_binary_prefix = get_network_use_binary_prefix(matches, config);
let app_config_fields = AppConfigFields {
let app_config_fields = AppConfig {
update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config)
.context("Update 'rate' in your config file.")?,
temperature_type: get_temperature(matches, config)
@ -218,28 +218,31 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
network_use_binary_prefix,
};
let layout_tree_output = if let Some(row) = &config.row {
create_layout_tree(row, process_defaults, &app_config_fields)?
let rows: Vec<LayoutRow>;
let row_ref = if let Some(row) = &config.row {
row
} else if get_use_basic_mode(matches, config) {
if get_use_battery(matches, config) {
let rows = toml::from_str::<Config>(DEFAULT_BASIC_BATTERY_LAYOUT)?
rows = toml::from_str::<Config>(DEFAULT_BASIC_BATTERY_LAYOUT)?
.row
.unwrap();
create_layout_tree(&rows, process_defaults, &app_config_fields)?
&rows
} else {
let rows = toml::from_str::<Config>(DEFAULT_BASIC_LAYOUT)?.row.unwrap();
create_layout_tree(&rows, process_defaults, &app_config_fields)?
rows = toml::from_str::<Config>(DEFAULT_BASIC_LAYOUT)?.row.unwrap();
&rows
}
} else if get_use_battery(matches, config) {
let rows = toml::from_str::<Config>(DEFAULT_BATTERY_LAYOUT)?
rows = toml::from_str::<Config>(DEFAULT_BATTERY_LAYOUT)?
.row
.unwrap();
create_layout_tree(&rows, process_defaults, &app_config_fields)?
&rows
} else {
let rows = toml::from_str::<Config>(DEFAULT_LAYOUT)?.row.unwrap();
create_layout_tree(&rows, process_defaults, &app_config_fields)?
rows = toml::from_str::<Config>(DEFAULT_LAYOUT)?.row.unwrap();
&rows
};
let layout_tree_output = create_layout_tree(row_ref, process_defaults, &app_config_fields)?;
let disk_filter =
get_ignore_list(&config.disk_filter).context("Update 'disk_filter' in your config file")?;
let mount_filter = get_ignore_list(&config.mount_filter)

View File

@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
/// of children.
#[derive(Clone, Deserialize, Debug, Serialize)]
#[serde(rename = "row")]
pub struct Row {
pub child: Option<Vec<RowChildren>>,
pub struct LayoutRow {
pub child: Option<Vec<LayoutRowChild>>,
pub ratio: Option<u32>,
}
@ -16,14 +16,14 @@ pub struct Row {
/// recursion between Row and Col.
#[derive(Clone, Deserialize, Debug, Serialize)]
#[serde(untagged)]
pub enum RowChildren {
pub enum LayoutRowChild {
Widget(FinalWidget),
/// The first one in the list is the "default" selected widget.
Carousel {
carousel_children: Vec<String>,
default: Option<bool>,
},
Col {
LayoutCol {
ratio: Option<u32>,
child: Vec<FinalWidget>,
},

View File

@ -0,0 +1,30 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`BatteryTable`] is a widget displaying battery stats.
pub struct BatteryTable {}
impl super::AppWidget for BatteryTable {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for BatteryTable {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("Battery Table")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -0,0 +1,31 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`CpuGraph`] is a widget displaying CPU data in a graph-like form, and with controls for showing only
/// specific plots.
pub struct CpuGraph {}
impl super::AppWidget for CpuGraph {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for CpuGraph {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("CPU Graph")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -0,0 +1,30 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`CpuSimple`] is a widget displaying simple CPU stats.
pub struct CpuSimple {}
impl super::AppWidget for CpuSimple {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for CpuSimple {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("CPU Simple")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -0,0 +1,30 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`DiskTable`] is a table displaying disk data.
pub struct DiskTable {}
impl super::AppWidget for DiskTable {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for DiskTable {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("Disk Table")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -0,0 +1,30 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`MemGraph`] is a widget displaying RAM/SWAP data in a graph-like form.
pub struct MemGraph {}
impl super::AppWidget for MemGraph {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for MemGraph {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("Mem Graph")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -0,0 +1,30 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`MemSimple`] is a widget displaying simple CPU stats.
pub struct MemSimple {}
impl super::AppWidget for MemSimple {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for MemSimple {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("Mem Simple")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -1,3 +1,6 @@
use anyhow::{anyhow, Result};
use enum_dispatch::enum_dispatch;
pub mod simple_table;
pub use simple_table::*;
@ -30,3 +33,12 @@ pub use mem_simple::*;
pub mod net_simple;
pub use net_simple::*;
use crate::{app::AppConfig, canvas::Painter, data_conversion::ConvertedData, tuine::ViewContext};
pub trait AppWidget {
fn build(
ctx: &mut ViewContext<'_>, painter: &Painter, config: &AppConfig,
data: &mut ConvertedData<'_>,
) -> Self;
}

View File

@ -0,0 +1,30 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`NetGraph`] is a widget displaying RAM/SWAP data in a graph-like form.
pub struct NetGraph {}
impl super::AppWidget for NetGraph {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for NetGraph {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("Net Graph")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -0,0 +1,30 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`NetSimple`] is a widget displaying simple CPU stats.
pub struct NetSimple {}
impl super::AppWidget for NetSimple {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for NetSimple {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("Net Simple")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -0,0 +1,30 @@
use tui::{text::Text, widgets::Paragraph, Frame};
use crate::tuine::{DrawContext, StateContext, TmpComponent};
/// A [`ProcessTable`] is a widget displaying process data, and with controls for searching/filtering entries.
pub struct ProcessTable {}
impl super::AppWidget for ProcessTable {
fn build(
ctx: &mut crate::tuine::ViewContext<'_>, painter: &crate::canvas::Painter,
config: &crate::app::AppConfig, data: &mut crate::data_conversion::ConvertedData<'_>,
) -> Self {
Self {}
}
}
impl<Message> TmpComponent<Message> for ProcessTable {
fn draw<Backend>(
&mut self, _state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
frame: &mut Frame<'_, Backend>,
) where
Backend: tui::backend::Backend,
{
let rect = draw_ctx.global_rect();
frame.render_widget(
Paragraph::new(Text::raw("Process Table")).block(tui::widgets::Block::default()),
rect,
);
}
}

View File

@ -1,12 +1,14 @@
use crate::{
app::AppConfig,
canvas::Painter,
data_conversion::ConvertedData,
tuine::{
Bounds, DataRow, DrawContext, LayoutNode, SimpleTable, Size, StateContext, Status,
TmpComponent, ViewContext,
},
};
use super::simple_table;
use super::{simple_table, AppWidget};
/// A [`TempTable`] is a table displaying temperature data.
///
@ -15,9 +17,12 @@ pub struct TempTable<Message> {
inner: SimpleTable<Message>,
}
impl<Message> TempTable<Message> {
pub fn build<R: Into<DataRow>>(
ctx: &mut ViewContext<'_>, painter: &Painter, data: Vec<R>,
impl<Message> TempTable<Message> {}
impl<Message> AppWidget for TempTable<Message> {
fn build(
ctx: &mut ViewContext<'_>, painter: &Painter, config: &AppConfig,
data: &mut ConvertedData<'_>,
) -> Self {
let style = simple_table::StyleSheet {
text: painter.colours.text_style,
@ -25,9 +30,10 @@ impl<Message> TempTable<Message> {
table_header: painter.colours.table_header_style,
border: painter.colours.border_style,
};
let rows = data.temp_table(config.temperature_type);
Self {
inner: SimpleTable::build(ctx, style, vec!["Sensor", "Temp"], data),
inner: SimpleTable::build(ctx, style, vec!["Sensor", "Temp"], rows),
}
}
}

View File

@ -1,10 +1,7 @@
use enum_dispatch::enum_dispatch;
use tui::Frame;
use super::{
Block, Bounds, Carousel, Container, DrawContext, Empty, Event, Flex, LayoutNode, Shortcut,
SimpleTable, Size, StateContext, Status, TempTable, TextTable, TmpComponent,
};
use super::*;
/// An [`Element`] is an instantiated [`Component`].
#[enum_dispatch(TmpComponent<Message>)]
@ -19,6 +16,15 @@ where
Shortcut(Shortcut<Message, C>),
TextTable(TextTable<Message>),
Empty,
BatteryTable(BatteryTable),
CpuGraph(CpuGraph),
CpuSimple(CpuSimple),
DiskTable(DiskTable),
MemGraph(MemGraph),
MemSimple(MemSimple),
NetGraph(NetGraph),
NetSimple(NetSimple),
ProcessTable(ProcessTable),
SimpleTable(SimpleTable<Message>),
TempTable(TempTable<Message>),
}