mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-10 14:44:18 +00:00
feature: mouse support for tabs and dd dialog (#230)
This commit is contained in:
parent
0c21cba189
commit
86c8b474ae
9 changed files with 192 additions and 37 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -35,6 +35,7 @@
|
|||
"armhf",
|
||||
"armv",
|
||||
"atim",
|
||||
"autohide",
|
||||
"choco",
|
||||
"cmdline",
|
||||
"commandline",
|
||||
|
@ -65,6 +66,7 @@
|
|||
"nuget",
|
||||
"nvme",
|
||||
"paren",
|
||||
"pids",
|
||||
"pmem",
|
||||
"ppid",
|
||||
"prepush",
|
||||
|
|
|
@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- [#208](https://github.com/ClementTsang/bottom/pull/208): Mouse support for tables and moving to widgets.
|
||||
|
||||
- [#217](https://github.com/ClementTsang/bottom/pull/217): Unofficial ARM support.
|
||||
- [#217](https://github.com/ClementTsang/bottom/pull/217): (Kinda) ARM support.
|
||||
|
||||
- [#220](https://github.com/ClementTsang/bottom/pull/220): Add ability to hide specific temperature and disk entries via config.
|
||||
|
||||
|
|
|
@ -370,10 +370,10 @@ Note that the `and` operator takes precedence over the `or` operator.
|
|||
|
||||
#### General
|
||||
|
||||
| | |
|
||||
| ------ | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| Scroll | Table: Scroll<br>Chart: Zooms in or out by scrolling up or down respectively |
|
||||
| Click | Selects the clicked widget. For tables, clicking can also select an entry.<br>Can be disabled via options/flags. |
|
||||
| | |
|
||||
| ------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| Scroll | Table: Scroll<br>Chart: Zooms in or out by scrolling up or down respectively |
|
||||
| Click | Selects the clicked widget, table entry, dialog option, or tab.<br>Can be disabled via options/flags. |
|
||||
|
||||
#### CPU bindings
|
||||
|
||||
|
|
50
src/app.rs
50
src/app.rs
|
@ -529,6 +529,7 @@ impl App {
|
|||
} else {
|
||||
self.delete_dialog_state.is_showing_dd = false;
|
||||
}
|
||||
self.is_force_redraw = true;
|
||||
} else if let BottomWidgetType::ProcSort = self.current_widget.widget_type {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
|
@ -2274,6 +2275,14 @@ impl App {
|
|||
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.
|
||||
|
||||
// TODO: [MOUSE] double click functionality...?
|
||||
// TODO: [REFACTOR] might want to refactor this, it's ugly as sin.
|
||||
// TODO: [REFACTOR] Might wanna refactor ALL state things in general, currently everything
|
||||
// is grouped up as an app state. We should separate stuff like event state and gui state and etc.
|
||||
|
||||
// Short circuit if we're in basic table... we might have to handle the basic table arrow
|
||||
// case here...
|
||||
if let Some(bt) = &mut self.basic_table_widget_state {
|
||||
if let (
|
||||
Some((left_tlc_x, left_tlc_y)),
|
||||
|
@ -2330,9 +2339,33 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
// Second short circuit --- are we in the dd dialog state? If so, only check yes/no and
|
||||
// bail after.
|
||||
if self.is_in_dialog() {
|
||||
if let (
|
||||
Some((yes_tlc_x, yes_tlc_y)),
|
||||
Some((yes_brc_x, yes_brc_y)),
|
||||
Some((no_tlc_x, no_tlc_y)),
|
||||
Some((no_brc_x, no_brc_y)),
|
||||
) = (
|
||||
self.delete_dialog_state.yes_tlc,
|
||||
self.delete_dialog_state.yes_brc,
|
||||
self.delete_dialog_state.no_tlc,
|
||||
self.delete_dialog_state.no_brc,
|
||||
) {
|
||||
if (x >= yes_tlc_x && y >= yes_tlc_y) && (x <= yes_brc_x && y <= yes_brc_y) {
|
||||
self.delete_dialog_state.is_on_yes = true;
|
||||
} else if (x >= no_tlc_x && y >= no_tlc_y) && (x <= no_brc_x && y <= no_brc_y) {
|
||||
self.delete_dialog_state.is_on_yes = false;
|
||||
}
|
||||
}
|
||||
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.
|
||||
// See: https://docs.rs/kdtree/0.6.0/kdtree/
|
||||
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)
|
||||
|
@ -2465,6 +2498,23 @@ impl App {
|
|||
}
|
||||
}
|
||||
}
|
||||
BottomWidgetType::Battery => {
|
||||
if let Some(battery_widget_state) = self
|
||||
.battery_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(tab_spacing) = &battery_widget_state.tab_click_locs {
|
||||
for (itx, ((tlc_x, tlc_y), (brc_x, brc_y))) in
|
||||
tab_spacing.iter().enumerate()
|
||||
{
|
||||
if (x >= *tlc_x && y >= *tlc_y) && (x <= *brc_x && y <= *brc_y) {
|
||||
battery_widget_state.currently_selected_battery_index = itx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,10 @@ pub struct AppScrollWidgetState {
|
|||
pub struct AppDeleteDialogState {
|
||||
pub is_showing_dd: bool,
|
||||
pub is_on_yes: bool, // Defaults to "No"
|
||||
pub yes_tlc: Option<(u16, u16)>,
|
||||
pub yes_brc: Option<(u16, u16)>,
|
||||
pub no_tlc: Option<(u16, u16)>,
|
||||
pub no_brc: Option<(u16, u16)>,
|
||||
}
|
||||
|
||||
pub struct AppHelpDialogState {
|
||||
|
@ -768,6 +772,7 @@ pub struct BasicTableWidgetState {
|
|||
#[derive(Default)]
|
||||
pub struct BatteryWidgetState {
|
||||
pub currently_selected_battery_index: usize,
|
||||
pub tab_click_locs: Option<Vec<((u16, u16), (u16, u16))>>,
|
||||
}
|
||||
|
||||
pub struct BatteryState {
|
||||
|
|
|
@ -214,6 +214,17 @@ impl Painter {
|
|||
widget.top_left_corner = None;
|
||||
widget.bottom_right_corner = None;
|
||||
}
|
||||
|
||||
// And reset dd_dialog...
|
||||
app_state.delete_dialog_state.yes_tlc = None;
|
||||
app_state.delete_dialog_state.yes_brc = None;
|
||||
app_state.delete_dialog_state.no_tlc = None;
|
||||
app_state.delete_dialog_state.no_brc = None;
|
||||
|
||||
// And battery dialog...
|
||||
for battery_widget in app_state.battery_state.widget_states.values_mut() {
|
||||
battery_widget.tab_click_locs = None;
|
||||
}
|
||||
}
|
||||
|
||||
terminal.autoresize()?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Rect},
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
terminal::Frame,
|
||||
widgets::{Block, Borders, Paragraph, Text},
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ pub trait KillDialog {
|
|||
fn get_dd_spans(&self, app_state: &App) -> Option<Vec<Text<'_>>>;
|
||||
|
||||
fn draw_dd_dialog<B: Backend>(
|
||||
&self, f: &mut Frame<'_, B>, dd_text: Option<Vec<Text<'_>>>, app_state: &App,
|
||||
&self, f: &mut Frame<'_, B>, dd_text: Option<Vec<Text<'_>>>, app_state: &mut App,
|
||||
draw_loc: Rect,
|
||||
) -> bool;
|
||||
}
|
||||
|
@ -34,35 +34,22 @@ impl KillDialog for Painter {
|
|||
if app_state.is_grouped(app_state.current_widget.widget_id) {
|
||||
if to_kill_processes.1.len() != 1 {
|
||||
Text::raw(format!(
|
||||
"Kill {} processes with the name \"{}\"?",
|
||||
"Kill {} processes with the name \"{}\"? Press ENTER to confirm.",
|
||||
to_kill_processes.1.len(),
|
||||
to_kill_processes.0
|
||||
))
|
||||
} else {
|
||||
Text::raw(format!(
|
||||
"Kill 1 process with the name \"{}\"?",
|
||||
"Kill 1 process with the name \"{}\"? Press ENTER to confirm.",
|
||||
to_kill_processes.0
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Text::raw(format!(
|
||||
"Kill process \"{}\" with PID {}?",
|
||||
"Kill process \"{}\" with PID {}? Press ENTER to confirm.",
|
||||
to_kill_processes.0, first_pid
|
||||
))
|
||||
},
|
||||
Text::raw("\n\n"),
|
||||
if app_state.delete_dialog_state.is_on_yes {
|
||||
Text::styled("Yes", self.colours.currently_selected_text_style)
|
||||
} else {
|
||||
Text::raw("Yes")
|
||||
},
|
||||
Text::raw(" "),
|
||||
if app_state.delete_dialog_state.is_on_yes {
|
||||
Text::raw("No")
|
||||
} else {
|
||||
Text::styled("No", self.colours.currently_selected_text_style)
|
||||
},
|
||||
Text::raw("\n"),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +58,7 @@ impl KillDialog for Painter {
|
|||
}
|
||||
|
||||
fn draw_dd_dialog<B: Backend>(
|
||||
&self, f: &mut Frame<'_, B>, dd_text: Option<Vec<Text<'_>>>, app_state: &App,
|
||||
&self, f: &mut Frame<'_, B>, dd_text: Option<Vec<Text<'_>>>, app_state: &mut App,
|
||||
draw_loc: Rect,
|
||||
) -> bool {
|
||||
if let Some(dd_text) = dd_text {
|
||||
|
@ -131,6 +118,75 @@ impl KillDialog for Painter {
|
|||
draw_loc,
|
||||
);
|
||||
|
||||
// Now draw buttons if needed...
|
||||
let split_draw_loc = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
if app_state.dd_err.is_some() {
|
||||
vec![Constraint::Percentage(100)]
|
||||
} else {
|
||||
vec![Constraint::Min(0), Constraint::Length(3)]
|
||||
}
|
||||
.as_ref(),
|
||||
)
|
||||
.split(draw_loc);
|
||||
|
||||
// This being true implies that dd_err is none.
|
||||
if let Some(button_draw_loc) = split_draw_loc.get(1) {
|
||||
let (yes_button, no_button) = if app_state.delete_dialog_state.is_on_yes {
|
||||
(
|
||||
Text::styled("Yes", self.colours.currently_selected_text_style),
|
||||
Text::raw("No"),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Text::raw("Yes"),
|
||||
Text::styled("No", self.colours.currently_selected_text_style),
|
||||
)
|
||||
};
|
||||
|
||||
let button_layout = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(35),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Percentage(35),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(*button_draw_loc);
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new([yes_button].iter())
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Right),
|
||||
button_layout[0],
|
||||
);
|
||||
f.render_widget(
|
||||
Paragraph::new([no_button].iter())
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Left),
|
||||
button_layout[2],
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
app_state.delete_dialog_state.yes_tlc =
|
||||
Some((button_layout[0].x, button_layout[0].y));
|
||||
app_state.delete_dialog_state.yes_brc = Some((
|
||||
button_layout[0].x + button_layout[0].width,
|
||||
button_layout[0].y + button_layout[0].height,
|
||||
));
|
||||
|
||||
app_state.delete_dialog_state.no_tlc =
|
||||
Some((button_layout[2].x, button_layout[2].y));
|
||||
app_state.delete_dialog_state.no_brc = Some((
|
||||
button_layout[2].x + button_layout[2].width,
|
||||
button_layout[2].y + button_layout[2].height,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if app_state.dd_err.is_some() {
|
||||
return app_state.delete_dialog_state.is_showing_dd;
|
||||
} else {
|
||||
|
|
|
@ -23,6 +23,7 @@ impl BatteryDisplayWidget for Painter {
|
|||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
|
||||
widget_id: u64,
|
||||
) {
|
||||
let should_get_widget_bounds = app_state.should_get_widget_bounds();
|
||||
if let Some(battery_widget_state) =
|
||||
app_state.battery_state.widget_states.get_mut(&widget_id)
|
||||
{
|
||||
|
@ -115,6 +116,18 @@ impl BatteryDisplayWidget for Painter {
|
|||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc)[0];
|
||||
|
||||
let tab_draw_loc = Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(2),
|
||||
Constraint::Min(0),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.direction(Direction::Vertical)
|
||||
.split(draw_loc)[1];
|
||||
|
||||
if let Some(battery_details) = app_state
|
||||
.canvas_data
|
||||
.battery_data
|
||||
|
@ -188,6 +201,13 @@ impl BatteryDisplayWidget for Painter {
|
|||
);
|
||||
}
|
||||
|
||||
let battery_names = app_state
|
||||
.canvas_data
|
||||
.battery_data
|
||||
.iter()
|
||||
.map(|battery| &battery.battery_name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Has to be placed AFTER for tui 0.9, place BEFORE for 0.10.
|
||||
f.render_widget(
|
||||
// Tabs::new(
|
||||
|
@ -198,26 +218,36 @@ impl BatteryDisplayWidget for Painter {
|
|||
// .map(|battery| Spans::from(battery.battery_name.clone())))
|
||||
// .collect::<Vec<_>>(),
|
||||
// )
|
||||
// FIXME: [MOUSE] Support mouse for the tabs?
|
||||
Tabs::default()
|
||||
.titles(
|
||||
app_state
|
||||
.canvas_data
|
||||
.battery_data
|
||||
.iter()
|
||||
.map(|battery| &battery.battery_name)
|
||||
.collect::<Vec<_>>()
|
||||
.as_ref(),
|
||||
)
|
||||
.block(battery_block)
|
||||
.titles(battery_names.as_ref())
|
||||
.block(Block::default())
|
||||
.divider(tui::symbols::line::VERTICAL)
|
||||
.style(self.colours.text_style)
|
||||
.highlight_style(self.colours.currently_selected_text_style)
|
||||
.select(battery_widget_state.currently_selected_battery_index),
|
||||
draw_loc,
|
||||
tab_draw_loc,
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
if should_get_widget_bounds {
|
||||
// Tab wizardry
|
||||
if !battery_names.is_empty() {
|
||||
let mut current_x = tab_draw_loc.x;
|
||||
let current_y = tab_draw_loc.y;
|
||||
let mut tab_click_locs: Vec<((u16, u16), (u16, u16))> = vec![];
|
||||
for battery in battery_names {
|
||||
// +1 because there's a space after the tab label.
|
||||
let width =
|
||||
unicode_width::UnicodeWidthStr::width(battery.as_str()) as u16 + 1;
|
||||
tab_click_locs
|
||||
.push(((current_x, current_y), (current_x + width, current_y)));
|
||||
|
||||
// +2 because we want to go one space past to get to the '|', then one MORE
|
||||
// to start at the blank space before the tab label.
|
||||
current_x = current_x + width + 2;
|
||||
}
|
||||
battery_widget_state.tab_click_locs = Some(tab_click_locs);
|
||||
}
|
||||
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||
|
|
|
@ -37,6 +37,7 @@ lazy_static! {
|
|||
tui::style::Style::default().fg(tui::style::Color::LightBlue);
|
||||
}
|
||||
|
||||
// FIXME: [HELP] I wanna update this before release... it's missing mouse too.
|
||||
// Help text
|
||||
pub const HELP_CONTENTS_TEXT: [&str; 8] = [
|
||||
"Press the corresponding numbers to jump to the section, or scroll:\n",
|
||||
|
|
Loading…
Reference in a new issue