mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-25 14:44:39 +02:00
refactor: Create basic widget system
This commit is contained in:
parent
fceae8d442
commit
4f0eb7b7eb
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -231,7 +231,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bottom"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bottom"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
authors = ["Clement Tsang <cjhtsang@uwaterloo.ca>"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/ClementTsang/bottom"
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub mod data_farmer;
|
||||
pub mod data_harvester;
|
||||
pub mod event;
|
||||
pub mod filter;
|
||||
pub mod layout_manager;
|
||||
mod process_killer;
|
||||
|
94
src/app/event.rs
Normal file
94
src/app/event.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub enum EventResult {
|
||||
Quit,
|
||||
Redraw,
|
||||
Continue,
|
||||
}
|
||||
|
||||
enum MultiKeyState {
|
||||
Idle,
|
||||
Waiting {
|
||||
trigger_instant: Instant,
|
||||
checked_index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// The possible outcomes of calling [`MultiKey::input`] on a [`MultiKey`].
|
||||
pub enum MultiKeyResult {
|
||||
/// Returned when a character was *accepted*, but has not completed the sequence required.
|
||||
Accepted,
|
||||
|
||||
/// Returned when a character is accepted and completes the sequence.
|
||||
Completed,
|
||||
|
||||
/// Returned if a character breaks the sequence or if it has already timed out.
|
||||
Rejected,
|
||||
}
|
||||
|
||||
/// A struct useful for managing multi-key keybinds.
|
||||
pub struct MultiKey {
|
||||
state: MultiKeyState,
|
||||
pattern: Vec<char>,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl MultiKey {
|
||||
pub fn register(pattern: Vec<char>, timeout: Duration) -> Self {
|
||||
Self {
|
||||
state: MultiKeyState::Idle,
|
||||
pattern,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.state = MultiKeyState::Idle;
|
||||
}
|
||||
|
||||
pub fn input(&mut self, c: char) -> MultiKeyResult {
|
||||
match &mut self.state {
|
||||
MultiKeyState::Idle => {
|
||||
if let Some(first) = self.pattern.first() {
|
||||
if *first == c {
|
||||
self.state = MultiKeyState::Waiting {
|
||||
trigger_instant: Instant::now(),
|
||||
checked_index: 0,
|
||||
};
|
||||
|
||||
return MultiKeyResult::Accepted;
|
||||
}
|
||||
}
|
||||
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
MultiKeyState::Waiting {
|
||||
trigger_instant,
|
||||
checked_index,
|
||||
} => {
|
||||
if trigger_instant.elapsed() > self.timeout {
|
||||
// Just reset and recursively call (putting it into Idle).
|
||||
self.reset();
|
||||
self.input(c)
|
||||
} else if let Some(next) = self.pattern.get(*checked_index + 1) {
|
||||
if *next == c {
|
||||
*checked_index += 1;
|
||||
|
||||
if *checked_index == self.pattern.len() - 1 {
|
||||
self.reset();
|
||||
MultiKeyResult::Completed
|
||||
} else {
|
||||
MultiKeyResult::Accepted
|
||||
}
|
||||
} else {
|
||||
self.reset();
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
} else {
|
||||
self.reset();
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
src/app/widgets/base/mod.rs
Normal file
13
src/app/widgets/base/mod.rs
Normal file
@ -0,0 +1,13 @@
|
||||
//! A collection of basic widgets.
|
||||
|
||||
pub mod text_table;
|
||||
pub use text_table::TextTable;
|
||||
|
||||
pub mod time_graph;
|
||||
pub use time_graph::TimeGraph;
|
||||
|
||||
pub mod scrollable;
|
||||
pub use scrollable::Scrollable;
|
||||
|
||||
pub mod text_input;
|
||||
pub use text_input::TextInput;
|
194
src/app/widgets/base/scrollable.rs
Normal file
194
src/app/widgets/base/scrollable.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent};
|
||||
use tui::widgets::TableState;
|
||||
|
||||
use crate::app::{
|
||||
event::{EventResult, MultiKey, MultiKeyResult},
|
||||
Widget,
|
||||
};
|
||||
|
||||
pub enum ScrollDirection {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
/// A "scrollable" [`Widget`] component. Intended for use as part of another [`Widget]].
|
||||
pub struct Scrollable {
|
||||
current_index: usize,
|
||||
previous_index: usize,
|
||||
scroll_direction: ScrollDirection,
|
||||
num_items: usize,
|
||||
|
||||
tui_state: TableState,
|
||||
gg_manager: MultiKey,
|
||||
}
|
||||
|
||||
impl Scrollable {
|
||||
/// Creates a new [`Scrollable`].
|
||||
pub fn new(num_items: usize) -> Self {
|
||||
Self {
|
||||
current_index: 0,
|
||||
previous_index: 0,
|
||||
scroll_direction: ScrollDirection::Down,
|
||||
num_items,
|
||||
tui_state: TableState::default(),
|
||||
gg_manager: MultiKey::register(vec!['g', 'g'], Duration::from_millis(400)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Scrollable`]. Note this will set the associated [`TableState`] to select the first entry.
|
||||
pub fn new_selected(num_items: usize) -> Self {
|
||||
let mut scrollable = Scrollable::new(num_items);
|
||||
scrollable.tui_state.select(Some(0));
|
||||
|
||||
scrollable
|
||||
}
|
||||
|
||||
pub fn index(&self) -> usize {
|
||||
self.current_index
|
||||
}
|
||||
|
||||
/// Update the index with this! This will automatically update the previous index and scroll direction!
|
||||
fn update_index(&mut self, new_index: usize) {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
match new_index.cmp(&self.current_index) {
|
||||
Ordering::Greater => {
|
||||
self.previous_index = self.current_index;
|
||||
self.current_index = new_index;
|
||||
self.scroll_direction = ScrollDirection::Down;
|
||||
}
|
||||
Ordering::Less => {
|
||||
self.previous_index = self.current_index;
|
||||
self.current_index = new_index;
|
||||
self.scroll_direction = ScrollDirection::Up;
|
||||
}
|
||||
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_to_first(&mut self) -> EventResult {
|
||||
if self.current_index != 0 {
|
||||
self.update_index(0);
|
||||
|
||||
EventResult::Redraw
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_to_last(&mut self) -> EventResult {
|
||||
let last_index = self.num_items - 1;
|
||||
if self.current_index != last_index {
|
||||
self.update_index(last_index);
|
||||
|
||||
EventResult::Redraw
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves *downward* by *incrementing* the current index.
|
||||
fn move_down(&mut self, change_by: usize) -> EventResult {
|
||||
let new_index = self.current_index + change_by;
|
||||
if new_index >= self.num_items {
|
||||
let last_index = self.num_items - 1;
|
||||
if self.current_index != last_index {
|
||||
self.update_index(last_index);
|
||||
|
||||
EventResult::Redraw
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
} else {
|
||||
self.update_index(new_index);
|
||||
EventResult::Redraw
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves *upward* by *decrementing* the current index.
|
||||
fn move_up(&mut self, change_by: usize) -> EventResult {
|
||||
let new_index = self.current_index.saturating_sub(change_by);
|
||||
if new_index == 0 {
|
||||
if self.current_index != 0 {
|
||||
self.update_index(0);
|
||||
|
||||
EventResult::Redraw
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
} else {
|
||||
self.update_index(new_index);
|
||||
EventResult::Redraw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Scrollable {
|
||||
type UpdateState = usize;
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
use crossterm::event::KeyCode::{Char, Down, Up};
|
||||
|
||||
if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT {
|
||||
match event.code {
|
||||
Down if event.modifiers == KeyModifiers::NONE => self.move_down(1),
|
||||
Up if event.modifiers == KeyModifiers::NONE => self.move_up(1),
|
||||
Char('j') => self.move_down(1),
|
||||
Char('k') => self.move_up(1),
|
||||
Char('g') => match self.gg_manager.input('g') {
|
||||
MultiKeyResult::Completed => self.skip_to_first(),
|
||||
MultiKeyResult::Accepted => EventResult::Continue,
|
||||
MultiKeyResult::Rejected => EventResult::Continue,
|
||||
},
|
||||
Char('G') => self.skip_to_last(),
|
||||
_ => EventResult::Continue,
|
||||
}
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent, _x: u16, y: u16) -> 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).
|
||||
|
||||
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);
|
||||
} else {
|
||||
let offset = selected - y;
|
||||
return self.move_up(offset);
|
||||
}
|
||||
}
|
||||
|
||||
EventResult::Continue
|
||||
}
|
||||
crossterm::event::MouseEventKind::ScrollDown => self.move_down(1),
|
||||
crossterm::event::MouseEventKind::ScrollUp => self.move_up(1),
|
||||
_ => EventResult::Continue,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, new_num_items: usize) {
|
||||
self.num_items = new_num_items;
|
||||
|
||||
if new_num_items <= self.current_index {
|
||||
self.current_index = new_num_items - 1;
|
||||
}
|
||||
|
||||
if new_num_items <= self.previous_index {
|
||||
self.previous_index = new_num_items - 1;
|
||||
}
|
||||
}
|
||||
}
|
1
src/app/widgets/base/text_input.rs
Normal file
1
src/app/widgets/base/text_input.rs
Normal file
@ -0,0 +1 @@
|
||||
pub struct TextInput {}
|
142
src/app/widgets/base/text_table.rs
Normal file
142
src/app/widgets/base/text_table.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use tui::layout::Rect;
|
||||
|
||||
use crate::{
|
||||
app::{event::EventResult, Scrollable, Widget},
|
||||
constants::TABLE_GAP_HEIGHT_LIMIT,
|
||||
};
|
||||
|
||||
struct Column {
|
||||
name: &'static str,
|
||||
|
||||
// TODO: I would remove these in the future, storing them here feels weird...
|
||||
desired_column_width: u16,
|
||||
calculated_column_width: u16,
|
||||
|
||||
x_bounds: (u16, u16),
|
||||
}
|
||||
|
||||
impl Column {}
|
||||
|
||||
/// The [`Widget::UpdateState`] of a [`TextTable`].
|
||||
pub struct TextTableUpdateState {
|
||||
num_items: Option<usize>,
|
||||
columns: Option<Vec<Column>>,
|
||||
}
|
||||
|
||||
/// A sortable, scrollable table with columns.
|
||||
pub struct TextTable {
|
||||
/// Controls the scrollable state.
|
||||
scrollable: Scrollable,
|
||||
|
||||
/// The columns themselves.
|
||||
columns: Vec<Column>,
|
||||
|
||||
/// Whether to show a gap between the column headers and the columns.
|
||||
show_gap: bool,
|
||||
|
||||
/// The bounding box of the [`TextTable`].
|
||||
bounds: Rect, // TODO: I kinda want to remove this...
|
||||
|
||||
/// Which index we're sorting by.
|
||||
sort_index: usize,
|
||||
}
|
||||
|
||||
impl TextTable {
|
||||
pub fn new(num_items: usize, columns: Vec<&'static str>) -> Self {
|
||||
Self {
|
||||
scrollable: Scrollable::new(num_items),
|
||||
columns: columns
|
||||
.into_iter()
|
||||
.map(|name| Column {
|
||||
name,
|
||||
desired_column_width: 0,
|
||||
calculated_column_width: 0,
|
||||
x_bounds: (0, 0),
|
||||
})
|
||||
.collect(),
|
||||
show_gap: true,
|
||||
bounds: Rect::default(),
|
||||
sort_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_show_gap(mut self, show_gap: bool) -> Self {
|
||||
self.show_gap = show_gap;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn sort_index(mut self, sort_index: usize) -> Self {
|
||||
self.sort_index = sort_index;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn update_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
|
||||
pub fn update_calculated_column_bounds(&mut self, calculated_bounds: &[u16]) {
|
||||
self.columns
|
||||
.iter_mut()
|
||||
.zip(calculated_bounds.iter())
|
||||
.for_each(|(column, bound)| column.calculated_column_width = *bound);
|
||||
}
|
||||
|
||||
pub fn desired_column_bounds(&self) -> Vec<u16> {
|
||||
self.columns
|
||||
.iter()
|
||||
.map(|column| column.desired_column_width)
|
||||
.collect()
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
fn handle_key_event(&mut self, event: crossterm::event::KeyEvent) -> EventResult {
|
||||
self.scrollable.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(
|
||||
&mut self, event: crossterm::event::MouseEvent, x: u16, y: u16,
|
||||
) -> EventResult {
|
||||
if y == 0 {
|
||||
for (index, column) in self.columns.iter().enumerate() {
|
||||
let (start, end) = column.x_bounds;
|
||||
if start >= x && end <= y {
|
||||
self.sort_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, update_state: Self::UpdateState) {
|
||||
if let Some(num_items) = update_state.num_items {
|
||||
self.scrollable.update(num_items);
|
||||
}
|
||||
|
||||
if let Some(columns) = update_state.columns {
|
||||
self.columns = columns;
|
||||
if self.columns.len() <= self.sort_index {
|
||||
self.sort_index = self.columns.len() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
157
src/app/widgets/base/time_graph.rs
Normal file
157
src/app/widgets/base/time_graph.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent};
|
||||
|
||||
use crate::app::{event::EventResult, Widget};
|
||||
|
||||
pub enum AutohideTimerState {
|
||||
Hidden,
|
||||
Running(Instant),
|
||||
}
|
||||
|
||||
pub enum AutohideTimer {
|
||||
Disabled,
|
||||
Enabled {
|
||||
state: AutohideTimerState,
|
||||
show_duration: Duration,
|
||||
},
|
||||
}
|
||||
|
||||
impl AutohideTimer {
|
||||
fn trigger_display_timer(&mut self) {
|
||||
match self {
|
||||
AutohideTimer::Disabled => todo!(),
|
||||
AutohideTimer::Enabled {
|
||||
state,
|
||||
show_duration: _,
|
||||
} => {
|
||||
*state = AutohideTimerState::Running(Instant::now());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_display_timer(&mut self) {
|
||||
match self {
|
||||
AutohideTimer::Disabled => {}
|
||||
AutohideTimer::Enabled {
|
||||
state,
|
||||
show_duration,
|
||||
} => match state {
|
||||
AutohideTimerState::Hidden => {}
|
||||
AutohideTimerState::Running(trigger_instant) => {
|
||||
if trigger_instant.elapsed() > *show_duration {
|
||||
*state = AutohideTimerState::Hidden;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A graph widget with controllable time ranges along the x-axis.
|
||||
pub struct TimeGraph {
|
||||
current_display_time: u64,
|
||||
autohide_timer: AutohideTimer,
|
||||
|
||||
default_time_value: u64,
|
||||
|
||||
min_duration: u64,
|
||||
max_duration: u64,
|
||||
time_interval: u64,
|
||||
}
|
||||
|
||||
impl TimeGraph {
|
||||
pub fn new(
|
||||
start_value: u64, autohide_timer: AutohideTimer, min_duration: u64, max_duration: u64,
|
||||
time_interval: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
current_display_time: start_value,
|
||||
autohide_timer,
|
||||
default_time_value: start_value,
|
||||
min_duration,
|
||||
max_duration,
|
||||
time_interval,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_char(&mut self, c: char) -> EventResult {
|
||||
match c {
|
||||
'-' => self.zoom_out(),
|
||||
'+' => self.zoom_in(),
|
||||
'=' => self.reset_zoom(),
|
||||
_ => EventResult::Continue,
|
||||
}
|
||||
}
|
||||
|
||||
fn zoom_in(&mut self) -> EventResult {
|
||||
let new_time = self.current_display_time.saturating_sub(self.time_interval);
|
||||
|
||||
if new_time >= self.min_duration {
|
||||
self.current_display_time = new_time;
|
||||
self.autohide_timer.trigger_display_timer();
|
||||
|
||||
EventResult::Redraw
|
||||
} else if new_time != self.min_duration {
|
||||
self.current_display_time = self.min_duration;
|
||||
self.autohide_timer.trigger_display_timer();
|
||||
|
||||
EventResult::Redraw
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn zoom_out(&mut self) -> EventResult {
|
||||
let new_time = self.current_display_time + self.time_interval;
|
||||
|
||||
if new_time <= self.max_duration {
|
||||
self.current_display_time = new_time;
|
||||
self.autohide_timer.trigger_display_timer();
|
||||
|
||||
EventResult::Redraw
|
||||
} else if new_time != self.max_duration {
|
||||
self.current_display_time = self.max_duration;
|
||||
self.autohide_timer.trigger_display_timer();
|
||||
|
||||
EventResult::Redraw
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_zoom(&mut self) -> EventResult {
|
||||
if self.current_display_time == self.default_time_value {
|
||||
EventResult::Continue
|
||||
} else {
|
||||
self.current_display_time = self.default_time_value;
|
||||
self.autohide_timer.trigger_display_timer();
|
||||
EventResult::Redraw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for TimeGraph {
|
||||
type UpdateState = ();
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
use crossterm::event::KeyCode::Char;
|
||||
|
||||
if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT {
|
||||
match event.code {
|
||||
Char(c) => self.handle_char(c),
|
||||
_ => EventResult::Continue,
|
||||
}
|
||||
} else {
|
||||
EventResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent, _x: u16, _y: u16) -> EventResult {
|
||||
match event.kind {
|
||||
crossterm::event::MouseEventKind::ScrollDown => self.zoom_out(),
|
||||
crossterm::event::MouseEventKind::ScrollUp => self.zoom_in(),
|
||||
_ => EventResult::Continue,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,15 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use tui::widgets::TableState;
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tui::{layout::Rect, widgets::TableState};
|
||||
|
||||
use crate::{app::layout_manager::BottomWidgetType, constants};
|
||||
use crate::{
|
||||
app::{event::EventResult, layout_manager::BottomWidgetType},
|
||||
constants,
|
||||
};
|
||||
|
||||
pub mod base;
|
||||
pub use base::*;
|
||||
|
||||
pub mod process;
|
||||
pub use process::*;
|
||||
@ -25,6 +32,34 @@ pub use self::battery::*;
|
||||
pub mod temp;
|
||||
pub use temp::*;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub trait Widget {
|
||||
type UpdateState;
|
||||
|
||||
/// Handles a [`KeyEvent`].
|
||||
///
|
||||
/// Defaults to returning [`EventResult::Continue`], indicating nothing should be done.
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
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!
|
||||
///
|
||||
/// Defaults to returning [`EventResult::Continue`], indicating nothing should be done.
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent, x: u16, y: u16) -> EventResult {
|
||||
EventResult::Continue
|
||||
}
|
||||
|
||||
/// Updates a [`Widget`]. Defaults to doing nothing.
|
||||
fn update(&mut self, update_state: Self::UpdateState) {}
|
||||
|
||||
/// Returns a [`Widget`]'s bounding box, if possible. Defaults to returning [`None`].
|
||||
fn bounding_box(&self) -> Option<Rect> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScrollDirection {
|
||||
// UP means scrolling up --- this usually DECREMENTS
|
||||
|
@ -4,7 +4,7 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use bottom::{canvas, constants::*, data_conversion::*, options::*, *};
|
||||
use bottom::{app::event::EventResult, canvas, constants::*, data_conversion::*, options::*, *};
|
||||
|
||||
use std::{
|
||||
boxed::Box,
|
||||
|
@ -31,6 +31,7 @@ use crossterm::{
|
||||
|
||||
use app::{
|
||||
data_harvester::{self, processes::ProcessSorting},
|
||||
event::EventResult,
|
||||
layout_manager::{UsedWidgets, WidgetDirection},
|
||||
AppState,
|
||||
};
|
||||
@ -74,12 +75,6 @@ pub enum ThreadControlEvent {
|
||||
UpdateUpdateTime(u64),
|
||||
}
|
||||
|
||||
pub enum EventResult {
|
||||
Quit,
|
||||
Redraw,
|
||||
Continue,
|
||||
}
|
||||
|
||||
pub fn handle_mouse_event(event: MouseEvent, app: &mut AppState) -> EventResult {
|
||||
match event.kind {
|
||||
MouseEventKind::Down(MouseButton::Left) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user