mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 07:34:27 +02:00
refactor: Create new main widgets
This commit is contained in:
parent
4f0eb7b7eb
commit
e657fec2c0
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -244,6 +244,7 @@ dependencies = [
|
||||
"crossterm",
|
||||
"ctrlc",
|
||||
"dirs",
|
||||
"enum_dispatch",
|
||||
"fern",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
@ -525,6 +526,18 @@ version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "enum_dispatch"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd53b3fde38a39a06b2e66dc282f3e86191e53bd04cc499929c15742beae3df8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.1"
|
||||
|
@ -43,6 +43,7 @@ ctrlc = { version = "3.1.9", features = ["termination"] }
|
||||
clap = "2.33"
|
||||
cfg-if = "1.0"
|
||||
dirs = "3.0.2"
|
||||
enum_dispatch = "0.3.7"
|
||||
futures = "0.3.14"
|
||||
futures-timer = "3.0.2"
|
||||
fxhash = "0.2.1"
|
||||
|
@ -7,14 +7,20 @@ pub enum EventResult {
|
||||
}
|
||||
|
||||
enum MultiKeyState {
|
||||
/// Currently not waiting on any next input.
|
||||
Idle,
|
||||
|
||||
/// Waiting for the next input, with a given trigger [`Instant`].
|
||||
Waiting {
|
||||
/// When it was triggered.
|
||||
trigger_instant: Instant,
|
||||
|
||||
/// What part of the pattern it is at.
|
||||
checked_index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// The possible outcomes of calling [`MultiKey::input`] on a [`MultiKey`].
|
||||
/// The possible outcomes of calling [`MultiKey::input`].
|
||||
pub enum MultiKeyResult {
|
||||
/// Returned when a character was *accepted*, but has not completed the sequence required.
|
||||
Accepted,
|
||||
@ -34,6 +40,7 @@ pub struct MultiKey {
|
||||
}
|
||||
|
||||
impl MultiKey {
|
||||
/// Creates a new [`MultiKey`] with a given pattern and timeout.
|
||||
pub fn register(pattern: Vec<char>, timeout: Duration) -> Self {
|
||||
Self {
|
||||
state: MultiKeyState::Idle,
|
||||
@ -42,10 +49,18 @@ impl MultiKey {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
/// Resets to an idle state.
|
||||
fn reset(&mut self) {
|
||||
self.state = MultiKeyState::Idle;
|
||||
}
|
||||
|
||||
/// Handles a char input and returns the current status of the [`MultiKey`] after, which is one of:
|
||||
/// - Accepting the char and moving to the next state
|
||||
/// - Completing the multi-key pattern
|
||||
/// - Rejecting it
|
||||
///
|
||||
/// Note that if a [`MultiKey`] only "times out" upon calling this - if it has timed out, it will first reset
|
||||
/// before trying to check the char.
|
||||
pub fn input(&mut self, c: char) -> MultiKeyResult {
|
||||
match &mut self.state {
|
||||
MultiKeyState::Idle => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent};
|
||||
use tui::widgets::TableState;
|
||||
use tui::{layout::Rect, widgets::TableState};
|
||||
|
||||
use crate::app::{
|
||||
event::{EventResult, MultiKey, MultiKeyResult},
|
||||
@ -13,7 +13,8 @@ pub enum ScrollDirection {
|
||||
Down,
|
||||
}
|
||||
|
||||
/// A "scrollable" [`Widget`] component. Intended for use as part of another [`Widget]].
|
||||
/// A "scrollable" [`Widget`] component. Intended for use as part of another [`Widget`] - as such, it does
|
||||
/// not have any bounds or the like.
|
||||
pub struct Scrollable {
|
||||
current_index: usize,
|
||||
previous_index: usize,
|
||||
@ -22,6 +23,8 @@ pub struct Scrollable {
|
||||
|
||||
tui_state: TableState,
|
||||
gg_manager: MultiKey,
|
||||
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl Scrollable {
|
||||
@ -34,6 +37,7 @@ impl Scrollable {
|
||||
num_items,
|
||||
tui_state: TableState::default(),
|
||||
gg_manager: MultiKey::register(vec!['g', 'g'], Duration::from_millis(400)),
|
||||
bounds: Rect::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +131,7 @@ impl Scrollable {
|
||||
}
|
||||
|
||||
impl Widget for Scrollable {
|
||||
type UpdateState = usize;
|
||||
type UpdateData = usize;
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
use crossterm::event::KeyCode::{Char, Down, Up};
|
||||
@ -151,18 +155,18 @@ impl Widget for Scrollable {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent, _x: u16, y: u16) -> EventResult {
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
match event.kind {
|
||||
crossterm::event::MouseEventKind::Down(MouseButton::Left) => {
|
||||
// This requires a bit of fancy calculation. The main trick is remembering that
|
||||
// we are using a *visual* index here - not what is the actual index! Luckily, we keep track of that
|
||||
// inside our linked copy of TableState!
|
||||
//
|
||||
|
||||
// Note that y is assumed to be *relative*;
|
||||
// we assume that y starts at where the list starts (and there are no gaps or whatever).
|
||||
let y = usize::from(event.row - self.bounds.top());
|
||||
|
||||
if let Some(selected) = self.tui_state.selected() {
|
||||
let y = y as usize;
|
||||
if y > selected {
|
||||
let offset = y - selected;
|
||||
return self.move_down(offset);
|
||||
@ -191,4 +195,12 @@ impl Widget for Scrollable {
|
||||
self.previous_index = new_num_items - 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tui::layout::Rect;
|
||||
|
||||
use crate::{
|
||||
app::{event::EventResult, Scrollable, Widget},
|
||||
constants::TABLE_GAP_HEIGHT_LIMIT,
|
||||
};
|
||||
use crate::app::{event::EventResult, Scrollable, Widget};
|
||||
|
||||
struct Column {
|
||||
name: &'static str,
|
||||
@ -18,7 +16,7 @@ struct Column {
|
||||
impl Column {}
|
||||
|
||||
/// The [`Widget::UpdateState`] of a [`TextTable`].
|
||||
pub struct TextTableUpdateState {
|
||||
pub struct TextTableUpdateData {
|
||||
num_items: Option<usize>,
|
||||
columns: Option<Vec<Column>>,
|
||||
}
|
||||
@ -35,7 +33,7 @@ pub struct TextTable {
|
||||
show_gap: bool,
|
||||
|
||||
/// The bounding box of the [`TextTable`].
|
||||
bounds: Rect, // TODO: I kinda want to remove this...
|
||||
bounds: Rect, // TODO: Consider moving bounds to something else???
|
||||
|
||||
/// Which index we're sorting by.
|
||||
sort_index: usize,
|
||||
@ -91,26 +89,20 @@ impl TextTable {
|
||||
pub fn column_names(&self) -> Vec<&'static str> {
|
||||
self.columns.iter().map(|column| column.name).collect()
|
||||
}
|
||||
|
||||
fn is_drawing_gap(&self) -> bool {
|
||||
if !self.show_gap {
|
||||
false
|
||||
} else {
|
||||
self.bounds.height >= TABLE_GAP_HEIGHT_LIMIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for TextTable {
|
||||
type UpdateState = TextTableUpdateState;
|
||||
type UpdateData = TextTableUpdateData;
|
||||
|
||||
fn handle_key_event(&mut self, event: crossterm::event::KeyEvent) -> EventResult {
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
self.scrollable.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(
|
||||
&mut self, event: crossterm::event::MouseEvent, x: u16, y: u16,
|
||||
) -> EventResult {
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
// Note these are representing RELATIVE coordinates!
|
||||
let x = event.column - self.bounds.left();
|
||||
let y = event.row - self.bounds.top();
|
||||
|
||||
if y == 0 {
|
||||
for (index, column) in self.columns.iter().enumerate() {
|
||||
let (start, end) = column.x_bounds;
|
||||
@ -120,23 +112,29 @@ impl Widget for TextTable {
|
||||
}
|
||||
|
||||
EventResult::Continue
|
||||
} else if self.is_drawing_gap() {
|
||||
self.scrollable.handle_mouse_event(event, x, y - 1)
|
||||
} else {
|
||||
self.scrollable.handle_mouse_event(event, x, y - 2)
|
||||
self.scrollable.handle_mouse_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, update_state: Self::UpdateState) {
|
||||
if let Some(num_items) = update_state.num_items {
|
||||
fn update(&mut self, update_data: Self::UpdateData) {
|
||||
if let Some(num_items) = update_data.num_items {
|
||||
self.scrollable.update(num_items);
|
||||
}
|
||||
|
||||
if let Some(columns) = update_state.columns {
|
||||
if let Some(columns) = update_data.columns {
|
||||
self.columns = columns;
|
||||
if self.columns.len() <= self.sort_index {
|
||||
self.sort_index = self.columns.len() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,17 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent};
|
||||
use tui::layout::Rect;
|
||||
|
||||
use crate::app::{event::EventResult, Widget};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum AutohideTimerState {
|
||||
Hidden,
|
||||
Running(Instant),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum AutohideTimer {
|
||||
Disabled,
|
||||
Enabled {
|
||||
@ -58,6 +61,8 @@ pub struct TimeGraph {
|
||||
min_duration: u64,
|
||||
max_duration: u64,
|
||||
time_interval: u64,
|
||||
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl TimeGraph {
|
||||
@ -72,9 +77,11 @@ impl TimeGraph {
|
||||
min_duration,
|
||||
max_duration,
|
||||
time_interval,
|
||||
bounds: Rect::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a char `c`.
|
||||
fn handle_char(&mut self, c: char) -> EventResult {
|
||||
match c {
|
||||
'-' => self.zoom_out(),
|
||||
@ -132,7 +139,7 @@ impl TimeGraph {
|
||||
}
|
||||
|
||||
impl Widget for TimeGraph {
|
||||
type UpdateState = ();
|
||||
type UpdateData = ();
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
use crossterm::event::KeyCode::Char;
|
||||
@ -147,11 +154,19 @@ impl Widget for TimeGraph {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent, _x: u16, _y: u16) -> EventResult {
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
match event.kind {
|
||||
crossterm::event::MouseEventKind::ScrollDown => self.zoom_out(),
|
||||
crossterm::event::MouseEventKind::ScrollUp => self.zoom_in(),
|
||||
_ => EventResult::Continue,
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,14 @@
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use super::{AppScrollWidgetState, CanvasTableWidthState};
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tui::layout::Rect;
|
||||
|
||||
use crate::app::event::EventResult;
|
||||
|
||||
use super::{
|
||||
does_point_intersect_rect, text_table::TextTableUpdateData, AppScrollWidgetState,
|
||||
CanvasTableWidthState, TextTable, TimeGraph, Widget,
|
||||
};
|
||||
|
||||
pub struct CpuWidgetState {
|
||||
pub current_display_time: u64,
|
||||
@ -45,3 +53,88 @@ impl CpuState {
|
||||
self.widget_states.get(&widget_id)
|
||||
}
|
||||
}
|
||||
|
||||
enum CpuGraphSelection {
|
||||
Graph,
|
||||
Legend,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Whether the [`CpuGraph`]'s legend is placed on the left or right.
|
||||
pub enum CpuGraphLegendPosition {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
pub struct CpuGraphUpdateData {
|
||||
pub legend_data: Option<TextTableUpdateData>,
|
||||
}
|
||||
|
||||
/// A widget designed to show CPU usage via a graph, along with a side legend implemented as a [`TextTable`].
|
||||
pub struct CpuGraph {
|
||||
graph: TimeGraph,
|
||||
legend: TextTable,
|
||||
pub legend_position: CpuGraphLegendPosition,
|
||||
|
||||
bounds: Rect,
|
||||
selected: CpuGraphSelection,
|
||||
}
|
||||
|
||||
impl CpuGraph {
|
||||
/// Creates a new [`CpuGraph`].
|
||||
pub fn new(
|
||||
graph: TimeGraph, legend: TextTable, legend_position: CpuGraphLegendPosition,
|
||||
) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
legend,
|
||||
legend_position,
|
||||
bounds: Rect::default(),
|
||||
selected: CpuGraphSelection::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for CpuGraph {
|
||||
type UpdateData = CpuGraphUpdateData;
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
match self.selected {
|
||||
CpuGraphSelection::Graph => self.graph.handle_key_event(event),
|
||||
CpuGraphSelection::Legend => self.legend.handle_key_event(event),
|
||||
CpuGraphSelection::None => EventResult::Continue,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
// Check where we clicked (and switch the selected field if required) and fire the handler from there.
|
||||
// Note we assume that the
|
||||
|
||||
let global_x = event.column;
|
||||
let global_y = event.row;
|
||||
|
||||
if does_point_intersect_rect(global_x, global_y, self.graph.bounds()) {
|
||||
self.selected = CpuGraphSelection::Graph;
|
||||
self.graph.handle_mouse_event(event)
|
||||
} else if does_point_intersect_rect(global_x, global_y, self.legend.bounds()) {
|
||||
self.selected = CpuGraphSelection::Legend;
|
||||
self.legend.handle_mouse_event(event)
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, update_data: Self::UpdateData) {
|
||||
if let Some(legend_data) = update_data.legend_data {
|
||||
self.legend.update(legend_data);
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,14 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{AppScrollWidgetState, CanvasTableWidthState};
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tui::layout::Rect;
|
||||
|
||||
use crate::app::event::EventResult;
|
||||
|
||||
use super::{
|
||||
text_table::TextTableUpdateData, AppScrollWidgetState, CanvasTableWidthState, TextTable,
|
||||
Widget,
|
||||
};
|
||||
|
||||
pub struct DiskWidgetState {
|
||||
pub scroll_state: AppScrollWidgetState,
|
||||
@ -33,3 +41,43 @@ impl DiskState {
|
||||
self.widget_states.get(&widget_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A table displaying disk data. Essentially a wrapper around a [`TextTable`].
|
||||
pub struct DiskTable {
|
||||
table: TextTable,
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl DiskTable {
|
||||
/// Creates a new [`DiskTable`].
|
||||
pub fn new(table: TextTable) -> Self {
|
||||
Self {
|
||||
table,
|
||||
bounds: Rect::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for DiskTable {
|
||||
type UpdateData = TextTableUpdateData;
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
self.table.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
self.table.handle_mouse_event(event)
|
||||
}
|
||||
|
||||
fn update(&mut self, update_data: Self::UpdateData) {
|
||||
self.table.update(update_data);
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tui::layout::Rect;
|
||||
|
||||
use crate::app::event::EventResult;
|
||||
|
||||
use super::{TimeGraph, Widget};
|
||||
|
||||
pub struct MemWidgetState {
|
||||
pub current_display_time: u64,
|
||||
pub autohide_timer: Option<Instant>,
|
||||
@ -35,3 +42,36 @@ impl MemState {
|
||||
self.widget_states.get(&widget_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that deals with displaying memory usage on a [`TimeGraph`]. Basically just a wrapper
|
||||
/// around [`TimeGraph`] as of now.
|
||||
pub struct MemGraph {
|
||||
graph: TimeGraph,
|
||||
}
|
||||
|
||||
impl MemGraph {
|
||||
/// Creates a new [`MemGraph`].
|
||||
pub fn new(graph: TimeGraph) -> Self {
|
||||
Self { graph }
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for MemGraph {
|
||||
type UpdateData = ();
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
self.graph.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
self.graph.handle_mouse_event(event)
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.graph.bounds()
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.graph.set_bounds(new_bounds);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use tui::{layout::Rect, widgets::TableState};
|
||||
|
||||
use crate::{
|
||||
@ -32,9 +33,10 @@ pub use self::battery::*;
|
||||
pub mod temp;
|
||||
pub use temp::*;
|
||||
|
||||
#[enum_dispatch]
|
||||
#[allow(unused_variables)]
|
||||
pub trait Widget {
|
||||
type UpdateState;
|
||||
type UpdateData;
|
||||
|
||||
/// Handles a [`KeyEvent`].
|
||||
///
|
||||
@ -43,21 +45,36 @@ pub trait Widget {
|
||||
EventResult::Continue
|
||||
}
|
||||
|
||||
/// Handles a [`MouseEvent`]. `x` and `y` represent *relative* mouse coordinates to the [`Widget`] - those should
|
||||
/// be used as opposed to the coordinates in the `event` unless you need absolute coordinates for some reason!
|
||||
/// Handles a [`MouseEvent`].
|
||||
///
|
||||
/// Defaults to returning [`EventResult::Continue`], indicating nothing should be done.
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent, x: u16, y: u16) -> EventResult {
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
EventResult::Continue
|
||||
}
|
||||
|
||||
/// Updates a [`Widget`]. Defaults to doing nothing.
|
||||
fn update(&mut self, update_state: Self::UpdateState) {}
|
||||
/// Updates a [`Widget`] with new data from some state outside of its control. Defaults to doing nothing.
|
||||
fn update(&mut self, update_data: Self::UpdateData) {}
|
||||
|
||||
/// Returns a [`Widget`]'s bounding box, if possible. Defaults to returning [`None`].
|
||||
fn bounding_box(&self) -> Option<Rect> {
|
||||
None
|
||||
}
|
||||
/// Returns a [`Widget`]'s bounding box. Note that these are defined in *global*, *absolute*
|
||||
/// coordinates.
|
||||
fn bounds(&self) -> Rect;
|
||||
|
||||
/// Updates a [`Widget`]s bounding box.
|
||||
fn set_bounds(&mut self, new_bounds: Rect);
|
||||
}
|
||||
|
||||
#[enum_dispatch(BottomWidget)]
|
||||
enum BottomWidget {
|
||||
MemGraph,
|
||||
TempTable,
|
||||
DiskTable,
|
||||
CpuGraph,
|
||||
NetGraph,
|
||||
OldNetGraph,
|
||||
}
|
||||
|
||||
pub fn does_point_intersect_rect(x: u16, y: u16, rect: Rect) -> bool {
|
||||
x >= rect.left() && x <= rect.right() && y >= rect.top() && y <= rect.bottom()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1,5 +1,9 @@
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use tui::layout::Rect;
|
||||
|
||||
use super::{TimeGraph, Widget};
|
||||
|
||||
pub struct NetWidgetState {
|
||||
pub current_display_time: u64,
|
||||
pub autohide_timer: Option<Instant>,
|
||||
@ -50,3 +54,115 @@ impl NetState {
|
||||
self.widget_states.get(&widget_id)
|
||||
}
|
||||
}
|
||||
|
||||
struct NetGraphCache {
|
||||
max_range: f64,
|
||||
labels: Vec<String>,
|
||||
time_start: f64,
|
||||
}
|
||||
|
||||
enum NetGraphCacheState {
|
||||
Uncached,
|
||||
Cached(NetGraphCache),
|
||||
}
|
||||
|
||||
/// A widget denoting network usage via a graph. This version is self-contained within a single [`TimeGraph`];
|
||||
/// if you need the older one that splits into two sections, use [`OldNetGraph`], which is built on a [`NetGraph`].
|
||||
///
|
||||
/// As of now, this is essentially just a wrapper around a [`TimeGraph`].
|
||||
pub struct NetGraph {
|
||||
graph: TimeGraph,
|
||||
|
||||
// Cached details; probably want to move at some point...
|
||||
draw_cache: NetGraphCacheState,
|
||||
}
|
||||
|
||||
impl NetGraph {
|
||||
/// Creates a new [`NetGraph`].
|
||||
pub fn new(graph: TimeGraph) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
draw_cache: NetGraphCacheState::Uncached,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cache(&mut self, max_range: f64, labels: Vec<String>, time_start: f64) {
|
||||
self.draw_cache = NetGraphCacheState::Cached(NetGraphCache {
|
||||
max_range,
|
||||
labels,
|
||||
time_start,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_cached(&self) -> bool {
|
||||
match self.draw_cache {
|
||||
NetGraphCacheState::Uncached => false,
|
||||
NetGraphCacheState::Cached(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for NetGraph {
|
||||
type UpdateData = ();
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.graph.bounds()
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.graph.set_bounds(new_bounds);
|
||||
}
|
||||
|
||||
fn handle_key_event(
|
||||
&mut self, event: crossterm::event::KeyEvent,
|
||||
) -> crate::app::event::EventResult {
|
||||
self.graph.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(
|
||||
&mut self, event: crossterm::event::MouseEvent,
|
||||
) -> crate::app::event::EventResult {
|
||||
self.graph.handle_mouse_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget denoting network usage via a graph and a separate, single row table. This is built on [`NetGraph`],
|
||||
/// and the main difference is that it also contains a bounding box for the graph + text.
|
||||
pub struct OldNetGraph {
|
||||
graph: NetGraph,
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl OldNetGraph {
|
||||
/// Creates a new [`OldNetGraph`].
|
||||
pub fn new(graph: NetGraph) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
bounds: Rect::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for OldNetGraph {
|
||||
type UpdateData = ();
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
|
||||
fn handle_key_event(
|
||||
&mut self, event: crossterm::event::KeyEvent,
|
||||
) -> crate::app::event::EventResult {
|
||||
self.graph.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(
|
||||
&mut self, event: crossterm::event::MouseEvent,
|
||||
) -> crate::app::event::EventResult {
|
||||
self.graph.handle_mouse_event(event)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,14 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{AppScrollWidgetState, CanvasTableWidthState};
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tui::layout::Rect;
|
||||
|
||||
use crate::app::event::EventResult;
|
||||
|
||||
use super::{
|
||||
text_table::TextTableUpdateData, AppScrollWidgetState, CanvasTableWidthState, TextTable,
|
||||
Widget,
|
||||
};
|
||||
|
||||
pub struct TempWidgetState {
|
||||
pub scroll_state: AppScrollWidgetState,
|
||||
@ -33,3 +41,43 @@ impl TempState {
|
||||
self.widget_states.get(&widget_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A table displaying disk data. Essentially a wrapper around a [`TextTable`].
|
||||
pub struct TempTable {
|
||||
table: TextTable,
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl TempTable {
|
||||
/// Creates a new [`TempTable`].
|
||||
pub fn new(table: TextTable) -> Self {
|
||||
Self {
|
||||
table,
|
||||
bounds: Rect::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for TempTable {
|
||||
type UpdateData = TextTableUpdateData;
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
self.table.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
self.table.handle_mouse_event(event)
|
||||
}
|
||||
|
||||
fn update(&mut self, update_data: Self::UpdateData) {
|
||||
self.table.update(update_data);
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user