diff options
author | Clement Tsang <34804052+ClementTsang@users.noreply.github.com> | 2020-08-29 18:54:18 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-29 18:54:18 -0400 |
commit | 3d2fc76aa24047deb167768940c5969d2bb2a595 (patch) | |
tree | 9732b15a1570a83ffc7fa0db4d78773ceda30dd6 | |
parent | b6363096b416ddbe8f4a207cdd896194ba3e6e95 (diff) |
feature: Add mouse click support for moving between widgets (#208)
Adds mouse support to the application, to move between widgets and click on elements.
List of things to added:
- Click to move between widgets
- Click to move between widgets in basic mode
- Click on widget entries
- Ability to disable mouse if you don't like it, I guess
-rw-r--r-- | .vscode/settings.json | 5 | ||||
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | README.md | 77 | ||||
-rw-r--r-- | src/app.rs | 349 | ||||
-rw-r--r-- | src/app/layout_manager.rs | 10 | ||||
-rw-r--r-- | src/app/states.rs | 4 | ||||
-rw-r--r-- | src/bin/main.rs | 6 | ||||
-rw-r--r-- | src/canvas.rs | 41 | ||||
-rw-r--r-- | src/canvas/dialogs/help_dialog.rs | 2 | ||||
-rw-r--r-- | src/canvas/widgets/basic_table_arrows.rs | 211 | ||||
-rw-r--r-- | src/canvas/widgets/battery_display.rs | 23 | ||||
-rw-r--r-- | src/canvas/widgets/cpu_basic.rs | 13 | ||||
-rw-r--r-- | src/canvas/widgets/cpu_graph.rs | 68 | ||||
-rw-r--r-- | src/canvas/widgets/disk_table.rs | 34 | ||||
-rw-r--r-- | src/canvas/widgets/mem_basic.rs | 9 | ||||
-rw-r--r-- | src/canvas/widgets/mem_graph.rs | 9 | ||||
-rw-r--r-- | src/canvas/widgets/network_basic.rs | 9 | ||||
-rw-r--r-- | src/canvas/widgets/network_graph.rs | 19 | ||||
-rw-r--r-- | src/canvas/widgets/process_table.rs | 222 | ||||
-rw-r--r-- | src/canvas/widgets/temp_table.rs | 35 | ||||
-rw-r--r-- | src/lib.rs | 15 | ||||
-rw-r--r-- | src/options.rs | 60 | ||||
-rw-r--r-- | tests/layout_movement_tests.rs | 9 |
23 files changed, 901 insertions, 333 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index dacfb4b4..554f5994 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "cSpell.words": [ + "Artem", + "COPR", "DWORD", "Deque", "EINVAL", @@ -14,8 +16,10 @@ "MSRV", "Mahmoud", "Marcin", + "Mousebindings", "Nonexhaustive", "PKGBUILD", + "Polishchuk", "Qudsi", "SIGTERM", "TEBI", @@ -26,6 +30,7 @@ "WASD", "Wojnarowski", "andys", + "atim", "choco", "cmdline", "commandline", diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f8c06f..fa1f2b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#206](https://github.com/ClementTsang/bottom/pull/206): Adaptive network graphs --- prior to this update, graphs were stuck at a range from 0B to 1GiB. Now, they adjust to your current usage and time span, so if you're using, say, less than a MiB, it will cap at a MiB. If you're using 10GiB, then the graph will reflect that and span to a bit greater than 10GiB. +- [#208](https://github.com/ClementTsang/bottom/pull/208): Mouse support for tables and moving to widgets. + ### Changes ### Bug Fixes -- [211](https://github.com/ClementTsang/bottom/pull/211): Fixes a bug where you could move down in the process widget even if the process widget search was closed. +- [#211](https://github.com/ClementTsang/bottom/pull/211): Fixes a bug where you could move down in the process widget even if the process widget search was closed. ## [0.4.7] - 2020-08-26 @@ -25,7 +25,6 @@ A cross-platform graphical process/system monitor with a customizable interface - [Options](#options) - [Keybindings](#keybindings) - [General](#general) - - [CPU bindings](#cpu-bindings) - [Process bindings](#process-bindings) - [Process search bindings](#process-search-bindings) - [Process sort bindings](#process-sort-bindings) @@ -35,6 +34,9 @@ A cross-platform graphical process/system monitor with a customizable interface - [Supported comparison operators](#supported-comparison-operators) - [Supported logical operators](#supported-logical-operators) - [Supported units](#supported-units) +- [Mousebindings](#mousebindings) + - [General](#general-1) + - [CPU bindings](#cpu-bindings) - [Features](#features) - [Processes](#processes) - [Process searching](#process-searching) @@ -43,7 +45,7 @@ A cross-platform graphical process/system monitor with a customizable interface - [Expanding](#expanding) - [Basic mode](#basic-mode) - [Config files](#config-files) - - [Config flags](#config-flags) + - [Config flags and options](#config-flags-and-options) - [Theming](#theming) - [Layout](#layout) - [Battery](#battery) @@ -176,6 +178,7 @@ Run using `btm`. --use_old_network_legend Use the older (pre-0.4) network legend which is separate from the network chart --hide_table_gap Hides the spacing between table headers and data --battery Displays the battery widget for default and basic layouts + --disable_click Disables mouse clicks from interacting with the program ``` ### Options @@ -193,34 +196,27 @@ Run using `btm`. #### General -| | | -| ------------------------------------------- | ---------------------------------------------------------------------------- | -| `q`, `Ctrl-c` | Quit | -| `Esc` | Close dialog windows, search, widgets, or exit expanded mode | -| `Ctrl-r` | Reset display and any collected data | -| `f` | Freeze/unfreeze updating with new data | -| `Ctrl-Left`<br>`Shift-Left`<br>`H`<br>`A` | Move widget selection left | -| `Ctrl-Right`<br>`Shift-Right`<br>`L`<br>`D` | Move widget selection right | -| `Ctrl-Up`<br>`Shift-Up`<br>`K`<br>`W` | Move widget selection up | -| `Ctrl-Down`<br>`Shift-Down`<br>`J`<br>`S` | Move widget selection down | -| `Left`, `h` | Move left within widget | -| `Down`, `j` | Move down within widget | -| `Up`,`k` | Move up within widget | -| `Right`, `l` | Move right within widget | -| `?` | Open help menu | -| `gg`, `Home` | Jump to the first entry | -| `Shift-g`, `End` | Jump to the last entry | -| `e` | Toggle expanding the currently selected widget | -| `+` | Zoom in on chart (decrease time range) | -| `-` | Zoom out on chart (increase time range) | -| `=` | Reset zoom | -| Mouse scroll | Table: Scroll<br>Chart: Zooms in or out by scrolling up or down respectively | - -#### CPU bindings - -| | | -| ------------ | --------------------------------------------------------------------- | -| Mouse scroll | Scrolling over an CPU core/average shows only that entry on the chart | +| | | +| ------------------------------------------- | ------------------------------------------------------------ | +| `q`, `Ctrl-c` | Quit | +| `Esc` | Close dialog windows, search, widgets, or exit expanded mode | +| `Ctrl-r` | Reset display and any collected data | +| `f` | Freeze/unfreeze updating with new data | +| `Ctrl-Left`<br>`Shift-Left`<br>`H`<br>`A` | Move widget selection left | +| `Ctrl-Right`<br>`Shift-Right`<br>`L`<br>`D` | Move widget selection right | +| `Ctrl-Up`<br>`Shift-Up`<br>`K`<br>`W` | Move widget selection up | +| `Ctrl-Down`<br>`Shift-Down`<br>`J`<br>`S` | Move widget selection down | +| `Left`, `h` | Move left within widget | +| `Down`, `j` | Move down within widget | +| `Up`,`k` | Move up within widget | +| `Right`, `l` | Move right within widget | +| `?` | Open help menu | +| `gg`, `Home` | Jump to the first entry | +| `Shift-g`, `End` | Jump to the last entry | +| `e` | Toggle expanding the currently selected widget | +| `+` | Zoom in on chart (decrease time range) | +| `-` | Zoom out on chart (increase time range) | +| `=` | Reset zoom | #### Process bindings @@ -340,6 +336,21 @@ Note that the `and` operator takes precedence over the `or` operator. | -------- | ---------------------------------------------------- | -------------------------- | | `()` | `(<CONDITION 1> AND <CONDITION 2>) OR <CONDITION 3>` | Group together a condition | +### Mousebindings + +#### General + +| | | +| ------------ | --------------------------------------------------------------------------------------------------------------------- | +| Mouse scroll | Table: Scroll<br>Chart: Zooms in or out by scrolling up or down respectively | +| Mouse click | Selects the clicked widget. For tables, clicking can also select a specific entry. Can be disabled via options/flags. | + +#### CPU bindings + +| | | +| ------------ | --------------------------------------------------------------------- | +| Mouse scroll | Scrolling over an CPU core/average shows only that entry on the chart | + ## Features As yet _another_ process/system visualization and management application, bottom supports the typical features: @@ -434,11 +445,11 @@ By default, bottom will look at (based on [dirs](https://github.com/dirs-dev/dir Note that if a config file does not exist at either the default location or the passed in location via `-C` or `--config`, one is automatically created with no settings applied. -#### Config flags +#### Config flags and options The following options can be set under `[flags]` to achieve the same effect as passing in a flag on runtime. Note that if a flag is given, it will override the config file. -These are the following supported flag config values: +These are the following supported flag config values, which correspond to the flag of the same name described in [Flags](#flags) and [Options](#options): | Field | Type | | ------------------------ | ------------------------------------------------------------------------------------- | @@ -461,6 +472,7 @@ These are the following supported flag config values: | `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) | | `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | | `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | +| `disable_click` | Boolean | #### Theming @@ -614,6 +626,7 @@ Thanks to all contributors ([emoji key](https://allcontributors.org/docs/en/emoj <!-- markdownlint-enable --> <!-- prettier-ignore-end --> + <!-- ALL-CONTRIBUTORS-LIST:END --> ## Thanks @@ -40,6 +40,7 @@ pub struct AppConfigFields { pub autohide_time: bool, pub use_old_network_legend: bool, pub table_gap: u16, + pub disable_click: bool, } #[derive(TypedBuilder)] @@ -81,6 +82,9 @@ pub struct App { pub is_force_redraw: bool, #[builder(default = false, setter(skip))] + pub is_determining_widget_boundary: bool, + + #[builder(default = false, setter(skip))] pub basic_mode_use_percent: bool, pub cpu_state: CpuState, @@ -90,9 +94,7 @@ pub struct App { pub temp_state: TempState, pub disk_state: DiskState, pub battery_state: BatteryState, - pub basic_table_widget_state: Option<BasicTableWidgetState>, - pub app_config_fields: AppConfigFields, pub widget_map: HashMap<u64, BottomWidget>, pub current_widget: BottomWidget, @@ -133,6 +135,10 @@ impl App { self.data_collection.reset(); } + pub fn should_get_widget_bounds(&self) -> bool { + self.is_force_redraw || self.is_determining_widget_boundary + } + fn close_dd(&mut self) { self.delete_dialog_state.is_showing_dd = false; self.delete_dialog_state.is_on_yes = false; @@ -279,11 +285,9 @@ impl App { } } - /// "On space" if we don't want to treat is as a character. - pub fn on_space(&mut self) {} - pub fn on_slash(&mut self) { if !self.is_in_dialog() { + // FIXME: Add ProcSort too, it's annoying if let BottomWidgetType::Proc = self.current_widget.widget_type { // Toggle on if let Some(proc_widget_state) = self @@ -295,6 +299,7 @@ impl App { .search_state .is_enabled = true; self.move_widget_selection(&WidgetDirection::Down); + self.is_force_redraw = true; } } } @@ -302,6 +307,7 @@ impl App { pub fn toggle_sort(&mut self) { match &self.current_widget.widget_type { + // FIXME: [REFACTOR] Remove these @'s if unneeded, they were an idea but they're ultimately useless for me here...? widget_type @ BottomWidgetType::Proc | widget_type @ BottomWidgetType::ProcSort => { let widget_id = self.current_widget.widget_id - match &widget_type { @@ -326,6 +332,8 @@ impl App { self.move_widget_selection(&WidgetDirection::Right); } } + + self.is_force_redraw = true; } _ => {} } @@ -1151,7 +1159,6 @@ impl App { 'L' | 'D' => self.move_widget_selection(&WidgetDirection::Right), 'K' | 'W' => self.move_widget_selection(&WidgetDirection::Up), 'J' | 'S' => self.move_widget_selection(&WidgetDirection::Down), - ' ' => self.on_space(), '+' => self.zoom_in(), '-' => self.zoom_out(), '=' => self.reset_zoom(), @@ -1214,7 +1221,16 @@ impl App { } pub fn move_widget_selection(&mut self, direction: &WidgetDirection) { + // Since we only want to call reset once, we do it like this to avoid + // redundant calls on recursion. + self.move_widget_selection_logic(direction); + self.reset_multi_tap_keys(); + } + + fn move_widget_selection_logic(&mut self, direction: &WidgetDirection) { /* + The actual logic for widget movement. + We follow these following steps: 1. Send a movement signal in `direction`. 2. Check if this new widget we've landed on is hidden. If not, halt. @@ -1234,7 +1250,6 @@ impl App { match &new_widget.widget_type { BottomWidgetType::Temp | BottomWidgetType::Proc - | BottomWidgetType::ProcSearch | BottomWidgetType::ProcSort | BottomWidgetType::Disk | BottomWidgetType::Battery @@ -1272,13 +1287,16 @@ impl App { basic_table_widget_state.currently_displayed_widget_type = self.current_widget.widget_type.clone(); } + + // And let's not forget: + self.is_determining_widget_boundary = true; } BottomWidgetType::BasicTables => { match &direction { WidgetDirection::Up => { // Note this case would fail if it moved up into a hidden // widget, but it's for basic so whatever, it's all hard-coded - // right now anyways. + // right now anyways... if let Some(next_new_widget_id) = new_widget.up_neighbour { if let Some(next_new_widget) = self.widget_map.get(&next_new_widget_id) @@ -1288,11 +1306,22 @@ impl App { } } WidgetDirection::Down => { - // This means we're in basic mode. As such, then - // we want to move DOWN to the currently shown widget + // Assuming we're in basic mode (BasicTables), then + // we want to move DOWN to the currently shown widget. if let Some(basic_table_widget_state) = - &self.basic_table_widget_state + &mut self.basic_table_widget_state { + // We also want to move towards Proc if we had set it to ProcSort. + if let BottomWidgetType::ProcSort = + basic_table_widget_state.currently_displayed_widget_type + { + basic_table_widget_state + .currently_displayed_widget_type = + BottomWidgetType::Proc; + basic_table_widget_state + .currently_displayed_widget_id -= 2; + } + if let Some(next_new_widget) = self.widget_map.get( &basic_table_widget_state.currently_displayed_widget_id, ) { @@ -1308,13 +1337,13 @@ impl App { if let Some((parent_direction, offset)) = &new_widget.parent_reflector { if direction.is_opposite(parent_direction) { // Keep going in the current direction if hidden... - let next_neighbour_id = match &direction { + // unless we hit a wall of sorts. + let option_next_neighbour_id = match &direction { WidgetDirection::Left => new_widget.left_neighbour, WidgetDirection::Right => new_widget.right_neighbour, WidgetDirection::Up => new_widget.up_neighbour, WidgetDirection::Down => new_widget.down_neighbour, - } - .unwrap_or(*new_widget_id); + }; match &new_widget.widget_type { BottomWidgetType::CpuLegend => { if let Some(cpu_widget_state) = self @@ -1323,11 +1352,15 @@ impl App { .get(&(new_widget_id - *offset)) { if cpu_widget_state.is_legend_hidden { - if let Some(next_neighbour_widget) = - self.widget_map.get(&next_neighbour_id) + if let Some(next_neighbour_id) = + option_next_neighbour_id { - self.current_widget = - next_neighbour_widget.clone(); + if let Some(next_neighbour_widget) = + self.widget_map.get(&next_neighbour_id) + { + self.current_widget = + next_neighbour_widget.clone(); + } } } else { self.current_widget = new_widget.clone(); @@ -1344,12 +1377,17 @@ impl App { match &new_widget.widget_type { BottomWidgetType::ProcSearch => { if !proc_widget_state.is_search_enabled() { - if let Some(next_neighbour_widget) = - self.widget_map - .get(&next_neighbour_id) + if let Some(next_neighbour_id) = + option_next_neighbour_id { - self.current_widget = - next_neighbour_widget.clone(); + if let Some(next_neighbour_widget) = + self.widget_map + .get(&next_neighbour_id) + { + self.current_widget = + next_neighbour_widget + .clone(); + } } } else { self.current_widget = @@ -1358,12 +1396,17 @@ impl App { } BottomWidgetType::ProcSort => { if !proc_widget_state.is_sort_open { - if let Some(next_neighbour_widget) = - self.widget_map - .get(&next_neighbour_id) + if let Some(next_neighbour_id) = + option_next_neighbour_id { - self.current_widget = - next_neighbour_widget.clone(); + if let Some(next_neighbour_widget) = + self.widget_map + .get(&next_neighbour_id) + { + self.current_widget = + next_neighbour_widget + .clone(); + } } } else { self.current_widget = @@ -1498,7 +1541,7 @@ impl App { } if let Some(ref_dir) = &reflection_dir { - self.move_widget_selection(ref_dir); + self.move_widget_selection_logic(ref_dir); } } } @@ -1544,8 +1587,6 @@ impl App { }, } } - - self.reset_multi_tap_keys(); } fn handle_left_expanded_movement(&mut self) { @@ -1768,11 +1809,11 @@ impl App { pub fn decrement_position_count(&mut self) { if !self.is_in_dialog() { match self.current_widget.widget_type { - BottomWidgetType::Proc => self.change_process_position(-1), - BottomWidgetType::ProcSort => self.change_process_sort_position(-1), - BottomWidgetType::Temp => self.change_temp_position(-1), - BottomWidgetType::Disk => self.change_disk_position(-1), - BottomWidgetType::CpuLegend => self.change_cpu_table_position(-1), + BottomWidgetType::Proc => self.increment_process_position(-1), + BottomWidgetType::ProcSort => self.increment_process_sort_position(-1), + BottomWidgetType::Temp => self.increment_temp_position(-1), + BottomWidgetType::Disk => self.increment_disk_position(-1), + BottomWidgetType::CpuLegend => self.increment_cpu_legend_position(-1), _ => {} } } @@ -1781,17 +1822,17 @@ impl App { pub fn increment_position_count(&mut self) { if !self.is_in_dialog() { match self.current_widget.widget_type { - BottomWidgetType::Proc => self.change_process_position(1), - BottomWidgetType::ProcSort => self.change_process_sort_position(1), - BottomWidgetType::Temp => self.change_temp_position(1), - BottomWidgetType::Disk => self.change_disk_position(1), - BottomWidgetType::CpuLegend => self.change_cpu_table_position(1), + BottomWidgetType::Proc => self.increment_process_position(1), + BottomWidgetType::ProcSort => self.increment_process_sort_position(1), + BottomWidgetType::Temp => self.increment_temp_position(1), + BottomWidgetType::Disk => self.increment_disk_position(1), + BottomWidgetType::CpuLegend => self.increment_cpu_legend_position(1), _ => {} } } } - fn change_process_sort_position(&mut self, num_to_change_by: i64) { + fn increment_process_sort_position(&mut self, num_to_change_by: i64) { if let Some(proc_widget_state) = self .proc_state .get_mut_widget_state(self.current_widget.widget_id - 2) @@ -1814,7 +1855,7 @@ impl App { } } - fn change_cpu_table_position(&mut self, num_to_change_by: i64) { + fn increment_cpu_legend_position(&mut self, num_to_change_by: i64) { if let Some(cpu_widget_state) = self .cpu_state .widget_states @@ -1838,13 +1879,12 @@ impl App { } } - fn change_process_position(&mut self, num_to_change_by: i64) { + fn increment_process_position(&mut self, num_to_change_by: i64) { if let Some(proc_widget_state) = self .proc_state .get_mut_widget_state(self.current_widget.widget_id) { let current_posn = proc_widget_state.scroll_state.current_scroll_position; - if let Some(finalized_process_data) = self .canvas_data .finalized_process_data_map @@ -1866,7 +1906,7 @@ impl App { } } - fn change_temp_position(&mut self, num_to_change_by: i64) { + fn increment_temp_position(&mut self, num_to_change_by: i64) { if let Some(temp_widget_state) = self .temp_state .widget_states @@ -1890,7 +1930,7 @@ impl App { } } - fn change_disk_position(&mut self, num_to_change_by: i64) { + fn increment_disk_position(&mut self, num_to_change_by: i64) { if let Some(disk_widget_state) = self .disk_state .widget_states @@ -2168,4 +2208,219 @@ impl App { _ => {} } } + + /// Moves the mouse to the widget that was clicked on, then propagates the click down to be + /// handled by the widget specifically. + pub fn left_mouse_click_movement(&mut self, x: u16, y: u16) { + // Pretty dead simple - iterate through the widget map and go to the widget where the click + // is within. + if let Some(bt) = &mut self.basic_table_widget_state { + if let ( + Some((left_tlc_x, left_tlc_y)), + Some((left_brc_x, left_brc_y)), + Some((right_tlc_x, right_tlc_y)), + Some((right_brc_x, right_brc_y)), + ) = (bt.left_tlc, bt.left_brc, bt.right_tlc, bt.right_brc) + { + if (x >= left_tlc_x && y >= left_tlc_y) && (x <= left_brc_x && y <= left_brc_y) { + if let Some(new_widget) = + self.widget_map.get(&(bt.currently_displayed_widget_id)) + { + // We have to move to the current table widget first... + self.current_widget = new_widget.clone(); + + if let BottomWidgetType::Proc = &new_widget.widget_type { + if let Some(proc_widget_state) = + self.proc_state.get_widget_state(new_widget.widget_id) + { + if proc_widget_state.is_sort_open { + self.move_widget_selection(&WidgetDirection::Left); + } + } + } + self.move_widget_selection(&WidgetDirection::Left); + return; + } + } else if (x >= right_tlc_x && y >= right_tlc_y) + && (x <= right_brc_x && y <= right_brc_y) + { + if let Some(new_widget) = + self.widget_map.get(&(bt.currently_displayed_widget_id)) + { + // We have to move to the current table widget first... + self.current_widget = new_widget.clone(); + + if let BottomWidgetType::ProcSort = &new_widget.widget_type { + if let Some(proc_widget_state) = + self.proc_state.get_widget_state(new_widget.widget_id - 2) + { + if proc_widget_state.is_sort_open { + self.move_widget_selection(&WidgetDirection::Right); + } + } + } + } + self.move_widget_selection(&WidgetDirection::Right); + // Bit extra logic to ensure you always land on a proc widget, not the sort + if let BottomWidgetType::ProcSort = &self.current_widget.widget_type { + self.move_widget_selection(&WidgetDirection::Right); + } + return; + } + } + } + + let mut failed_to_get = true; + // TODO: [MOUSE] We could use a better data structure for this? Currently it's a blind + // traversal through a hashmap, using a 2d binary tree of sorts would be better. + for (new_widget_id, widget) in &self.widget_map { + if let (Some((tlc_x, tlc_y)), Some((brc_x, brc_y))) = + (widget.top_left_corner, widget.bottom_right_corner) + { + if (x >= tlc_x && y >= tlc_y) && (x <= brc_x && y <= brc_y) { + if let Some(new_widget) = self.widget_map.get(&new_widget_id) { + self.current_widget = new_widget.clone(); + |