mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 15:44:17 +02:00
Layout
This commit is contained in:
parent
3dbe788920
commit
65e36901c0
43
src/app.rs
43
src/app.rs
@ -29,7 +29,6 @@ use frozen_state::FrozenState;
|
|||||||
use crate::{
|
use crate::{
|
||||||
canvas::Painter,
|
canvas::Painter,
|
||||||
constants,
|
constants,
|
||||||
data_conversion::ConvertedData,
|
|
||||||
tuine::{Application, Element, Status, ViewContext},
|
tuine::{Application, Element, Status, ViewContext},
|
||||||
units::data_units::DataUnit,
|
units::data_units::DataUnit,
|
||||||
Pid,
|
Pid,
|
||||||
@ -135,6 +134,7 @@ pub enum AppMessages {
|
|||||||
to_kill: Vec<Pid>,
|
to_kill: Vec<Pid>,
|
||||||
signal: Option<i32>,
|
signal: Option<i32>,
|
||||||
},
|
},
|
||||||
|
Expand,
|
||||||
ToggleFreeze,
|
ToggleFreeze,
|
||||||
Reset,
|
Reset,
|
||||||
Clean,
|
Clean,
|
||||||
@ -150,23 +150,16 @@ pub struct AppState {
|
|||||||
frozen_state: FrozenState,
|
frozen_state: FrozenState,
|
||||||
current_screen: CurrentScreen,
|
current_screen: CurrentScreen,
|
||||||
pub painter: Painter,
|
pub painter: Painter,
|
||||||
|
layout: WidgetLayoutNode,
|
||||||
terminator: Arc<AtomicBool>,
|
terminator: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
/// Creates a new [`AppState`].
|
/// Creates a new [`AppState`].
|
||||||
pub fn new(
|
pub fn new(
|
||||||
app_config: AppConfig, filters: DataFilters, layout_tree_output: LayoutCreationOutput,
|
app_config: AppConfig, filters: DataFilters, layout: WidgetLayoutNode,
|
||||||
painter: Painter,
|
used_widgets: UsedWidgets, painter: Painter,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let LayoutCreationOutput {
|
|
||||||
layout_tree: _,
|
|
||||||
root: _,
|
|
||||||
widget_lookup_map,
|
|
||||||
selected: _,
|
|
||||||
used_widgets,
|
|
||||||
} = layout_tree_output;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
app_config,
|
app_config,
|
||||||
filters,
|
filters,
|
||||||
@ -177,7 +170,7 @@ impl AppState {
|
|||||||
data_collection: Default::default(),
|
data_collection: Default::default(),
|
||||||
frozen_state: Default::default(),
|
frozen_state: Default::default(),
|
||||||
current_screen: Default::default(),
|
current_screen: Default::default(),
|
||||||
|
layout,
|
||||||
terminator: Self::register_terminator()?,
|
terminator: Self::register_terminator()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -221,6 +214,10 @@ impl Application for AppState {
|
|||||||
// FIXME: Handle process termination
|
// FIXME: Handle process termination
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
AppMessages::Expand => {
|
||||||
|
// FIXME: Expand current widget
|
||||||
|
true
|
||||||
|
}
|
||||||
AppMessages::ToggleFreeze => {
|
AppMessages::ToggleFreeze => {
|
||||||
self.frozen_state.toggle(&self.data_collection);
|
self.frozen_state.toggle(&self.data_collection);
|
||||||
true
|
true
|
||||||
@ -246,7 +243,27 @@ impl Application for AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element<Self::Message> {
|
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) {
|
fn destructor(&mut self) {
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::SelectableType,
|
||||||
BasicCpu, BasicMem, BasicNet, BatteryTable, Carousel, DiskTable, Empty, MemGraph, NetGraph,
|
|
||||||
OldNetGraph, ProcessManager, SelectableType, TempTable,
|
|
||||||
},
|
|
||||||
error::{BottomError, Result},
|
error::{BottomError, Result},
|
||||||
options::{
|
options::layout_options::{FinalWidget, LayoutRow, LayoutRowChild, LayoutRule},
|
||||||
layout_options::{LayoutRow, LayoutRowChild, LayoutRule},
|
|
||||||
ProcessDefaults,
|
|
||||||
},
|
|
||||||
tuine::{Element, Flex},
|
|
||||||
};
|
};
|
||||||
use indextree::{Arena, NodeId};
|
use indextree::{Arena, NodeId};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
@ -17,9 +10,7 @@ use tui::layout::Rect;
|
|||||||
|
|
||||||
use crate::app::widgets::Widget;
|
use crate::app::widgets::Widget;
|
||||||
|
|
||||||
use super::{
|
use super::{event::SelectionAction, OldBottomWidget, UsedWidgets};
|
||||||
event::SelectionAction, AppConfig, AppState, CpuGraph, OldBottomWidget, TimeGraph, UsedWidgets,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum BottomWidgetType {
|
pub enum BottomWidgetType {
|
||||||
@ -41,12 +32,6 @@ pub enum BottomWidgetType {
|
|||||||
Carousel,
|
Carousel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BottomWidgetType {
|
|
||||||
fn default() -> Self {
|
|
||||||
BottomWidgetType::Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for BottomWidgetType {
|
impl FromStr for BottomWidgetType {
|
||||||
type Err = BottomError;
|
type Err = BottomError;
|
||||||
|
|
||||||
@ -122,16 +107,6 @@ pub struct RowLayout {
|
|||||||
pub bound: Rect,
|
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.
|
/// Represents a column in the layout tree.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct ColLayout {
|
pub struct ColLayout {
|
||||||
@ -140,16 +115,6 @@ pub struct ColLayout {
|
|||||||
pub bound: Rect,
|
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.
|
/// Represents a widget in the layout tree.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||||
pub struct WidgetLayout {
|
pub struct WidgetLayout {
|
||||||
@ -178,327 +143,125 @@ pub enum MovementDirection {
|
|||||||
Down,
|
Down,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_widget_layout<Message>(
|
/// An intermediate representation of the widget layout.
|
||||||
layout_rows: &[LayoutRow], app: &AppState,
|
pub enum WidgetLayoutNode {
|
||||||
) -> anyhow::Result<Element<Message>> {
|
Row {
|
||||||
let mut root = Flex::column();
|
children: Vec<WidgetLayoutNode>,
|
||||||
|
parent_rule: LayoutRule,
|
||||||
for layout_row in layout_rows {
|
},
|
||||||
let mut row = Flex::row();
|
Col {
|
||||||
if let Some(children) = &layout_row.child {
|
children: Vec<WidgetLayoutNode>,
|
||||||
for child in children {
|
parent_rule: LayoutRule,
|
||||||
match child {
|
},
|
||||||
LayoutRowChild::Widget(widget) => {}
|
Carousel {
|
||||||
LayoutRowChild::Carousel {
|
children: Vec<BottomWidgetType>,
|
||||||
carousel_children,
|
selected: bool,
|
||||||
default,
|
},
|
||||||
} => {}
|
Widget {
|
||||||
LayoutRowChild::LayoutCol {
|
widget_type: BottomWidgetType,
|
||||||
ratio,
|
selected: bool,
|
||||||
child: children,
|
width_rule: LayoutRule,
|
||||||
} => for child in children {},
|
height_rule: LayoutRule,
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root = root.with_child(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(root.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper struct to simplify the output of [`create_layout_tree`].
|
/// Parses the layout in the config into an intermediate representation.
|
||||||
pub struct LayoutCreationOutput {
|
pub fn parse_widget_layout(
|
||||||
pub layout_tree: Arena<LayoutNode>,
|
layout_rows: &[LayoutRow],
|
||||||
pub root: NodeId,
|
) -> anyhow::Result<(WidgetLayoutNode, UsedWidgets)> {
|
||||||
pub widget_lookup_map: FxHashMap<NodeId, OldBottomWidget>,
|
let mut root_children = Vec::with_capacity(layout_rows.len());
|
||||||
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
|
|
||||||
let mut used_widgets = UsedWidgets::default();
|
let mut used_widgets = UsedWidgets::default();
|
||||||
|
|
||||||
for row in rows {
|
for layout_row in layout_rows {
|
||||||
let row_id = arena.new_node(LayoutNode::Row(RowLayout::new(
|
if let Some(children) = &layout_row.child {
|
||||||
row.ratio
|
let mut row_children = Vec::with_capacity(children.len());
|
||||||
.map(|ratio| LayoutRule::Expand { ratio })
|
|
||||||
.unwrap_or(LayoutRule::Child),
|
|
||||||
)));
|
|
||||||
root_id.append(row_id, &mut arena);
|
|
||||||
|
|
||||||
if let Some(children) = &row.child {
|
|
||||||
for child in children {
|
for child in children {
|
||||||
match child {
|
match child {
|
||||||
LayoutRowChild::Widget(widget) => {
|
LayoutRowChild::Widget(widget) => {
|
||||||
let widget_id = arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
|
let FinalWidget {
|
||||||
row_id.append(widget_id, &mut arena);
|
rule,
|
||||||
|
widget_type,
|
||||||
|
default,
|
||||||
|
} = widget;
|
||||||
|
|
||||||
if let Some(true) = widget.default {
|
let widget_type = widget_type.parse::<BottomWidgetType>()?;
|
||||||
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>()?;
|
|
||||||
used_widgets.add(&widget_type);
|
used_widgets.add(&widget_type);
|
||||||
|
|
||||||
add_widget_to_map(
|
row_children.push(WidgetLayoutNode::Widget {
|
||||||
&mut widget_lookup_map,
|
|
||||||
widget_type,
|
widget_type,
|
||||||
widget_id,
|
selected: default.unwrap_or(false),
|
||||||
&process_defaults,
|
width_rule: rule.unwrap_or_default(),
|
||||||
app_config_fields,
|
height_rule: LayoutRule::default(),
|
||||||
widget.rule.unwrap_or_default(),
|
});
|
||||||
LayoutRule::default(),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
LayoutRowChild::Carousel {
|
LayoutRowChild::Carousel {
|
||||||
carousel_children,
|
carousel_children,
|
||||||
default,
|
default,
|
||||||
} => {
|
} => {
|
||||||
if !carousel_children.is_empty() {
|
let mut car_children = Vec::with_capacity(carousel_children.len());
|
||||||
let mut child_ids = Vec::with_capacity(carousel_children.len());
|
for widget_type in carousel_children {
|
||||||
let carousel_widget_id =
|
let widget_type = widget_type.parse::<BottomWidgetType>()?;
|
||||||
arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
|
used_widgets.add(&widget_type);
|
||||||
row_id.append(carousel_widget_id, &mut arena);
|
|
||||||
|
|
||||||
if let Some(true) = default {
|
car_children.push(widget_type);
|
||||||
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(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
row_children.push(WidgetLayoutNode::Carousel {
|
||||||
|
children: car_children,
|
||||||
|
selected: default.unwrap_or(false),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
LayoutRowChild::LayoutCol {
|
LayoutRowChild::LayoutCol {
|
||||||
ratio,
|
ratio,
|
||||||
child: col_child,
|
child: children,
|
||||||
} => {
|
} => {
|
||||||
let col_id = arena.new_node(LayoutNode::Col(ColLayout::new(
|
let mut col_children = Vec::with_capacity(children.len());
|
||||||
ratio
|
for widget in children {
|
||||||
.map(|ratio| LayoutRule::Expand { ratio })
|
let FinalWidget {
|
||||||
.unwrap_or(LayoutRule::Child),
|
rule,
|
||||||
)));
|
widget_type,
|
||||||
row_id.append(col_id, &mut arena);
|
default,
|
||||||
|
} = widget;
|
||||||
for widget in col_child {
|
let widget_type = widget_type.parse::<BottomWidgetType>()?;
|
||||||
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>()?;
|
|
||||||
used_widgets.add(&widget_type);
|
used_widgets.add(&widget_type);
|
||||||
|
|
||||||
add_widget_to_map(
|
col_children.push(WidgetLayoutNode::Widget {
|
||||||
&mut widget_lookup_map,
|
|
||||||
widget_type,
|
widget_type,
|
||||||
widget_id,
|
selected: default.unwrap_or(false),
|
||||||
&process_defaults,
|
width_rule: LayoutRule::default(),
|
||||||
app_config_fields,
|
height_rule: rule.unwrap_or_default(),
|
||||||
LayoutRule::default(),
|
});
|
||||||
widget.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;
|
let root = WidgetLayoutNode::Col {
|
||||||
if let Some(first_selected) = first_selected {
|
children: root_children,
|
||||||
selected = first_selected;
|
parent_rule: LayoutRule::Expand { ratio: 1 },
|
||||||
} 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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
correct_layout_last_selections(&mut arena, selected);
|
Ok((root, used_widgets))
|
||||||
|
|
||||||
Ok(LayoutCreationOutput {
|
|
||||||
layout_tree: arena,
|
|
||||||
root: root_id,
|
|
||||||
widget_lookup_map,
|
|
||||||
selected,
|
|
||||||
used_widgets,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We may have situations where we also have to make sure the correct layout indices are selected.
|
/// We may have situations where we also have to make sure the correct layout indices are selected.
|
||||||
|
@ -119,7 +119,6 @@ pub struct SimpleSortableColumn {
|
|||||||
original_name: Cow<'static, str>,
|
original_name: Cow<'static, str>,
|
||||||
pub shortcut: Option<(KeyEvent, String)>,
|
pub shortcut: Option<(KeyEvent, String)>,
|
||||||
pub default_descending: bool,
|
pub default_descending: bool,
|
||||||
x_bounds: Option<(u16, u16)>,
|
|
||||||
|
|
||||||
pub internal: SimpleColumn,
|
pub internal: SimpleColumn,
|
||||||
|
|
||||||
@ -138,7 +137,6 @@ impl SimpleSortableColumn {
|
|||||||
original_name,
|
original_name,
|
||||||
shortcut,
|
shortcut,
|
||||||
default_descending,
|
default_descending,
|
||||||
x_bounds: None,
|
|
||||||
internal: SimpleColumn::new(full_name, desired_width),
|
internal: SimpleColumn::new(full_name, desired_width),
|
||||||
sorting_status: SortStatus::NotSorting,
|
sorting_status: SortStatus::NotSorting,
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ pub const HELP_CONTENTS_TEXT: [&str; 8] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
pub const GENERAL_HELP_TITLE: &str = "General";
|
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"],
|
["q, Ctrl-c", "Quit"],
|
||||||
[
|
[
|
||||||
"Esc",
|
"Esc",
|
||||||
@ -251,6 +251,8 @@ pub const GENERAL_HELP_TEXT: [[&str; 2]; 21] = [
|
|||||||
["+", "Zoom in on chart (decrease time range)"],
|
["+", "Zoom in on chart (decrease time range)"],
|
||||||
["-", "Zoom out on chart (increase time range)"],
|
["-", "Zoom out on chart (increase time range)"],
|
||||||
["=", "Reset zoom"],
|
["=", "Reset zoom"],
|
||||||
|
["Page Up", "Move up one page in a table"],
|
||||||
|
["Page Down", "Move down one page in a table"],
|
||||||
[
|
[
|
||||||
"Mouse scroll",
|
"Mouse scroll",
|
||||||
"Scroll through the tables or zoom in/out of charts by scrolling up/down",
|
"Scroll through the tables or zoom in/out of charts by scrolling up/down",
|
||||||
|
@ -241,7 +241,7 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
|
|||||||
&rows
|
&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 =
|
let disk_filter =
|
||||||
get_ignore_list(&config.disk_filter).context("Update 'disk_filter' in your config file")?;
|
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)?)?;
|
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(
|
fn get_update_rate_in_milliseconds(
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
use anyhow::{anyhow, Result};
|
|
||||||
use enum_dispatch::enum_dispatch;
|
|
||||||
|
|
||||||
pub mod simple_table;
|
pub mod simple_table;
|
||||||
pub use simple_table::*;
|
pub use simple_table::*;
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|
||||||
use tui::style::Style;
|
use tui::style::Style;
|
||||||
|
|
||||||
use crate::tuine::{
|
use crate::tuine::{
|
||||||
self, block,
|
self, block,
|
||||||
text_table::{self, DataRow, SortType, TextTableProps},
|
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`].
|
/// A set of styles for a [`SimpleTable`].
|
||||||
|
@ -3,8 +3,8 @@ use crate::{
|
|||||||
canvas::Painter,
|
canvas::Painter,
|
||||||
data_conversion::ConvertedData,
|
data_conversion::ConvertedData,
|
||||||
tuine::{
|
tuine::{
|
||||||
Bounds, DataRow, DrawContext, LayoutNode, SimpleTable, Size, StateContext, Status,
|
Bounds, DrawContext, LayoutNode, SimpleTable, Size, StateContext, Status, TmpComponent,
|
||||||
TmpComponent, ViewContext,
|
ViewContext,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,3 @@ pub use custom_legend_chart::TimeChart;
|
|||||||
|
|
||||||
pub mod pipe_gauge;
|
pub mod pipe_gauge;
|
||||||
pub use pipe_gauge::PipeGauge;
|
pub use pipe_gauge::PipeGauge;
|
||||||
|
|
||||||
pub mod block_builder;
|
|
||||||
pub use block_builder::BlockBuilder;
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user