From 55fed66f0b2d9db4b0c9c3672643be83b5294b44 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Tue, 12 Nov 2024 23:36:46 -0500 Subject: [PATCH 01/11] w.i.p. concept for more mouse interaction within the tui run fmt --- tui/src/state.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/tui/src/state.rs b/tui/src/state.rs index 7f96aee9..e2203d0a 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -13,7 +13,9 @@ use linutil_core::{ego_tree::NodeId, Config, ListNode, TabList}; #[cfg(feature = "tips")] use rand::Rng; use ratatui::{ - crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind}, + crossterm::event::{ + KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind, + }, layout::{Alignment, Constraint, Direction, Flex, Layout, Position, Rect}, style::{Style, Stylize}, text::{Line, Span, Text}, @@ -501,9 +503,30 @@ impl AppState { match event.kind { MouseEventKind::Moved => { if mouse_in_list { - self.focus = Focus::List + self.focus = Focus::List; + if let Some(areas) = &self.areas { + let relative_y = position.y.saturating_sub(areas.list.y + 1); + let list_len = self.filter.item_list().len(); + if relative_y < list_len as u16 { + self.selection.select(Some(relative_y as usize)); + } + } } else if mouse_in_tab_list { - self.focus = Focus::TabList + self.focus = Focus::TabList; + if let Some(areas) = &self.areas { + let relative_y = position.y.saturating_sub(areas.tab_list.y + 1); + if relative_y < self.tabs.len() as u16 { + self.current_tab.select(Some(relative_y as usize)); + self.refresh_tab(); + } + } + } + } + MouseEventKind::Down(MouseButton::Left) => { + if mouse_in_list { + self.handle_enter(); + } else if mouse_in_tab_list { + self.focus = Focus::List; } } MouseEventKind::ScrollDown => { @@ -513,7 +536,7 @@ impl AppState { } self.refresh_tab(); } else if mouse_in_list { - self.selection.select_next() + self.scroll_down(); } } MouseEventKind::ScrollUp => { @@ -523,7 +546,7 @@ impl AppState { } self.refresh_tab(); } else if mouse_in_list { - self.selection.select_previous() + self.scroll_up(); } } _ => {} From 961511fb72c39a29ab20783b1c0ea89973c9cb08 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Tue, 12 Nov 2024 23:41:20 -0500 Subject: [PATCH 02/11] improve mouse position calculation --- tui/src/state.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tui/src/state.rs b/tui/src/state.rs index e2203d0a..4ecc2c95 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -505,7 +505,8 @@ impl AppState { if mouse_in_list { self.focus = Focus::List; if let Some(areas) = &self.areas { - let relative_y = position.y.saturating_sub(areas.list.y + 1); + let list_start = areas.list.y + 4; + let relative_y = position.y.saturating_sub(list_start); let list_len = self.filter.item_list().len(); if relative_y < list_len as u16 { self.selection.select(Some(relative_y as usize)); From fbfcd5002c382a31ceb26f0431461a52cc3a4f26 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Tue, 12 Nov 2024 23:46:44 -0500 Subject: [PATCH 03/11] fix subdir bug --- tui/src/state.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tui/src/state.rs b/tui/src/state.rs index 4ecc2c95..7889e9a1 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -508,7 +508,12 @@ impl AppState { let list_start = areas.list.y + 4; let relative_y = position.y.saturating_sub(list_start); let list_len = self.filter.item_list().len(); - if relative_y < list_len as u16 { + let adjusted_len = if self.at_root() { + list_len + } else { + list_len + 1 + }; + if relative_y < adjusted_len as u16 { self.selection.select(Some(relative_y as usize)); } } From 6e84cb301b9cea96643e7b86fbada3749c81965f Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Tue, 12 Nov 2024 23:51:39 -0500 Subject: [PATCH 04/11] allow selection of search bar --- tui/src/state.rs | 53 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/tui/src/state.rs b/tui/src/state.rs index 7889e9a1..36e33b8f 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -495,14 +495,31 @@ impl AppState { return true; } - if matches!(self.focus, Focus::TabList | Focus::List) { + if matches!(self.focus, Focus::TabList | Focus::List | Focus::Search) { let position = Position::new(event.column, event.row); let mouse_in_tab_list = self.areas.as_ref().unwrap().tab_list.contains(position); let mouse_in_list = self.areas.as_ref().unwrap().list.contains(position); + let mouse_in_search = if let Some(areas) = &self.areas { + position.y >= areas.list.y + && position.y < areas.list.y + 3 + && position.x >= areas.list.x + && position.x < areas.list.x + areas.list.width + } else { + false + }; + match event.kind { MouseEventKind::Moved => { - if mouse_in_list { + if mouse_in_search { + if !matches!(self.focus, Focus::Search) { + self.focus = Focus::Search; + self.filter.activate_search(); + } + } else if mouse_in_list { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } self.focus = Focus::List; if let Some(areas) = &self.areas { let list_start = areas.list.y + 4; @@ -518,6 +535,9 @@ impl AppState { } } } else if mouse_in_tab_list { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } self.focus = Focus::TabList; if let Some(areas) = &self.areas { let relative_y = position.y.saturating_sub(areas.tab_list.y + 1); @@ -529,30 +549,35 @@ impl AppState { } } MouseEventKind::Down(MouseButton::Left) => { - if mouse_in_list { + if mouse_in_search { + self.enter_search(); + } else if mouse_in_list { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } self.handle_enter(); } else if mouse_in_tab_list { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } self.focus = Focus::List; } } - MouseEventKind::ScrollDown => { + MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } if mouse_in_tab_list { if self.current_tab.selected().unwrap() != self.tabs.len() - 1 { self.current_tab.select_next(); } self.refresh_tab(); } else if mouse_in_list { - self.scroll_down(); - } - } - MouseEventKind::ScrollUp => { - if mouse_in_tab_list { - if self.current_tab.selected().unwrap() != 0 { - self.current_tab.select_previous(); + if event.kind == MouseEventKind::ScrollDown { + self.scroll_down(); + } else { + self.scroll_up(); } - self.refresh_tab(); - } else if mouse_in_list { - self.scroll_up(); } } _ => {} From 684346272d643cee721e61abe08945b3e8c99318 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Wed, 13 Nov 2024 00:05:15 -0500 Subject: [PATCH 05/11] bring up description on scroll wheel pressdown, bring up script preview on Right Mouse Button pressdown --- tui/src/state.rs | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/tui/src/state.rs b/tui/src/state.rs index 36e33b8f..2da2eb69 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -548,21 +548,36 @@ impl AppState { } } } - MouseEventKind::Down(MouseButton::Left) => { - if mouse_in_search { - self.enter_search(); - } else if mouse_in_list { - if matches!(self.focus, Focus::Search) { - self.exit_search(); + MouseEventKind::Down(button) => match button { + MouseButton::Left => { + if mouse_in_search { + self.enter_search(); + } else if mouse_in_list { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } + self.handle_enter(); + } else if mouse_in_tab_list { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } + self.focus = Focus::List; } - self.handle_enter(); - } else if mouse_in_tab_list { - if matches!(self.focus, Focus::Search) { - self.exit_search(); - } - self.focus = Focus::List; } - } + MouseButton::Right if mouse_in_list => { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } + self.enable_preview(); + } + MouseButton::Middle if mouse_in_list => { + if matches!(self.focus, Focus::Search) { + self.exit_search(); + } + self.enable_description(); + } + _ => {} + }, MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => { if matches!(self.focus, Focus::Search) { self.exit_search(); From 6dae016104bba7657a0d3c4f8ec96f61495b956c Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Wed, 13 Nov 2024 00:11:01 -0500 Subject: [PATCH 06/11] add click and drag --- tui/src/floating_text.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tui/src/floating_text.rs b/tui/src/floating_text.rs index 6a2546cc..80274bfb 100644 --- a/tui/src/floating_text.rs +++ b/tui/src/floating_text.rs @@ -9,7 +9,7 @@ use crate::{float::FloatContent, hint::Shortcut}; use linutil_core::Command; use ratatui::{ - crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind}, + crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind}, layout::Rect, style::{Style, Stylize}, text::Line, @@ -33,6 +33,8 @@ pub struct FloatingText { mode_title: String, wrap_words: bool, frame_height: usize, + drag_start_y: Option, + drag_start_scroll: Option, } macro_rules! style { @@ -141,6 +143,8 @@ impl FloatingText { h_scroll: 0, wrap_words, frame_height: 0, + drag_start_y: None, + drag_start_scroll: None, } } @@ -165,6 +169,8 @@ impl FloatingText { v_scroll: 0, wrap_words: false, frame_height: 0, + drag_start_y: None, + drag_start_scroll: None, }) } @@ -206,6 +212,19 @@ impl FloatingText { }; } } + + fn handle_drag(&mut self, current_y: u16) { + if let (Some(start_y), Some(start_scroll)) = (self.drag_start_y, self.drag_start_scroll) { + let delta = start_y as i32 - current_y as i32; + let new_scroll = start_scroll as i32 + delta; + + let max_scroll = self + .wrapped_lines + .len() + .saturating_sub(self.frame_height.saturating_sub(2)); + self.v_scroll = new_scroll.clamp(0, max_scroll as i32) as usize; + } + } } impl FloatContent for FloatingText { @@ -285,6 +304,17 @@ impl FloatContent for FloatingText { fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool { match event.kind { + MouseEventKind::Down(MouseButton::Left) => { + self.drag_start_y = Some(event.row); + self.drag_start_scroll = Some(self.v_scroll); + } + MouseEventKind::Up(MouseButton::Left) => { + self.drag_start_y = None; + self.drag_start_scroll = None; + } + MouseEventKind::Drag(MouseButton::Left) => { + self.handle_drag(event.row); + } MouseEventKind::ScrollDown => self.scroll_down(), MouseEventKind::ScrollUp => self.scroll_up(), MouseEventKind::ScrollLeft => self.scroll_left(), From 93eb1196e8e0f49e5f231307e86e7f5a23be4ed7 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Wed, 13 Nov 2024 03:30:56 -0500 Subject: [PATCH 07/11] make right click close floating windows --- tui/src/float.rs | 9 +++++++-- tui/src/state.rs | 28 ++++++++++++++++++---------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/tui/src/float.rs b/tui/src/float.rs index 4d6ac006..af836175 100644 --- a/tui/src/float.rs +++ b/tui/src/float.rs @@ -4,6 +4,8 @@ use ratatui::{ Frame, }; +use crate::event::MouseButton; +use crate::event::MouseEventKind; use crate::hint::Shortcut; pub trait FloatContent { @@ -54,8 +56,11 @@ impl Float { self.content.draw(frame, popup_area); } - pub fn handle_mouse_event(&mut self, event: &MouseEvent) { - self.content.handle_mouse_event(event); + pub fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool { + match event.kind { + MouseEventKind::Down(MouseButton::Right) => true, + _ => self.content.handle_mouse_event(event), + } } // Returns true if the floating window is finished. diff --git a/tui/src/state.rs b/tui/src/state.rs index 2da2eb69..eaa582a7 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -495,6 +495,23 @@ impl AppState { return true; } + match &mut self.focus { + Focus::FloatingWindow(float) => { + if float.handle_mouse_event(event) { + self.focus = Focus::List; + } + return true; + } + Focus::ConfirmationPrompt(prompt) => { + if prompt.handle_mouse_event(event) { + self.focus = Focus::List; + self.selected_commands.clear(); + } + return true; + } + _ => {} + } + if matches!(self.focus, Focus::TabList | Focus::List | Focus::Search) { let position = Position::new(event.column, event.row); let mouse_in_tab_list = self.areas.as_ref().unwrap().tab_list.contains(position); @@ -561,7 +578,7 @@ impl AppState { if matches!(self.focus, Focus::Search) { self.exit_search(); } - self.focus = Focus::List; + self.focus = Focus::TabList; } } MouseButton::Right if mouse_in_list => { @@ -598,15 +615,6 @@ impl AppState { _ => {} } } - match &mut self.focus { - Focus::FloatingWindow(float) => { - float.content.handle_mouse_event(event); - } - Focus::ConfirmationPrompt(confirm) => { - confirm.content.handle_mouse_event(event); - } - _ => {} - } true } From d1baa4483382713cfa2acd3a42a7957cfe428925 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Wed, 13 Nov 2024 03:38:28 -0500 Subject: [PATCH 08/11] combine crates --- tui/src/float.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tui/src/float.rs b/tui/src/float.rs index af836175..2d235df3 100644 --- a/tui/src/float.rs +++ b/tui/src/float.rs @@ -4,9 +4,7 @@ use ratatui::{ Frame, }; -use crate::event::MouseButton; -use crate::event::MouseEventKind; -use crate::hint::Shortcut; +use crate::{event::MouseButton, event::MouseEventKind, hint::Shortcut}; pub trait FloatContent { fn draw(&mut self, frame: &mut Frame, area: Rect); From 692bff1627727fc9fdd93540f1c5aa45eb43c6d7 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Wed, 13 Nov 2024 03:43:43 -0500 Subject: [PATCH 09/11] w.i.p. left click confirmation --- tui/src/confirmation.rs | 11 ++++++++--- tui/src/state.rs | 25 +++++++++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/tui/src/confirmation.rs b/tui/src/confirmation.rs index 2ed5898b..4b0c16f3 100644 --- a/tui/src/confirmation.rs +++ b/tui/src/confirmation.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use crate::{float::FloatContent, hint::Shortcut}; use ratatui::{ - crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind}, + crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind}, layout::Alignment, prelude::*, widgets::{Block, Borders, Clear, List}, @@ -87,15 +87,20 @@ impl FloatContent for ConfirmPrompt { fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool { match event.kind { + MouseEventKind::Down(MouseButton::Left) => { + self.status = ConfirmStatus::Confirm; + true + } MouseEventKind::ScrollDown => { self.scroll_down(); + false } MouseEventKind::ScrollUp => { self.scroll_up(); + false } - _ => {} + _ => false, } - false } fn handle_key_event(&mut self, key: &KeyEvent) -> bool { diff --git a/tui/src/state.rs b/tui/src/state.rs index eaa582a7..7b02d405 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -483,7 +483,7 @@ impl AppState { match &mut self.focus { Focus::FloatingWindow(float) => float.draw(frame, chunks[1]), - Focus::ConfirmationPrompt(prompt) => prompt.draw(frame, chunks[1]), + Focus::ConfirmationPrompt(confirm) => confirm.draw(frame, chunks[1]), _ => {} } @@ -502,12 +502,25 @@ impl AppState { } return true; } - Focus::ConfirmationPrompt(prompt) => { - if prompt.handle_mouse_event(event) { - self.focus = Focus::List; - self.selected_commands.clear(); + Focus::ConfirmationPrompt(confirm) => { + if confirm.handle_mouse_event(event) { + match confirm.content.status { + ConfirmStatus::Abort => { + self.focus = Focus::List; + if !self.multi_select { + self.selected_commands.clear() + } else { + if let Some(node) = self.get_selected_node() { + if !node.multi_select { + self.selected_commands.retain(|cmd| cmd.name != node.name); + } + } + } + } + ConfirmStatus::Confirm => self.handle_confirm_command(), + ConfirmStatus::None => {} + } } - return true; } _ => {} } From a8343155e5c2803f314175429827ca2d6947a0ac Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Wed, 13 Nov 2024 03:46:00 -0500 Subject: [PATCH 10/11] change to false run clippy --- tui/src/running_command.rs | 2 +- tui/src/state.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tui/src/running_command.rs b/tui/src/running_command.rs index d45c37c5..02900aae 100644 --- a/tui/src/running_command.rs +++ b/tui/src/running_command.rs @@ -113,7 +113,7 @@ impl FloatContent for RunningCommand { } _ => {} } - true + false } /// Handle key events of the running command "window". Returns true when the "window" should be /// closed diff --git a/tui/src/state.rs b/tui/src/state.rs index 7b02d405..2e56e13a 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -509,11 +509,9 @@ impl AppState { self.focus = Focus::List; if !self.multi_select { self.selected_commands.clear() - } else { - if let Some(node) = self.get_selected_node() { - if !node.multi_select { - self.selected_commands.retain(|cmd| cmd.name != node.name); - } + } else if let Some(node) = self.get_selected_node() { + if !node.multi_select { + self.selected_commands.retain(|cmd| cmd.name != node.name); } } } From eeb501401a0c3e550fc338177b56f12a52f2ada7 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Wed, 13 Nov 2024 04:00:37 -0500 Subject: [PATCH 11/11] fix confirmation prompt closing via rmb --- tui/src/confirmation.rs | 4 ++++ tui/src/float.rs | 5 ++++- tui/src/state.rs | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tui/src/confirmation.rs b/tui/src/confirmation.rs index 4b0c16f3..128aa43c 100644 --- a/tui/src/confirmation.rs +++ b/tui/src/confirmation.rs @@ -91,6 +91,10 @@ impl FloatContent for ConfirmPrompt { self.status = ConfirmStatus::Confirm; true } + MouseEventKind::Down(MouseButton::Right) => { + self.status = ConfirmStatus::Abort; + false + } MouseEventKind::ScrollDown => { self.scroll_down(); false diff --git a/tui/src/float.rs b/tui/src/float.rs index 2d235df3..afe32603 100644 --- a/tui/src/float.rs +++ b/tui/src/float.rs @@ -56,7 +56,10 @@ impl Float { pub fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool { match event.kind { - MouseEventKind::Down(MouseButton::Right) => true, + MouseEventKind::Down(MouseButton::Right) => { + self.content.handle_mouse_event(event); + true + } _ => self.content.handle_mouse_event(event), } } diff --git a/tui/src/state.rs b/tui/src/state.rs index 2e56e13a..0a53f4c4 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -519,6 +519,7 @@ impl AppState { ConfirmStatus::None => {} } } + return true; } _ => {} }