mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-04-08 17:05:59 +02:00
refactor: add general keybinds, fix buggy movement
Adds back some of the general program keybinds, and fixes both a bug causing widget movement via keybinds to be incorrect, and not correcting the last selected widget in the layout tree rows/cols after clicking/setting the default widget!
This commit is contained in:
parent
587987a2a5
commit
e7b9c72912
@ -41,8 +41,8 @@ Note that key bindings are generally case-sensitive.
|
|||||||
| ------------------------------------------------------------ | ------------------------------------------------------------ |
|
| ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||||
| ++q++ , ++ctrl+c++ | Quit |
|
| ++q++ , ++ctrl+c++ | Quit |
|
||||||
| ++esc++ | Close dialog windows, search, widgets, or exit expanded mode |
|
| ++esc++ | Close dialog windows, search, widgets, or exit expanded mode |
|
||||||
| ++ctrl+r++ | Reset display and any collected data |
|
| ++ctrl+r++ | Resets any collected data |
|
||||||
| ++f++ | Freeze/unfreeze updating with new data |
|
| ++f++ | Toggles freezing, which stops new data from being shown |
|
||||||
| ++question++ | Open help menu |
|
| ++question++ | Open help menu |
|
||||||
| ++e++ | Toggle expanding the currently selected widget |
|
| ++e++ | Toggle expanding the currently selected widget |
|
||||||
| ++ctrl+up++ <br/> ++shift+up++ <br/> ++K++ <br/> ++W++ | Select the widget above |
|
| ++ctrl+up++ <br/> ++shift+up++ <br/> ++K++ <br/> ++W++ | Select the widget above |
|
||||||
|
189
src/app.rs
189
src/app.rs
@ -104,19 +104,29 @@ pub struct AppConfigFields {
|
|||||||
pub no_write: bool,
|
pub no_write: bool,
|
||||||
pub show_table_scroll_position: bool,
|
pub show_table_scroll_position: bool,
|
||||||
pub is_advanced_kill: bool,
|
pub is_advanced_kill: bool,
|
||||||
// TODO: Remove these, move network details state-side.
|
|
||||||
pub network_unit_type: DataUnit,
|
pub network_unit_type: DataUnit,
|
||||||
pub network_scale_type: AxisScaling,
|
pub network_scale_type: AxisScaling,
|
||||||
pub network_use_binary_prefix: bool,
|
pub network_use_binary_prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The [`FrozenState`] indicates whether the application state should be frozen; if it is, save a snapshot of
|
||||||
|
/// the data collected at that instant.
|
||||||
|
pub enum FrozenState {
|
||||||
|
NotFrozen,
|
||||||
|
Frozen(DataCollection),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FrozenState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::NotFrozen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub dd_err: Option<String>,
|
pub dd_err: Option<String>,
|
||||||
|
|
||||||
to_delete_process_list: Option<(String, Vec<Pid>)>,
|
to_delete_process_list: Option<(String, Vec<Pid>)>,
|
||||||
|
|
||||||
pub is_frozen: bool,
|
|
||||||
|
|
||||||
pub canvas_data: canvas::DisplayableData,
|
pub canvas_data: canvas::DisplayableData,
|
||||||
|
|
||||||
pub data_collection: DataCollection,
|
pub data_collection: DataCollection,
|
||||||
@ -161,6 +171,7 @@ pub struct AppState {
|
|||||||
pub widget_lookup_map: FxHashMap<NodeId, TmpBottomWidget>,
|
pub widget_lookup_map: FxHashMap<NodeId, TmpBottomWidget>,
|
||||||
pub layout_tree: Arena<LayoutNode>,
|
pub layout_tree: Arena<LayoutNode>,
|
||||||
pub layout_tree_root: NodeId,
|
pub layout_tree_root: NodeId,
|
||||||
|
frozen_state: FrozenState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@ -189,7 +200,6 @@ impl AppState {
|
|||||||
// Use defaults.
|
// Use defaults.
|
||||||
dd_err: Default::default(),
|
dd_err: Default::default(),
|
||||||
to_delete_process_list: Default::default(),
|
to_delete_process_list: Default::default(),
|
||||||
is_frozen: Default::default(),
|
|
||||||
canvas_data: Default::default(),
|
canvas_data: Default::default(),
|
||||||
data_collection: Default::default(),
|
data_collection: Default::default(),
|
||||||
is_expanded: Default::default(),
|
is_expanded: Default::default(),
|
||||||
@ -211,37 +221,30 @@ impl AppState {
|
|||||||
basic_mode_use_percent: Default::default(),
|
basic_mode_use_percent: Default::default(),
|
||||||
is_force_redraw: Default::default(),
|
is_force_redraw: Default::default(),
|
||||||
is_determining_widget_boundary: Default::default(),
|
is_determining_widget_boundary: Default::default(),
|
||||||
|
frozen_state: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_frozen(&self) -> bool {
|
||||||
|
matches!(self.frozen_state, FrozenState::Frozen(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_freeze(&mut self) {
|
||||||
|
if matches!(self.frozen_state, FrozenState::Frozen(_)) {
|
||||||
|
self.frozen_state = FrozenState::NotFrozen;
|
||||||
|
} else {
|
||||||
|
self.frozen_state = FrozenState::Frozen(self.data_collection.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
// Reset multi
|
// Call reset on all widgets.
|
||||||
self.reset_multi_tap_keys();
|
self.widget_lookup_map
|
||||||
|
|
||||||
// Reset dialog state
|
|
||||||
self.help_dialog_state.is_showing_help = false;
|
|
||||||
self.delete_dialog_state.is_showing_dd = false;
|
|
||||||
|
|
||||||
// Close all searches and reset it
|
|
||||||
self.proc_state
|
|
||||||
.widget_states
|
|
||||||
.values_mut()
|
.values_mut()
|
||||||
.for_each(|state| {
|
.for_each(|widget| widget.reset());
|
||||||
state.process_search_state.search_state.reset();
|
|
||||||
});
|
|
||||||
self.proc_state.force_update_all = true;
|
|
||||||
|
|
||||||
// Clear current delete list
|
|
||||||
self.to_delete_process_list = None;
|
|
||||||
self.dd_err = None;
|
|
||||||
|
|
||||||
// Unfreeze.
|
// Unfreeze.
|
||||||
self.is_frozen = false;
|
self.frozen_state = FrozenState::NotFrozen;
|
||||||
|
|
||||||
// Reset zoom
|
|
||||||
self.reset_cpu_zoom();
|
|
||||||
self.reset_mem_zoom();
|
|
||||||
self.reset_net_zoom();
|
|
||||||
|
|
||||||
// Reset data
|
// Reset data
|
||||||
self.data_collection.reset();
|
self.data_collection.reset();
|
||||||
@ -259,10 +262,77 @@ impl AppState {
|
|||||||
self.dd_err = None;
|
self.dd_err = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles a global event involving a char.
|
||||||
|
fn handle_global_char(&mut self, c: char) -> EventResult {
|
||||||
|
if c.is_ascii_control() {
|
||||||
|
EventResult::NoRedraw
|
||||||
|
} else {
|
||||||
|
// Check for case-sensitive bindings first.
|
||||||
|
match c {
|
||||||
|
'H' | 'A' => self.move_to_widget(MovementDirection::Left),
|
||||||
|
'L' | 'D' => self.move_to_widget(MovementDirection::Right),
|
||||||
|
'K' | 'W' => self.move_to_widget(MovementDirection::Up),
|
||||||
|
'J' | 'S' => self.move_to_widget(MovementDirection::Down),
|
||||||
|
_ => {
|
||||||
|
let c = c.to_ascii_lowercase();
|
||||||
|
match c {
|
||||||
|
'q' => EventResult::Quit,
|
||||||
|
'e' => {
|
||||||
|
if self.app_config_fields.use_basic_mode {
|
||||||
|
EventResult::NoRedraw
|
||||||
|
} else {
|
||||||
|
self.is_expanded = !self.is_expanded;
|
||||||
|
EventResult::Redraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'?' => {
|
||||||
|
self.help_dialog_state.is_showing_help = true;
|
||||||
|
EventResult::Redraw
|
||||||
|
}
|
||||||
|
'f' => {
|
||||||
|
self.toggle_freeze();
|
||||||
|
if !self.is_frozen() {
|
||||||
|
let data_collection = &self.data_collection;
|
||||||
|
self.widget_lookup_map
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|(_id, widget)| widget.update_data(data_collection));
|
||||||
|
}
|
||||||
|
EventResult::Redraw
|
||||||
|
}
|
||||||
|
_ => EventResult::NoRedraw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves to a widget.
|
||||||
|
fn move_to_widget(&mut self, direction: MovementDirection) -> EventResult {
|
||||||
|
let layout_tree = &mut self.layout_tree;
|
||||||
|
let previous_selected = self.selected_widget;
|
||||||
|
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
|
||||||
|
match move_widget_selection(layout_tree, widget, self.selected_widget, direction) {
|
||||||
|
MoveWidgetResult::ForceRedraw(new_widget_id) => {
|
||||||
|
self.selected_widget = new_widget_id;
|
||||||
|
EventResult::Redraw
|
||||||
|
}
|
||||||
|
MoveWidgetResult::NodeId(new_widget_id) => {
|
||||||
|
self.selected_widget = new_widget_id;
|
||||||
|
|
||||||
|
if previous_selected != self.selected_widget {
|
||||||
|
EventResult::Redraw
|
||||||
|
} else {
|
||||||
|
EventResult::NoRedraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EventResult::NoRedraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles a global [`KeyEvent`], and returns an [`EventResult`].
|
/// Handles a global [`KeyEvent`], and returns an [`EventResult`].
|
||||||
fn handle_global_shortcut(&mut self, event: KeyEvent) -> EventResult {
|
fn handle_global_shortcut(&mut self, event: KeyEvent) -> EventResult {
|
||||||
// TODO: Write this.
|
|
||||||
|
|
||||||
if event.modifiers.is_empty() {
|
if event.modifiers.is_empty() {
|
||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
@ -280,24 +350,33 @@ impl AppState {
|
|||||||
EventResult::NoRedraw
|
EventResult::NoRedraw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char('q') => EventResult::Quit,
|
KeyCode::Char(c) => self.handle_global_char(c),
|
||||||
KeyCode::Char('e') => {
|
|
||||||
if self.app_config_fields.use_basic_mode {
|
|
||||||
EventResult::NoRedraw
|
|
||||||
} else {
|
|
||||||
self.is_expanded = !self.is_expanded;
|
|
||||||
EventResult::Redraw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char('?') => {
|
|
||||||
self.help_dialog_state.is_showing_help = true;
|
|
||||||
EventResult::Redraw
|
|
||||||
}
|
|
||||||
_ => EventResult::NoRedraw,
|
_ => EventResult::NoRedraw,
|
||||||
}
|
}
|
||||||
} else if let KeyModifiers::CONTROL = event.modifiers {
|
} else if let KeyModifiers::CONTROL = event.modifiers {
|
||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Char('c') => EventResult::Quit,
|
KeyCode::Char('c') | KeyCode::Char('C') => EventResult::Quit,
|
||||||
|
KeyCode::Char('r') | KeyCode::Char('R') => {
|
||||||
|
self.reset();
|
||||||
|
let data_collection = &self.data_collection;
|
||||||
|
self.widget_lookup_map
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|(_id, widget)| widget.update_data(data_collection));
|
||||||
|
EventResult::Redraw
|
||||||
|
}
|
||||||
|
KeyCode::Left => self.move_to_widget(MovementDirection::Left),
|
||||||
|
KeyCode::Right => self.move_to_widget(MovementDirection::Right),
|
||||||
|
KeyCode::Up => self.move_to_widget(MovementDirection::Up),
|
||||||
|
KeyCode::Down => self.move_to_widget(MovementDirection::Down),
|
||||||
|
_ => EventResult::NoRedraw,
|
||||||
|
}
|
||||||
|
} else if let KeyModifiers::SHIFT = event.modifiers {
|
||||||
|
match event.code {
|
||||||
|
KeyCode::Left => self.move_to_widget(MovementDirection::Left),
|
||||||
|
KeyCode::Right => self.move_to_widget(MovementDirection::Right),
|
||||||
|
KeyCode::Up => self.move_to_widget(MovementDirection::Up),
|
||||||
|
KeyCode::Down => self.move_to_widget(MovementDirection::Down),
|
||||||
|
KeyCode::Char(c) => self.handle_global_char(c),
|
||||||
_ => EventResult::NoRedraw,
|
_ => EventResult::NoRedraw,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -317,8 +396,15 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
ReturnSignal::Update => {
|
ReturnSignal::Update => {
|
||||||
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
|
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
|
||||||
|
match &self.frozen_state {
|
||||||
|
FrozenState::NotFrozen => {
|
||||||
widget.update_data(&self.data_collection);
|
widget.update_data(&self.data_collection);
|
||||||
}
|
}
|
||||||
|
FrozenState::Frozen(frozen_data) => {
|
||||||
|
widget.update_data(frozen_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
EventResult::Redraw
|
EventResult::Redraw
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -379,7 +465,11 @@ impl AppState {
|
|||||||
if was_id_already_selected {
|
if was_id_already_selected {
|
||||||
return self.convert_widget_event_result(result);
|
return self.convert_widget_event_result(result);
|
||||||
} else {
|
} else {
|
||||||
// If the weren't equal, *force* a redraw.
|
// If the weren't equal, *force* a redraw, and correct the layout tree.
|
||||||
|
correct_layout_last_selections(
|
||||||
|
&mut self.layout_tree,
|
||||||
|
self.selected_widget,
|
||||||
|
);
|
||||||
let _ = self.convert_widget_event_result(result);
|
let _ = self.convert_widget_event_result(result);
|
||||||
return EventResult::Redraw;
|
return EventResult::Redraw;
|
||||||
}
|
}
|
||||||
@ -402,7 +492,7 @@ impl AppState {
|
|||||||
BottomEvent::Update(new_data) => {
|
BottomEvent::Update(new_data) => {
|
||||||
self.data_collection.eat_data(new_data);
|
self.data_collection.eat_data(new_data);
|
||||||
|
|
||||||
if !self.is_frozen {
|
if !self.is_frozen() {
|
||||||
let data_collection = &self.data_collection;
|
let data_collection = &self.data_collection;
|
||||||
self.widget_lookup_map
|
self.widget_lookup_map
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@ -1599,12 +1689,7 @@ impl AppState {
|
|||||||
'G' => self.skip_to_last(),
|
'G' => self.skip_to_last(),
|
||||||
'k' => self.on_up_key(),
|
'k' => self.on_up_key(),
|
||||||
'j' => self.on_down_key(),
|
'j' => self.on_down_key(),
|
||||||
'f' => {
|
'f' => {}
|
||||||
self.is_frozen = !self.is_frozen;
|
|
||||||
if self.is_frozen {
|
|
||||||
self.data_collection.set_frozen_time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'C' => {
|
'C' => {
|
||||||
// self.open_config(),
|
// self.open_config(),
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ use regex::Regex;
|
|||||||
pub type TimeOffset = f64;
|
pub type TimeOffset = f64;
|
||||||
pub type Value = f64;
|
pub type Value = f64;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct TimedData {
|
pub struct TimedData {
|
||||||
pub rx_data: Value,
|
pub rx_data: Value,
|
||||||
pub tx_data: Value,
|
pub tx_data: Value,
|
||||||
@ -41,14 +41,14 @@ pub struct TimedData {
|
|||||||
/// collected, and what is needed to convert into a displayable form.
|
/// collected, and what is needed to convert into a displayable form.
|
||||||
///
|
///
|
||||||
/// If the app is *frozen* - that is, we do not want to *display* any changing
|
/// If the app is *frozen* - that is, we do not want to *display* any changing
|
||||||
/// data, keep updating this, don't convert to canvas displayable data!
|
/// data, keep updating this. As of 2021-09-08, we just clone the current collection
|
||||||
|
/// when it freezes to have a snapshot floating around.
|
||||||
///
|
///
|
||||||
/// Note that with this method, the *app* thread is responsible for cleaning -
|
/// Note that with this method, the *app* thread is responsible for cleaning -
|
||||||
/// not the data collector.
|
/// not the data collector.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DataCollection {
|
pub struct DataCollection {
|
||||||
pub current_instant: Instant,
|
pub current_instant: Instant,
|
||||||
pub frozen_instant: Option<Instant>,
|
|
||||||
pub timed_data_vec: Vec<(Instant, TimedData)>,
|
pub timed_data_vec: Vec<(Instant, TimedData)>,
|
||||||
pub network_harvest: network::NetworkHarvest,
|
pub network_harvest: network::NetworkHarvest,
|
||||||
pub memory_harvest: memory::MemHarvest,
|
pub memory_harvest: memory::MemHarvest,
|
||||||
@ -70,7 +70,6 @@ impl Default for DataCollection {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
DataCollection {
|
DataCollection {
|
||||||
current_instant: Instant::now(),
|
current_instant: Instant::now(),
|
||||||
frozen_instant: None,
|
|
||||||
timed_data_vec: Vec::default(),
|
timed_data_vec: Vec::default(),
|
||||||
network_harvest: network::NetworkHarvest::default(),
|
network_harvest: network::NetworkHarvest::default(),
|
||||||
memory_harvest: memory::MemHarvest::default(),
|
memory_harvest: memory::MemHarvest::default(),
|
||||||
@ -92,21 +91,19 @@ impl Default for DataCollection {
|
|||||||
|
|
||||||
impl DataCollection {
|
impl DataCollection {
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.timed_data_vec = Vec::default();
|
self.timed_data_vec = Default::default();
|
||||||
self.network_harvest = network::NetworkHarvest::default();
|
self.network_harvest = Default::default();
|
||||||
self.memory_harvest = memory::MemHarvest::default();
|
self.memory_harvest = Default::default();
|
||||||
self.swap_harvest = memory::MemHarvest::default();
|
self.swap_harvest = Default::default();
|
||||||
self.cpu_harvest = cpu::CpuHarvest::default();
|
self.cpu_harvest = Default::default();
|
||||||
self.process_harvest = Vec::default();
|
self.process_harvest = Default::default();
|
||||||
self.disk_harvest = Vec::default();
|
self.process_name_pid_map = Default::default();
|
||||||
self.io_harvest = disks::IoHarvest::default();
|
self.process_cmd_pid_map = Default::default();
|
||||||
self.io_labels_and_prev = Vec::default();
|
self.disk_harvest = Default::default();
|
||||||
self.temp_harvest = Vec::default();
|
self.io_harvest = Default::default();
|
||||||
self.battery_harvest = Vec::default();
|
self.io_labels_and_prev = Default::default();
|
||||||
}
|
self.temp_harvest = Default::default();
|
||||||
|
self.battery_harvest = Default::default();
|
||||||
pub fn set_frozen_time(&mut self) {
|
|
||||||
self.frozen_instant = Some(self.current_instant);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clean_data(&mut self, max_time_millis: u64) {
|
pub fn clean_data(&mut self, max_time_millis: u64) {
|
||||||
|
@ -45,7 +45,7 @@ pub enum WidgetEventResult {
|
|||||||
|
|
||||||
/// How a widget should handle a widget selection request.
|
/// How a widget should handle a widget selection request.
|
||||||
pub enum SelectionAction {
|
pub enum SelectionAction {
|
||||||
/// This event occurs if the widget internally handled the selection action.
|
/// This event occurs if the widget internally handled the selection action. A redraw is required.
|
||||||
Handled,
|
Handled,
|
||||||
/// This event occurs if the widget did not handle the selection action; the caller must handle it.
|
/// This event occurs if the widget did not handle the selection action; the caller must handle it.
|
||||||
NotHandled,
|
NotHandled,
|
||||||
|
@ -997,9 +997,9 @@ Supported widget names:
|
|||||||
// --- New stuff ---
|
// --- New stuff ---
|
||||||
|
|
||||||
/// Represents a row in the layout tree.
|
/// Represents a row in the layout tree.
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct RowLayout {
|
pub struct RowLayout {
|
||||||
last_selected_index: usize,
|
last_selected: Option<NodeId>,
|
||||||
pub parent_rule: LayoutRule,
|
pub parent_rule: LayoutRule,
|
||||||
pub bound: Rect,
|
pub bound: Rect,
|
||||||
}
|
}
|
||||||
@ -1007,7 +1007,7 @@ pub struct RowLayout {
|
|||||||
impl RowLayout {
|
impl RowLayout {
|
||||||
fn new(parent_rule: LayoutRule) -> Self {
|
fn new(parent_rule: LayoutRule) -> Self {
|
||||||
Self {
|
Self {
|
||||||
last_selected_index: 0,
|
last_selected: None,
|
||||||
parent_rule,
|
parent_rule,
|
||||||
bound: Rect::default(),
|
bound: Rect::default(),
|
||||||
}
|
}
|
||||||
@ -1015,9 +1015,9 @@ impl RowLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a column in the layout tree.
|
/// Represents a column in the layout tree.
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct ColLayout {
|
pub struct ColLayout {
|
||||||
last_selected_index: usize,
|
last_selected: Option<NodeId>,
|
||||||
pub parent_rule: LayoutRule,
|
pub parent_rule: LayoutRule,
|
||||||
pub bound: Rect,
|
pub bound: Rect,
|
||||||
}
|
}
|
||||||
@ -1025,7 +1025,7 @@ pub struct ColLayout {
|
|||||||
impl ColLayout {
|
impl ColLayout {
|
||||||
fn new(parent_rule: LayoutRule) -> Self {
|
fn new(parent_rule: LayoutRule) -> Self {
|
||||||
Self {
|
Self {
|
||||||
last_selected_index: 0,
|
last_selected: None,
|
||||||
parent_rule,
|
parent_rule,
|
||||||
bound: Rect::default(),
|
bound: Rect::default(),
|
||||||
}
|
}
|
||||||
@ -1033,7 +1033,7 @@ impl ColLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a widget in the layout tree.
|
/// Represents a widget in the layout tree.
|
||||||
#[derive(PartialEq, Eq, Clone, Default)]
|
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||||
pub struct WidgetLayout {
|
pub struct WidgetLayout {
|
||||||
pub bound: Rect,
|
pub bound: Rect,
|
||||||
}
|
}
|
||||||
@ -1042,7 +1042,7 @@ pub struct WidgetLayout {
|
|||||||
/// - [`LayoutNode::Row`] (a non-leaf that distributes its children horizontally)
|
/// - [`LayoutNode::Row`] (a non-leaf that distributes its children horizontally)
|
||||||
/// - [`LayoutNode::Col`] (a non-leaf node that distributes its children vertically)
|
/// - [`LayoutNode::Col`] (a non-leaf node that distributes its children vertically)
|
||||||
/// - [`LayoutNode::Widget`] (a leaf node that contains the ID of the widget it is associated with)
|
/// - [`LayoutNode::Widget`] (a leaf node that contains the ID of the widget it is associated with)
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum LayoutNode {
|
pub enum LayoutNode {
|
||||||
/// A non-leaf that distributes its children horizontally
|
/// A non-leaf that distributes its children horizontally
|
||||||
Row(RowLayout),
|
Row(RowLayout),
|
||||||
@ -1382,6 +1382,8 @@ pub fn create_layout_tree(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
correct_layout_last_selections(&mut arena, selected);
|
||||||
|
|
||||||
Ok(LayoutCreationOutput {
|
Ok(LayoutCreationOutput {
|
||||||
layout_tree: arena,
|
layout_tree: arena,
|
||||||
root: root_id,
|
root: root_id,
|
||||||
@ -1391,6 +1393,35 @@ pub fn create_layout_tree(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We may have situations where we also have to make sure the correct layout indices are selected.
|
||||||
|
/// For example, when we select a widget by clicking, we want to update the layout so that it's as if a user
|
||||||
|
/// manually moved to it via keybinds.
|
||||||
|
///
|
||||||
|
/// We can do this by just going through the ancestors, starting from the widget itself.
|
||||||
|
pub fn correct_layout_last_selections(arena: &mut Arena<LayoutNode>, selected: NodeId) {
|
||||||
|
let mut selected_ancestors = selected.ancestors(&arena).collect::<Vec<_>>();
|
||||||
|
let prev_node = selected_ancestors.pop();
|
||||||
|
if let Some(mut prev_node) = prev_node {
|
||||||
|
for node in selected_ancestors {
|
||||||
|
if let Some(layout_node) = arena.get_mut(node).map(|n| n.get_mut()) {
|
||||||
|
match layout_node {
|
||||||
|
LayoutNode::Row(RowLayout { last_selected, .. })
|
||||||
|
| LayoutNode::Col(ColLayout { last_selected, .. }) => {
|
||||||
|
*last_selected = Some(prev_node);
|
||||||
|
}
|
||||||
|
LayoutNode::Widget(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_node = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum MoveWidgetResult {
|
||||||
|
ForceRedraw(NodeId),
|
||||||
|
NodeId(NodeId),
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to find and return the selected [`BottomWidgetId`] after moving in a direction.
|
/// Attempts to find and return the selected [`BottomWidgetId`] after moving in a direction.
|
||||||
///
|
///
|
||||||
/// Note this function assumes a properly built tree - if not, bad things may happen! We generally assume that:
|
/// Note this function assumes a properly built tree - if not, bad things may happen! We generally assume that:
|
||||||
@ -1399,7 +1430,7 @@ pub fn create_layout_tree(
|
|||||||
pub fn move_widget_selection(
|
pub fn move_widget_selection(
|
||||||
layout_tree: &mut Arena<LayoutNode>, current_widget: &mut TmpBottomWidget,
|
layout_tree: &mut Arena<LayoutNode>, current_widget: &mut TmpBottomWidget,
|
||||||
current_widget_id: NodeId, direction: MovementDirection,
|
current_widget_id: NodeId, direction: MovementDirection,
|
||||||
) -> NodeId {
|
) -> MoveWidgetResult {
|
||||||
// We first give our currently-selected widget a chance to react to the movement - it may handle it internally!
|
// We first give our currently-selected widget a chance to react to the movement - it may handle it internally!
|
||||||
let handled = match direction {
|
let handled = match direction {
|
||||||
MovementDirection::Left => current_widget.handle_widget_selection_left(),
|
MovementDirection::Left => current_widget.handle_widget_selection_left(),
|
||||||
@ -1408,16 +1439,18 @@ pub fn move_widget_selection(
|
|||||||
MovementDirection::Down => current_widget.handle_widget_selection_down(),
|
MovementDirection::Down => current_widget.handle_widget_selection_down(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Do testing.
|
||||||
|
|
||||||
match handled {
|
match handled {
|
||||||
SelectionAction::Handled => {
|
SelectionAction::Handled => {
|
||||||
// If it was handled by the widget, then we don't have to do anything - return the current one.
|
// If it was handled by the widget, then we don't have to do anything - return the current one.
|
||||||
current_widget_id
|
MoveWidgetResult::ForceRedraw(current_widget_id)
|
||||||
}
|
}
|
||||||
SelectionAction::NotHandled => {
|
SelectionAction::NotHandled => {
|
||||||
/// Keeps traversing up the `layout_tree` until it hits a parent where `current_id` is a child and parent
|
/// Keeps traversing up the `layout_tree` until it hits a parent where `current_id` is a child and parent
|
||||||
/// is a [`LayoutNode::Row`], returning its parent's [`NodeId`] and the child's [`NodeId`] (in that order).
|
/// is a [`LayoutNode::Row`], returning its parent's [`NodeId`] and the child's [`NodeId`] (in that order).
|
||||||
/// If this crawl fails (i.e. hits a root, it is an invalid tree for some reason), it returns [`None`].
|
/// If this crawl fails (i.e. hits a root, it is an invalid tree for some reason), it returns [`None`].
|
||||||
fn find_first_row(
|
fn find_parent_row(
|
||||||
layout_tree: &Arena<LayoutNode>, current_id: NodeId,
|
layout_tree: &Arena<LayoutNode>, current_id: NodeId,
|
||||||
) -> Option<(NodeId, NodeId)> {
|
) -> Option<(NodeId, NodeId)> {
|
||||||
layout_tree
|
layout_tree
|
||||||
@ -1430,7 +1463,7 @@ pub fn move_widget_selection(
|
|||||||
})
|
})
|
||||||
.and_then(|(parent_id, parent_node)| match parent_node.get() {
|
.and_then(|(parent_id, parent_node)| match parent_node.get() {
|
||||||
LayoutNode::Row(_) => Some((parent_id, current_id)),
|
LayoutNode::Row(_) => Some((parent_id, current_id)),
|
||||||
LayoutNode::Col(_) => find_first_row(layout_tree, parent_id),
|
LayoutNode::Col(_) => find_parent_row(layout_tree, parent_id),
|
||||||
LayoutNode::Widget(_) => None,
|
LayoutNode::Widget(_) => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1438,7 +1471,7 @@ pub fn move_widget_selection(
|
|||||||
/// Keeps traversing up the `layout_tree` until it hits a parent where `current_id` is a child and parent
|
/// Keeps traversing up the `layout_tree` until it hits a parent where `current_id` is a child and parent
|
||||||
/// is a [`LayoutNode::Col`], returning its parent's [`NodeId`] and the child's [`NodeId`] (in that order).
|
/// is a [`LayoutNode::Col`], returning its parent's [`NodeId`] and the child's [`NodeId`] (in that order).
|
||||||
/// If this crawl fails (i.e. hits a root, it is an invalid tree for some reason), it returns [`None`].
|
/// If this crawl fails (i.e. hits a root, it is an invalid tree for some reason), it returns [`None`].
|
||||||
fn find_first_col(
|
fn find_parent_col(
|
||||||
layout_tree: &Arena<LayoutNode>, current_id: NodeId,
|
layout_tree: &Arena<LayoutNode>, current_id: NodeId,
|
||||||
) -> Option<(NodeId, NodeId)> {
|
) -> Option<(NodeId, NodeId)> {
|
||||||
layout_tree
|
layout_tree
|
||||||
@ -1450,7 +1483,7 @@ pub fn move_widget_selection(
|
|||||||
.map(|parent_node| (parent_id, parent_node))
|
.map(|parent_node| (parent_id, parent_node))
|
||||||
})
|
})
|
||||||
.and_then(|(parent_id, parent_node)| match parent_node.get() {
|
.and_then(|(parent_id, parent_node)| match parent_node.get() {
|
||||||
LayoutNode::Row(_) => find_first_col(layout_tree, parent_id),
|
LayoutNode::Row(_) => find_parent_col(layout_tree, parent_id),
|
||||||
LayoutNode::Col(_) => Some((parent_id, current_id)),
|
LayoutNode::Col(_) => Some((parent_id, current_id)),
|
||||||
LayoutNode::Widget(_) => None,
|
LayoutNode::Widget(_) => None,
|
||||||
})
|
})
|
||||||
@ -1461,21 +1494,19 @@ pub fn move_widget_selection(
|
|||||||
if let Some(current_node) = layout_tree.get(current_id) {
|
if let Some(current_node) = layout_tree.get(current_id) {
|
||||||
match current_node.get() {
|
match current_node.get() {
|
||||||
LayoutNode::Row(RowLayout {
|
LayoutNode::Row(RowLayout {
|
||||||
last_selected_index,
|
last_selected,
|
||||||
parent_rule: _,
|
parent_rule: _,
|
||||||
bound: _,
|
bound: _,
|
||||||
})
|
})
|
||||||
| LayoutNode::Col(ColLayout {
|
| LayoutNode::Col(ColLayout {
|
||||||
last_selected_index,
|
last_selected,
|
||||||
parent_rule: _,
|
parent_rule: _,
|
||||||
bound: _,
|
bound: _,
|
||||||
}) => {
|
}) => {
|
||||||
if let Some(next_child) =
|
if let Some(next_child) = *last_selected {
|
||||||
current_id.children(layout_tree).nth(*last_selected_index)
|
|
||||||
{
|
|
||||||
descend_to_leaf(layout_tree, next_child)
|
descend_to_leaf(layout_tree, next_child)
|
||||||
} else {
|
} else {
|
||||||
current_id
|
current_node.first_child().unwrap_or(current_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LayoutNode::Widget(_) => {
|
LayoutNode::Widget(_) => {
|
||||||
@ -1493,7 +1524,7 @@ pub fn move_widget_selection(
|
|||||||
// on the tree layout to help us decide where to go.
|
// on the tree layout to help us decide where to go.
|
||||||
// Movement logic is inspired by i3. When we enter a new column/row, we go to the *last* selected
|
// Movement logic is inspired by i3. When we enter a new column/row, we go to the *last* selected
|
||||||
// element; if we can't, go to the nearest one.
|
// element; if we can't, go to the nearest one.
|
||||||
match direction {
|
let proposed_id = match direction {
|
||||||
MovementDirection::Left => {
|
MovementDirection::Left => {
|
||||||
// When we move "left":
|
// When we move "left":
|
||||||
// 1. Look for the parent of the current widget.
|
// 1. Look for the parent of the current widget.
|
||||||
@ -1513,7 +1544,8 @@ pub fn move_widget_selection(
|
|||||||
fn find_left(
|
fn find_left(
|
||||||
layout_tree: &mut Arena<LayoutNode>, current_id: NodeId,
|
layout_tree: &mut Arena<LayoutNode>, current_id: NodeId,
|
||||||
) -> NodeId {
|
) -> NodeId {
|
||||||
if let Some((parent_id, child_id)) = find_first_row(layout_tree, current_id)
|
if let Some((parent_id, child_id)) =
|
||||||
|
find_parent_row(layout_tree, current_id)
|
||||||
{
|
{
|
||||||
if let Some(prev_sibling) =
|
if let Some(prev_sibling) =
|
||||||
child_id.preceding_siblings(layout_tree).nth(1)
|
child_id.preceding_siblings(layout_tree).nth(1)
|
||||||
@ -1521,16 +1553,17 @@ pub fn move_widget_selection(
|
|||||||
// Subtract one from the currently selected index...
|
// Subtract one from the currently selected index...
|
||||||
if let Some(parent) = layout_tree.get_mut(parent_id) {
|
if let Some(parent) = layout_tree.get_mut(parent_id) {
|
||||||
if let LayoutNode::Row(row) = parent.get_mut() {
|
if let LayoutNode::Row(row) = parent.get_mut() {
|
||||||
row.last_selected_index =
|
row.last_selected = Some(prev_sibling);
|
||||||
row.last_selected_index.saturating_sub(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now descend downwards!
|
// Now descend downwards!
|
||||||
descend_to_leaf(layout_tree, prev_sibling)
|
descend_to_leaf(layout_tree, prev_sibling)
|
||||||
} else {
|
} else if parent_id != current_id {
|
||||||
// Darn, we can't go further back! Recurse on this ID.
|
// Darn, we can't go further back! Recurse on this ID.
|
||||||
find_left(layout_tree, child_id)
|
find_left(layout_tree, parent_id)
|
||||||
|
} else {
|
||||||
|
current_id
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Failed, just return the current ID.
|
// Failed, just return the current ID.
|
||||||
@ -1546,23 +1579,26 @@ pub fn move_widget_selection(
|
|||||||
fn find_right(
|
fn find_right(
|
||||||
layout_tree: &mut Arena<LayoutNode>, current_id: NodeId,
|
layout_tree: &mut Arena<LayoutNode>, current_id: NodeId,
|
||||||
) -> NodeId {
|
) -> NodeId {
|
||||||
if let Some((parent_id, child_id)) = find_first_row(layout_tree, current_id)
|
if let Some((parent_id, child_id)) =
|
||||||
|
find_parent_row(layout_tree, current_id)
|
||||||
{
|
{
|
||||||
if let Some(prev_sibling) =
|
if let Some(following_sibling) =
|
||||||
child_id.following_siblings(layout_tree).nth(1)
|
child_id.following_siblings(layout_tree).nth(1)
|
||||||
{
|
{
|
||||||
// Add one to the currently selected index...
|
// Add one to the currently selected index...
|
||||||
if let Some(parent) = layout_tree.get_mut(parent_id) {
|
if let Some(parent) = layout_tree.get_mut(parent_id) {
|
||||||
if let LayoutNode::Row(row) = parent.get_mut() {
|
if let LayoutNode::Row(row) = parent.get_mut() {
|
||||||
row.last_selected_index += 1;
|
row.last_selected = Some(following_sibling);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now descend downwards!
|
// Now descend downwards!
|
||||||
descend_to_leaf(layout_tree, prev_sibling)
|
descend_to_leaf(layout_tree, following_sibling)
|
||||||
} else {
|
} else if parent_id != current_id {
|
||||||
// Darn, we can't go further back! Recurse on this ID.
|
// Darn, we can't go further back! Recurse on this ID.
|
||||||
find_right(layout_tree, child_id)
|
find_right(layout_tree, parent_id)
|
||||||
|
} else {
|
||||||
|
current_id
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Failed, just return the current ID.
|
// Failed, just return the current ID.
|
||||||
@ -1578,7 +1614,8 @@ pub fn move_widget_selection(
|
|||||||
fn find_above(
|
fn find_above(
|
||||||
layout_tree: &mut Arena<LayoutNode>, current_id: NodeId,
|
layout_tree: &mut Arena<LayoutNode>, current_id: NodeId,
|
||||||
) -> NodeId {
|
) -> NodeId {
|
||||||
if let Some((parent_id, child_id)) = find_first_col(layout_tree, current_id)
|
if let Some((parent_id, child_id)) =
|
||||||
|
find_parent_col(layout_tree, current_id)
|
||||||
{
|
{
|
||||||
if let Some(prev_sibling) =
|
if let Some(prev_sibling) =
|
||||||
child_id.preceding_siblings(layout_tree).nth(1)
|
child_id.preceding_siblings(layout_tree).nth(1)
|
||||||
@ -1586,16 +1623,17 @@ pub fn move_widget_selection(
|
|||||||
// Subtract one from the currently selected index...
|
// Subtract one from the currently selected index...
|
||||||
if let Some(parent) = layout_tree.get_mut(parent_id) {
|
if let Some(parent) = layout_tree.get_mut(parent_id) {
|
||||||
if let LayoutNode::Col(row) = parent.get_mut() {
|
if let LayoutNode::Col(row) = parent.get_mut() {
|
||||||
row.last_selected_index =
|
row.last_selected = Some(prev_sibling);
|
||||||
row.last_selected_index.saturating_sub(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now descend downwards!
|
// Now descend downwards!
|
||||||
descend_to_leaf(layout_tree, prev_sibling)
|
descend_to_leaf(layout_tree, prev_sibling)
|
||||||
} else {
|
} else if parent_id != current_id {
|
||||||
// Darn, we can't go further back! Recurse on this ID.
|
// Darn, we can't go further back! Recurse on this ID.
|
||||||
find_above(layout_tree, child_id)
|
find_above(layout_tree, parent_id)
|
||||||
|
} else {
|
||||||
|
current_id
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Failed, just return the current ID.
|
// Failed, just return the current ID.
|
||||||
@ -1611,23 +1649,26 @@ pub fn move_widget_selection(
|
|||||||
fn find_below(
|
fn find_below(
|
||||||
layout_tree: &mut Arena<LayoutNode>, current_id: NodeId,
|
layout_tree: &mut Arena<LayoutNode>, current_id: NodeId,
|
||||||
) -> NodeId {
|
) -> NodeId {
|
||||||
if let Some((parent_id, child_id)) = find_first_col(layout_tree, current_id)
|
if let Some((parent_id, child_id)) =
|
||||||
|
find_parent_col(layout_tree, current_id)
|
||||||
{
|
{
|
||||||
if let Some(prev_sibling) =
|
if let Some(following_sibling) =
|
||||||
child_id.following_siblings(layout_tree).nth(1)
|
child_id.following_siblings(layout_tree).nth(1)
|
||||||
{
|
{
|
||||||
// Add one to the currently selected index...
|
// Add one to the currently selected index...
|
||||||
if let Some(parent) = layout_tree.get_mut(parent_id) {
|
if let Some(parent) = layout_tree.get_mut(parent_id) {
|
||||||
if let LayoutNode::Col(row) = parent.get_mut() {
|
if let LayoutNode::Col(row) = parent.get_mut() {
|
||||||
row.last_selected_index += 1;
|
row.last_selected = Some(following_sibling);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now descend downwards!
|
// Now descend downwards!
|
||||||
descend_to_leaf(layout_tree, prev_sibling)
|
descend_to_leaf(layout_tree, following_sibling)
|
||||||
} else {
|
} else if parent_id != current_id {
|
||||||
// Darn, we can't go further back! Recurse on this ID.
|
// Darn, we can't go further back! Recurse on this ID.
|
||||||
find_below(layout_tree, child_id)
|
find_below(layout_tree, parent_id)
|
||||||
|
} else {
|
||||||
|
current_id
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Failed, just return the current ID.
|
// Failed, just return the current ID.
|
||||||
@ -1636,6 +1677,12 @@ pub fn move_widget_selection(
|
|||||||
}
|
}
|
||||||
find_below(layout_tree, current_widget_id)
|
find_below(layout_tree, current_widget_id)
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(LayoutNode::Widget(_)) = layout_tree.get(proposed_id).map(|n| n.get()) {
|
||||||
|
MoveWidgetResult::NodeId(proposed_id)
|
||||||
|
} else {
|
||||||
|
MoveWidgetResult::NodeId(current_widget_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,9 @@ pub trait Widget {
|
|||||||
fn selectable_type(&self) -> SelectableType {
|
fn selectable_type(&self) -> SelectableType {
|
||||||
SelectableType::Selectable
|
SelectableType::Selectable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets state in a [`Widget`]; used when a reset signal is given. The default implementation does nothing.
|
||||||
|
fn reset(&mut self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a widget can be selected, not selected, or redirected upon selection.
|
/// Whether a widget can be selected, not selected, or redirected upon selection.
|
||||||
|
@ -129,7 +129,7 @@ impl Widget for BasicMem {
|
|||||||
|
|
||||||
// TODO: [Data update optimization] Probably should just make another function altogether for basic mode.
|
// TODO: [Data update optimization] Probably should just make another function altogether for basic mode.
|
||||||
self.mem_data = if let (Some(data), Some((_, fraction))) = (
|
self.mem_data = if let (Some(data), Some((_, fraction))) = (
|
||||||
convert_mem_data_points(data_collection, false).last(),
|
convert_mem_data_points(data_collection).last(),
|
||||||
memory_labels,
|
memory_labels,
|
||||||
) {
|
) {
|
||||||
(
|
(
|
||||||
@ -141,7 +141,7 @@ impl Widget for BasicMem {
|
|||||||
(0.0, "0.0B/0.0B".to_string(), "0%".to_string())
|
(0.0, "0.0B/0.0B".to_string(), "0%".to_string())
|
||||||
};
|
};
|
||||||
self.swap_data = if let (Some(data), Some((_, fraction))) = (
|
self.swap_data = if let (Some(data), Some((_, fraction))) = (
|
||||||
convert_swap_data_points(data_collection, false).last(),
|
convert_swap_data_points(data_collection).last(),
|
||||||
swap_labels,
|
swap_labels,
|
||||||
) {
|
) {
|
||||||
Some((
|
Some((
|
||||||
|
@ -109,7 +109,6 @@ impl Widget for BasicNet {
|
|||||||
fn update_data(&mut self, data_collection: &DataCollection) {
|
fn update_data(&mut self, data_collection: &DataCollection) {
|
||||||
let network_data = convert_network_data_points(
|
let network_data = convert_network_data_points(
|
||||||
data_collection,
|
data_collection,
|
||||||
false, // TODO: I think the is_frozen here is also useless; see mem and cpu
|
|
||||||
true,
|
true,
|
||||||
&AxisScaling::Linear,
|
&AxisScaling::Linear,
|
||||||
&self.unit_type,
|
&self.unit_type,
|
||||||
|
@ -9,7 +9,9 @@ use tui::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
event::WidgetEventResult, text_table::SimpleColumn, time_graph::TimeGraphData,
|
event::{SelectionAction, WidgetEventResult},
|
||||||
|
text_table::SimpleColumn,
|
||||||
|
time_graph::TimeGraphData,
|
||||||
AppConfigFields, AppScrollWidgetState, CanvasTableWidthState, Component, DataCollection,
|
AppConfigFields, AppScrollWidgetState, CanvasTableWidthState, Component, DataCollection,
|
||||||
TextTable, TimeGraph, Widget,
|
TextTable, TimeGraph, Widget,
|
||||||
},
|
},
|
||||||
@ -47,7 +49,6 @@ impl CpuState {
|
|||||||
enum CpuGraphSelection {
|
enum CpuGraphSelection {
|
||||||
Graph,
|
Graph,
|
||||||
Legend,
|
Legend,
|
||||||
None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the [`CpuGraph`]'s legend is placed on the left or right.
|
/// Whether the [`CpuGraph`]'s legend is placed on the left or right.
|
||||||
@ -95,7 +96,7 @@ impl CpuGraph {
|
|||||||
legend_position,
|
legend_position,
|
||||||
showing_avg,
|
showing_avg,
|
||||||
bounds: Rect::default(),
|
bounds: Rect::default(),
|
||||||
selected: CpuGraphSelection::None,
|
selected: CpuGraphSelection::Graph,
|
||||||
display_data: Default::default(),
|
display_data: Default::default(),
|
||||||
load_avg_data: [0.0; 3],
|
load_avg_data: [0.0; 3],
|
||||||
width: LayoutRule::default(),
|
width: LayoutRule::default(),
|
||||||
@ -121,7 +122,6 @@ impl Component for CpuGraph {
|
|||||||
match self.selected {
|
match self.selected {
|
||||||
CpuGraphSelection::Graph => self.graph.handle_key_event(event),
|
CpuGraphSelection::Graph => self.graph.handle_key_event(event),
|
||||||
CpuGraphSelection::Legend => self.legend.handle_key_event(event),
|
CpuGraphSelection::Legend => self.legend.handle_key_event(event),
|
||||||
CpuGraphSelection::None => WidgetEventResult::NoRedraw,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +190,55 @@ impl Widget for CpuGraph {
|
|||||||
CpuGraphLegendPosition::Right => (split_area[0], split_area[1]),
|
CpuGraphLegendPosition::Right => (split_area[0], split_area[1]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let legend_block = self
|
||||||
|
.block()
|
||||||
|
.selected(selected && matches!(&self.selected, CpuGraphSelection::Legend))
|
||||||
|
.expanded(expanded)
|
||||||
|
.hide_title(true);
|
||||||
|
|
||||||
|
let legend_data = self
|
||||||
|
.display_data
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(cpu_index, core_data)| {
|
||||||
|
let style = Some(if cpu_index == 0 {
|
||||||
|
painter.colours.all_colour_style
|
||||||
|
} else if self.showing_avg && cpu_index == 1 {
|
||||||
|
painter.colours.avg_colour_style
|
||||||
|
} else {
|
||||||
|
let cpu_style_index = if self.showing_avg {
|
||||||
|
// No underflow should occur, as if cpu_index was
|
||||||
|
// 1 and avg is showing, it's caught by the above case!
|
||||||
|
cpu_index - 2
|
||||||
|
} else {
|
||||||
|
cpu_index - 1
|
||||||
|
};
|
||||||
|
painter.colours.cpu_colour_styles
|
||||||
|
[cpu_style_index % painter.colours.cpu_colour_styles.len()]
|
||||||
|
});
|
||||||
|
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
core_data.cpu_name.clone().into(),
|
||||||
|
Some(core_data.short_cpu_name.clone().into()),
|
||||||
|
style,
|
||||||
|
),
|
||||||
|
(core_data.legend_value.clone().into(), None, style),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// TODO: You MUST draw the table first, otherwise the index may mismatch after a reset. This is a bad gotcha - we should look into auto-updating the table's length!
|
||||||
|
self.legend.draw_tui_table(
|
||||||
|
painter,
|
||||||
|
f,
|
||||||
|
&legend_data,
|
||||||
|
legend_block,
|
||||||
|
legend_block_area,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
const Y_BOUNDS: [f64; 2] = [0.0, 100.5];
|
const Y_BOUNDS: [f64; 2] = [0.0, 100.5];
|
||||||
let y_bound_labels: [Cow<'static, str>; 2] = ["0%".into(), "100%".into()];
|
let y_bound_labels: [Cow<'static, str>; 2] = ["0%".into(), "100%".into()];
|
||||||
|
|
||||||
@ -243,59 +292,10 @@ impl Widget for CpuGraph {
|
|||||||
graph_block,
|
graph_block,
|
||||||
graph_block_area,
|
graph_block_area,
|
||||||
);
|
);
|
||||||
|
|
||||||
let legend_block = self
|
|
||||||
.block()
|
|
||||||
.selected(selected && matches!(&self.selected, CpuGraphSelection::Legend))
|
|
||||||
.expanded(expanded)
|
|
||||||
.hide_title(true);
|
|
||||||
|
|
||||||
let legend_data = self
|
|
||||||
.display_data
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(cpu_index, core_data)| {
|
|
||||||
let style = Some(if cpu_index == 0 {
|
|
||||||
painter.colours.all_colour_style
|
|
||||||
} else if self.showing_avg && cpu_index == 1 {
|
|
||||||
painter.colours.avg_colour_style
|
|
||||||
} else {
|
|
||||||
let cpu_style_index = if self.showing_avg {
|
|
||||||
// No underflow should occur, as if cpu_index was
|
|
||||||
// 1 and avg is showing, it's caught by the above case!
|
|
||||||
cpu_index - 2
|
|
||||||
} else {
|
|
||||||
cpu_index - 1
|
|
||||||
};
|
|
||||||
painter.colours.cpu_colour_styles
|
|
||||||
[cpu_style_index % painter.colours.cpu_colour_styles.len()]
|
|
||||||
});
|
|
||||||
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
core_data.cpu_name.clone().into(),
|
|
||||||
Some(core_data.short_cpu_name.clone().into()),
|
|
||||||
style,
|
|
||||||
),
|
|
||||||
(core_data.legend_value.clone().into(), None, style),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
self.legend.draw_tui_table(
|
|
||||||
painter,
|
|
||||||
f,
|
|
||||||
&legend_data,
|
|
||||||
legend_block,
|
|
||||||
legend_block_area,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_data(&mut self, data_collection: &DataCollection) {
|
fn update_data(&mut self, data_collection: &DataCollection) {
|
||||||
// TODO: *Maybe* look into only taking in enough data for the current retention? Though this isn't great, it means you have to be like process with the whole updating thing.
|
convert_cpu_data_points(data_collection, &mut self.display_data);
|
||||||
convert_cpu_data_points(data_collection, &mut self.display_data, false); // TODO: Again, the "is_frozen" is probably useless
|
|
||||||
self.load_avg_data = data_collection.load_avg_harvest;
|
self.load_avg_data = data_collection.load_avg_harvest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,4 +306,46 @@ impl Widget for CpuGraph {
|
|||||||
fn height(&self) -> LayoutRule {
|
fn height(&self) -> LayoutRule {
|
||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_widget_selection_left(&mut self) -> SelectionAction {
|
||||||
|
match self.legend_position {
|
||||||
|
CpuGraphLegendPosition::Left => {
|
||||||
|
if let CpuGraphSelection::Graph = self.selected {
|
||||||
|
self.selected = CpuGraphSelection::Legend;
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CpuGraphLegendPosition::Right => {
|
||||||
|
if let CpuGraphSelection::Legend = self.selected {
|
||||||
|
self.selected = CpuGraphSelection::Graph;
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_widget_selection_right(&mut self) -> SelectionAction {
|
||||||
|
match self.legend_position {
|
||||||
|
CpuGraphLegendPosition::Left => {
|
||||||
|
if let CpuGraphSelection::Legend = self.selected {
|
||||||
|
self.selected = CpuGraphSelection::Graph;
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CpuGraphLegendPosition::Right => {
|
||||||
|
if let CpuGraphSelection::Graph = self.selected {
|
||||||
|
self.selected = CpuGraphSelection::Legend;
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,8 +129,8 @@ impl Widget for MemGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_data(&mut self, data_collection: &DataCollection) {
|
fn update_data(&mut self, data_collection: &DataCollection) {
|
||||||
self.mem_data = convert_mem_data_points(data_collection, false); // TODO: I think the "is_frozen" part is useless... it's always false now.
|
self.mem_data = convert_mem_data_points(data_collection);
|
||||||
self.swap_data = convert_swap_data_points(data_collection, false);
|
self.swap_data = convert_swap_data_points(data_collection);
|
||||||
let (memory_labels, swap_labels) = convert_mem_labels(data_collection);
|
let (memory_labels, swap_labels) = convert_mem_labels(data_collection);
|
||||||
|
|
||||||
self.mem_labels = memory_labels;
|
self.mem_labels = memory_labels;
|
||||||
|
@ -568,7 +568,6 @@ impl Widget for NetGraph {
|
|||||||
fn update_data(&mut self, data_collection: &DataCollection) {
|
fn update_data(&mut self, data_collection: &DataCollection) {
|
||||||
let network_data = convert_network_data_points(
|
let network_data = convert_network_data_points(
|
||||||
data_collection,
|
data_collection,
|
||||||
false, // TODO: I think the is_frozen here is also useless; see mem and cpu
|
|
||||||
false,
|
false,
|
||||||
&self.scale_type,
|
&self.scale_type,
|
||||||
&self.unit_type,
|
&self.unit_type,
|
||||||
@ -709,7 +708,6 @@ impl Widget for OldNetGraph {
|
|||||||
fn update_data(&mut self, data_collection: &DataCollection) {
|
fn update_data(&mut self, data_collection: &DataCollection) {
|
||||||
let network_data = convert_network_data_points(
|
let network_data = convert_network_data_points(
|
||||||
data_collection,
|
data_collection,
|
||||||
false, // TODO: I think the is_frozen here is also useless; see mem and cpu
|
|
||||||
true,
|
true,
|
||||||
&self.net_graph.scale_type,
|
&self.net_graph.scale_type,
|
||||||
&self.net_graph.unit_type,
|
&self.net_graph.unit_type,
|
||||||
|
@ -15,7 +15,7 @@ use tui::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
data_harvester::processes::ProcessHarvest,
|
data_harvester::processes::ProcessHarvest,
|
||||||
event::{MultiKey, MultiKeyResult, ReturnSignal, WidgetEventResult},
|
event::{MultiKey, MultiKeyResult, ReturnSignal, SelectionAction, WidgetEventResult},
|
||||||
query::*,
|
query::*,
|
||||||
text_table::DesiredColumnWidth,
|
text_table::DesiredColumnWidth,
|
||||||
widgets::tui_stuff::BlockBuilder,
|
widgets::tui_stuff::BlockBuilder,
|
||||||
@ -575,6 +575,7 @@ impl ProcState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The currently selected part of a [`ProcessManager`]
|
/// The currently selected part of a [`ProcessManager`]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
enum ProcessManagerSelection {
|
enum ProcessManagerSelection {
|
||||||
Processes,
|
Processes,
|
||||||
Sort,
|
Sort,
|
||||||
@ -775,6 +776,7 @@ pub struct ProcessManager {
|
|||||||
dd_multi: MultiKey,
|
dd_multi: MultiKey,
|
||||||
|
|
||||||
selected: ProcessManagerSelection,
|
selected: ProcessManagerSelection,
|
||||||
|
prev_selected: ProcessManagerSelection,
|
||||||
|
|
||||||
in_tree_mode: bool,
|
in_tree_mode: bool,
|
||||||
show_sort: bool,
|
show_sort: bool,
|
||||||
@ -819,6 +821,7 @@ impl ProcessManager {
|
|||||||
search_block_bounds: Rect::default(),
|
search_block_bounds: Rect::default(),
|
||||||
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static...
|
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static...
|
||||||
selected: ProcessManagerSelection::Processes,
|
selected: ProcessManagerSelection::Processes,
|
||||||
|
prev_selected: ProcessManagerSelection::Processes,
|
||||||
in_tree_mode: false,
|
in_tree_mode: false,
|
||||||
show_sort: false,
|
show_sort: false,
|
||||||
show_search: false,
|
show_search: false,
|
||||||
@ -871,6 +874,7 @@ impl ProcessManager {
|
|||||||
WidgetEventResult::NoRedraw
|
WidgetEventResult::NoRedraw
|
||||||
} else {
|
} else {
|
||||||
self.show_search = true;
|
self.show_search = true;
|
||||||
|
self.prev_selected = self.selected;
|
||||||
self.selected = ProcessManagerSelection::Search;
|
self.selected = ProcessManagerSelection::Search;
|
||||||
WidgetEventResult::Redraw
|
WidgetEventResult::Redraw
|
||||||
}
|
}
|
||||||
@ -883,6 +887,7 @@ impl ProcessManager {
|
|||||||
self.sort_menu
|
self.sort_menu
|
||||||
.set_index(self.process_table.current_sorting_column_index());
|
.set_index(self.process_table.current_sorting_column_index());
|
||||||
self.show_sort = true;
|
self.show_sort = true;
|
||||||
|
self.prev_selected = self.selected;
|
||||||
self.selected = ProcessManagerSelection::Sort;
|
self.selected = ProcessManagerSelection::Sort;
|
||||||
WidgetEventResult::Redraw
|
WidgetEventResult::Redraw
|
||||||
}
|
}
|
||||||
@ -966,6 +971,7 @@ impl ProcessManager {
|
|||||||
fn hide_sort(&mut self) {
|
fn hide_sort(&mut self) {
|
||||||
self.show_sort = false;
|
self.show_sort = false;
|
||||||
if let ProcessManagerSelection::Sort = self.selected {
|
if let ProcessManagerSelection::Sort = self.selected {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
self.selected = ProcessManagerSelection::Processes;
|
self.selected = ProcessManagerSelection::Processes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -973,6 +979,7 @@ impl ProcessManager {
|
|||||||
fn hide_search(&mut self) {
|
fn hide_search(&mut self) {
|
||||||
self.show_search = false;
|
self.show_search = false;
|
||||||
if let ProcessManagerSelection::Search = self.selected {
|
if let ProcessManagerSelection::Search = self.selected {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
self.selected = ProcessManagerSelection::Processes;
|
self.selected = ProcessManagerSelection::Processes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1129,6 +1136,7 @@ impl Component for ProcessManager {
|
|||||||
if let ProcessManagerSelection::Processes = self.selected {
|
if let ProcessManagerSelection::Processes = self.selected {
|
||||||
self.process_table.handle_mouse_event(event)
|
self.process_table.handle_mouse_event(event)
|
||||||
} else {
|
} else {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
self.selected = ProcessManagerSelection::Processes;
|
self.selected = ProcessManagerSelection::Processes;
|
||||||
match self.process_table.handle_mouse_event(event) {
|
match self.process_table.handle_mouse_event(event) {
|
||||||
WidgetEventResult::Quit => WidgetEventResult::Quit,
|
WidgetEventResult::Quit => WidgetEventResult::Quit,
|
||||||
@ -1142,6 +1150,7 @@ impl Component for ProcessManager {
|
|||||||
if let ProcessManagerSelection::Sort = self.selected {
|
if let ProcessManagerSelection::Sort = self.selected {
|
||||||
self.sort_menu.handle_mouse_event(event)
|
self.sort_menu.handle_mouse_event(event)
|
||||||
} else {
|
} else {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
self.selected = ProcessManagerSelection::Sort;
|
self.selected = ProcessManagerSelection::Sort;
|
||||||
self.sort_menu.handle_mouse_event(event);
|
self.sort_menu.handle_mouse_event(event);
|
||||||
WidgetEventResult::Redraw
|
WidgetEventResult::Redraw
|
||||||
@ -1154,6 +1163,7 @@ impl Component for ProcessManager {
|
|||||||
if let ProcessManagerSelection::Search = self.selected {
|
if let ProcessManagerSelection::Search = self.selected {
|
||||||
self.search_input.handle_mouse_event(event)
|
self.search_input.handle_mouse_event(event)
|
||||||
} else {
|
} else {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
self.selected = ProcessManagerSelection::Search;
|
self.selected = ProcessManagerSelection::Search;
|
||||||
self.search_input.handle_mouse_event(event);
|
self.search_input.handle_mouse_event(event);
|
||||||
WidgetEventResult::Redraw
|
WidgetEventResult::Redraw
|
||||||
@ -1261,9 +1271,11 @@ impl Widget for ProcessManager {
|
|||||||
area
|
area
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let process_selected =
|
||||||
|
selected && matches!(self.selected, ProcessManagerSelection::Processes);
|
||||||
let process_block = self
|
let process_block = self
|
||||||
.block()
|
.block()
|
||||||
.selected(selected && matches!(self.selected, ProcessManagerSelection::Processes))
|
.selected(process_selected)
|
||||||
.borders(self.block_border)
|
.borders(self.block_border)
|
||||||
.expanded(expanded && !self.show_sort && !self.show_search);
|
.expanded(expanded && !self.show_sort && !self.show_search);
|
||||||
|
|
||||||
@ -1273,7 +1285,7 @@ impl Widget for ProcessManager {
|
|||||||
&self.display_data,
|
&self.display_data,
|
||||||
process_block,
|
process_block,
|
||||||
area,
|
area,
|
||||||
selected,
|
process_selected,
|
||||||
self.show_scroll_index,
|
self.show_scroll_index,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1485,4 +1497,69 @@ impl Widget for ProcessManager {
|
|||||||
fn height(&self) -> LayoutRule {
|
fn height(&self) -> LayoutRule {
|
||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_widget_selection_left(&mut self) -> SelectionAction {
|
||||||
|
if self.show_sort {
|
||||||
|
if let ProcessManagerSelection::Processes = self.selected {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
|
self.selected = ProcessManagerSelection::Sort;
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_widget_selection_right(&mut self) -> SelectionAction {
|
||||||
|
if self.show_sort {
|
||||||
|
if let ProcessManagerSelection::Sort = self.selected {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
|
self.selected = ProcessManagerSelection::Processes;
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_widget_selection_up(&mut self) -> SelectionAction {
|
||||||
|
if self.show_search {
|
||||||
|
if let ProcessManagerSelection::Search = self.selected {
|
||||||
|
let prev = self.prev_selected;
|
||||||
|
self.prev_selected = self.selected;
|
||||||
|
if self.show_sort && prev == ProcessManagerSelection::Sort {
|
||||||
|
self.selected = ProcessManagerSelection::Sort;
|
||||||
|
} else {
|
||||||
|
self.selected = ProcessManagerSelection::Processes;
|
||||||
|
}
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_widget_selection_down(&mut self) -> SelectionAction {
|
||||||
|
if self.show_search {
|
||||||
|
if let ProcessManagerSelection::Processes = self.selected {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
|
self.selected = ProcessManagerSelection::Search;
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else if self.show_sort && self.selected == ProcessManagerSelection::Sort {
|
||||||
|
self.prev_selected = self.selected;
|
||||||
|
self.selected = ProcessManagerSelection::Search;
|
||||||
|
SelectionAction::Handled
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SelectionAction::NotHandled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
// Set up input handling
|
// Set up input handling
|
||||||
let (sender, receiver) = mpsc::channel(); // FIXME: Make this bounded, prevents overloading.
|
let (sender, receiver) = mpsc::channel(); // FIXME: Make this bounded, prevents overloading.
|
||||||
let _input_thread = create_input_thread(sender.clone(), thread_termination_lock.clone());
|
let input_thread = create_input_thread(sender.clone(), thread_termination_lock.clone());
|
||||||
|
|
||||||
// Cleaning loop
|
// Cleaning loop
|
||||||
// TODO: Probably worth spinning this off into an async thread or something...
|
// TODO: Probably worth spinning this off into an async thread or something...
|
||||||
@ -102,6 +102,7 @@ fn main() -> Result<()> {
|
|||||||
terminal.hide_cursor()?;
|
terminal.hide_cursor()?;
|
||||||
|
|
||||||
// Set panic hook
|
// Set panic hook
|
||||||
|
// TODO: Make this close all the child threads too!
|
||||||
panic::set_hook(Box::new(|info| panic_hook(info)));
|
panic::set_hook(Box::new(|info| panic_hook(info)));
|
||||||
|
|
||||||
// Set termination hook
|
// Set termination hook
|
||||||
@ -135,6 +136,8 @@ fn main() -> Result<()> {
|
|||||||
// I think doing it in this order is safe...
|
// I think doing it in this order is safe...
|
||||||
*thread_termination_lock.lock().unwrap() = true;
|
*thread_termination_lock.lock().unwrap() = true;
|
||||||
thread_termination_cvar.notify_all();
|
thread_termination_cvar.notify_all();
|
||||||
|
|
||||||
|
let _ = input_thread.join();
|
||||||
cleanup_terminal(&mut terminal)?;
|
cleanup_terminal(&mut terminal)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -207,7 +207,7 @@ impl Painter {
|
|||||||
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::AppState,
|
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::AppState,
|
||||||
) -> error::Result<()> {
|
) -> error::Result<()> {
|
||||||
terminal.draw(|mut f| {
|
terminal.draw(|mut f| {
|
||||||
let (draw_area, frozen_draw_loc) = if app_state.is_frozen {
|
let (draw_area, frozen_draw_loc) = if app_state.is_frozen() {
|
||||||
let split_loc = Layout::default()
|
let split_loc = Layout::default()
|
||||||
.constraints([Constraint::Min(0), Constraint::Length(1)])
|
.constraints([Constraint::Min(0), Constraint::Length(1)])
|
||||||
.split(f.size());
|
.split(f.size());
|
||||||
|
@ -243,8 +243,8 @@ pub const GENERAL_HELP_TEXT: [&str; 30] = [
|
|||||||
"1 - General",
|
"1 - General",
|
||||||
"q, Ctrl-c Quit",
|
"q, Ctrl-c Quit",
|
||||||
"Esc Close dialog windows, search, widgets, or exit expanded mode",
|
"Esc Close dialog windows, search, widgets, or exit expanded mode",
|
||||||
"Ctrl-r Reset display and any collected data",
|
"Ctrl-r Resets any collected data",
|
||||||
"f Freeze/unfreeze updating with new data",
|
"f Toggles freezing, which stops new data from being shown",
|
||||||
"Ctrl-Left, ",
|
"Ctrl-Left, ",
|
||||||
"Shift-Left, Move widget selection left",
|
"Shift-Left, Move widget selection left",
|
||||||
"H, A ",
|
"H, A ",
|
||||||
|
@ -189,17 +189,9 @@ pub fn convert_disk_row(current_data: &DataCollection) -> TextTableData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_cpu_data_points(
|
pub fn convert_cpu_data_points(
|
||||||
current_data: &DataCollection, existing_cpu_data: &mut Vec<ConvertedCpuData>, is_frozen: bool,
|
current_data: &DataCollection, existing_cpu_data: &mut Vec<ConvertedCpuData>,
|
||||||
) {
|
) {
|
||||||
let current_time = if is_frozen {
|
let current_time = current_data.current_instant;
|
||||||
if let Some(frozen_instant) = current_data.frozen_instant {
|
|
||||||
frozen_instant
|
|
||||||
} else {
|
|
||||||
current_data.current_instant
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
current_data.current_instant
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize cpu_data_vector if the lengths don't match...
|
// Initialize cpu_data_vector if the lengths don't match...
|
||||||
if let Some((_time, data)) = ¤t_data.timed_data_vec.last() {
|
if let Some((_time, data)) = ¤t_data.timed_data_vec.last() {
|
||||||
@ -250,10 +242,10 @@ pub fn convert_cpu_data_points(
|
|||||||
cpu.legend_value = format!("{:.0}%", cpu_usage.round());
|
cpu.legend_value = format!("{:.0}%", cpu_usage.round());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (time, data) in ¤t_data.timed_data_vec {
|
for (time, data) in ¤t_data.timed_data_vec {
|
||||||
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
|
let time_from_start: f64 =
|
||||||
|
(current_time.duration_since(*time).as_millis() as f64).floor();
|
||||||
|
|
||||||
for (itx, cpu) in data.cpu_data.iter().enumerate() {
|
for (itx, cpu) in data.cpu_data.iter().enumerate() {
|
||||||
if let Some(cpu_data) = existing_cpu_data.get_mut(itx + 1) {
|
if let Some(cpu_data) = existing_cpu_data.get_mut(itx + 1) {
|
||||||
@ -265,19 +257,17 @@ pub fn convert_cpu_data_points(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// No data, clear if non-empty. This is usually the case after a reset.
|
||||||
|
if !existing_cpu_data.is_empty() {
|
||||||
|
*existing_cpu_data = vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_mem_data_points(current_data: &DataCollection, is_frozen: bool) -> Vec<Point> {
|
pub fn convert_mem_data_points(current_data: &DataCollection) -> Vec<Point> {
|
||||||
let mut result: Vec<Point> = Vec::new();
|
let mut result: Vec<Point> = Vec::new();
|
||||||
let current_time = if is_frozen {
|
let current_time = current_data.current_instant;
|
||||||
if let Some(frozen_instant) = current_data.frozen_instant {
|
|
||||||
frozen_instant
|
|
||||||
} else {
|
|
||||||
current_data.current_instant
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
current_data.current_instant
|
|
||||||
};
|
|
||||||
|
|
||||||
for (time, data) in ¤t_data.timed_data_vec {
|
for (time, data) in ¤t_data.timed_data_vec {
|
||||||
if let Some(mem_data) = data.mem_data {
|
if let Some(mem_data) = data.mem_data {
|
||||||
@ -293,17 +283,9 @@ pub fn convert_mem_data_points(current_data: &DataCollection, is_frozen: bool) -
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_swap_data_points(current_data: &DataCollection, is_frozen: bool) -> Vec<Point> {
|
pub fn convert_swap_data_points(current_data: &DataCollection) -> Vec<Point> {
|
||||||
let mut result: Vec<Point> = Vec::new();
|
let mut result: Vec<Point> = Vec::new();
|
||||||
let current_time = if is_frozen {
|
let current_time = current_data.current_instant;
|
||||||
if let Some(frozen_instant) = current_data.frozen_instant {
|
|
||||||
frozen_instant
|
|
||||||
} else {
|
|
||||||
current_data.current_instant
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
current_data.current_instant
|
|
||||||
};
|
|
||||||
|
|
||||||
for (time, data) in ¤t_data.timed_data_vec {
|
for (time, data) in ¤t_data.timed_data_vec {
|
||||||
if let Some(swap_data) = data.swap_data {
|
if let Some(swap_data) = data.swap_data {
|
||||||
@ -391,21 +373,13 @@ pub fn convert_mem_labels(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rx_tx_data_points(
|
pub fn get_rx_tx_data_points(
|
||||||
current_data: &DataCollection, is_frozen: bool, network_scale_type: &AxisScaling,
|
current_data: &DataCollection, network_scale_type: &AxisScaling, network_unit_type: &DataUnit,
|
||||||
network_unit_type: &DataUnit, network_use_binary_prefix: bool,
|
network_use_binary_prefix: bool,
|
||||||
) -> (Vec<Point>, Vec<Point>) {
|
) -> (Vec<Point>, Vec<Point>) {
|
||||||
let mut rx: Vec<Point> = Vec::new();
|
let mut rx: Vec<Point> = Vec::new();
|
||||||
let mut tx: Vec<Point> = Vec::new();
|
let mut tx: Vec<Point> = Vec::new();
|
||||||
|
|
||||||
let current_time = if is_frozen {
|
let current_time = current_data.current_instant;
|
||||||
if let Some(frozen_instant) = current_data.frozen_instant {
|
|
||||||
frozen_instant
|
|
||||||
} else {
|
|
||||||
current_data.current_instant
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
current_data.current_instant
|
|
||||||
};
|
|
||||||
|
|
||||||
for (time, data) in ¤t_data.timed_data_vec {
|
for (time, data) in ¤t_data.timed_data_vec {
|
||||||
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
|
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
|
||||||
@ -446,13 +420,11 @@ pub fn get_rx_tx_data_points(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_network_data_points(
|
pub fn convert_network_data_points(
|
||||||
current_data: &DataCollection, is_frozen: bool, need_four_points: bool,
|
current_data: &DataCollection, need_four_points: bool, network_scale_type: &AxisScaling,
|
||||||
network_scale_type: &AxisScaling, network_unit_type: &DataUnit,
|
network_unit_type: &DataUnit, network_use_binary_prefix: bool,
|
||||||
network_use_binary_prefix: bool,
|
|
||||||
) -> ConvertedNetworkData {
|
) -> ConvertedNetworkData {
|
||||||
let (rx, tx) = get_rx_tx_data_points(
|
let (rx, tx) = get_rx_tx_data_points(
|
||||||
current_data,
|
current_data,
|
||||||
is_frozen,
|
|
||||||
network_scale_type,
|
network_scale_type,
|
||||||
network_unit_type,
|
network_unit_type,
|
||||||
network_use_binary_prefix,
|
network_use_binary_prefix,
|
||||||
|
16
src/lib.rs
16
src/lib.rs
@ -21,7 +21,7 @@ use std::{
|
|||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{
|
event::{
|
||||||
read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent,
|
poll, read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent,
|
||||||
MouseEventKind,
|
MouseEventKind,
|
||||||
},
|
},
|
||||||
execute,
|
execute,
|
||||||
@ -251,16 +251,11 @@ pub fn cleanup_terminal(
|
|||||||
)?;
|
)?;
|
||||||
terminal.show_cursor()?;
|
terminal.show_cursor()?;
|
||||||
|
|
||||||
// if is_debug {
|
|
||||||
// let mut tmp_dir = std::env::temp_dir();
|
|
||||||
// tmp_dir.push("bottom_debug.log");
|
|
||||||
// println!("Your debug file is located at {:?}", tmp_dir.as_os_str());
|
|
||||||
// }
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs
|
/// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs
|
||||||
|
#[allow(clippy::mutex_atomic)]
|
||||||
pub fn panic_hook(panic_info: &PanicInfo<'_>) {
|
pub fn panic_hook(panic_info: &PanicInfo<'_>) {
|
||||||
let mut stdout = stdout();
|
let mut stdout = stdout();
|
||||||
|
|
||||||
@ -307,6 +302,8 @@ pub fn create_input_thread(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(poll) = poll(Duration::from_millis(20)) {
|
||||||
|
if poll {
|
||||||
if let Ok(event) = read() {
|
if let Ok(event) = read() {
|
||||||
match event {
|
match event {
|
||||||
Event::Key(event) => {
|
Event::Key(event) => {
|
||||||
@ -321,7 +318,8 @@ pub fn create_input_thread(
|
|||||||
MouseEventKind::Drag(_) => {}
|
MouseEventKind::Drag(_) => {}
|
||||||
MouseEventKind::Moved => {}
|
MouseEventKind::Moved => {}
|
||||||
_ => {
|
_ => {
|
||||||
if Instant::now().duration_since(mouse_timer).as_millis() >= 20 {
|
if Instant::now().duration_since(mouse_timer).as_millis() >= 20
|
||||||
|
{
|
||||||
if sender.send(BottomEvent::MouseInput(event)).is_err() {
|
if sender.send(BottomEvent::MouseInput(event)).is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -337,6 +335,8 @@ pub fn create_input_thread(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user