From ee090dd31f90a75655ef5d1642db6fcc54366a5e Mon Sep 17 00:00:00 2001 From: Jeevitha Kannan K S Date: Tue, 22 Oct 2024 10:57:52 +0530 Subject: [PATCH 1/6] feat: add mouse scrolling --- core/tabs/utils/bluetooth-control.sh | 6 ++--- tui/src/confirmation.rs | 17 +++++++++++++- tui/src/float.rs | 7 +++++- tui/src/floating_text.rs | 11 ++++++++- tui/src/main.rs | 26 ++++++++++++++------- tui/src/running_command.rs | 14 ++++++++++- tui/src/state.rs | 35 +++++++++++++++++++++++++++- 7 files changed, 100 insertions(+), 16 deletions(-) diff --git a/core/tabs/utils/bluetooth-control.sh b/core/tabs/utils/bluetooth-control.sh index da7ee23d..261eccf8 100644 --- a/core/tabs/utils/bluetooth-control.sh +++ b/core/tabs/utils/bluetooth-control.sh @@ -32,7 +32,7 @@ setupBluetooth() { # Function to display the main menu main_menu() { while true; do - clear + printf "%b\n" "${YELLOW}Bluetooth Manager${RC}" printf "%b\n" "${YELLOW}=================${RC}" printf "%b\n" "1. Scan for devices" @@ -58,7 +58,7 @@ main_menu() { # Function to scan for devices scan_devices() { - clear + printf "%b\n" "${YELLOW}Scanning for devices...${RC}" bluetoothctl --timeout 10 scan on devices=$(bluetoothctl devices) @@ -81,7 +81,7 @@ prompt_for_mac() { failure_msg=$5 while true; do - clear + devices=$(bluetoothctl devices) if [ -z "$devices" ]; then printf "%b\n" "${RED}No devices available. Please scan for devices first.${RC}" diff --git a/tui/src/confirmation.rs b/tui/src/confirmation.rs index 28732e35..82f652d1 100644 --- a/tui/src/confirmation.rs +++ b/tui/src/confirmation.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use crate::{float::FloatContent, hint::Shortcut}; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::{ layout::Alignment, prelude::*, @@ -84,6 +84,21 @@ impl FloatContent for ConfirmPrompt { frame.render_widget(List::new(paths_text), inner_area); } + fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool { + match event.kind { + MouseEventKind::ScrollDown => { + self.scroll_down(); + ConfirmStatus::None + } + MouseEventKind::ScrollUp => { + self.scroll_up(); + ConfirmStatus::None + } + _ => ConfirmStatus::None, + }; + false + } + fn handle_key_event(&mut self, key: &KeyEvent) -> bool { use KeyCode::*; self.status = match key.code { diff --git a/tui/src/float.rs b/tui/src/float.rs index 7b569752..52dd215b 100644 --- a/tui/src/float.rs +++ b/tui/src/float.rs @@ -1,4 +1,4 @@ -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{KeyCode, KeyEvent, MouseEvent}; use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, Frame, @@ -9,6 +9,7 @@ use crate::hint::Shortcut; pub trait FloatContent { fn draw(&mut self, frame: &mut Frame, area: Rect); fn handle_key_event(&mut self, key: &KeyEvent) -> bool; + fn handle_mouse_event(&mut self, key: &MouseEvent) -> bool; fn is_finished(&self) -> bool; fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>); } @@ -53,6 +54,10 @@ impl Float { self.content.draw(frame, popup_area); } + pub fn handle_mouse_event(&mut self, event: &MouseEvent) { + self.content.handle_mouse_event(event); + } + // Returns true if the floating window is finished. pub fn handle_key_event(&mut self, key: &KeyEvent) -> bool { match key.code { diff --git a/tui/src/floating_text.rs b/tui/src/floating_text.rs index 879fcbc5..d0f0923b 100644 --- a/tui/src/floating_text.rs +++ b/tui/src/floating_text.rs @@ -8,7 +8,7 @@ use crate::{float::FloatContent, hint::Shortcut}; use linutil_core::Command; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::{ layout::Rect, @@ -260,6 +260,15 @@ impl FloatContent for FloatingText { frame.render_widget(list, inner_area); } + fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool { + match event.kind { + MouseEventKind::ScrollDown => self.scroll_down(), + MouseEventKind::ScrollUp => self.scroll_up(), + _ => {} + } + false + } + fn handle_key_event(&mut self, key: &KeyEvent) -> bool { use KeyCode::*; match key.code { diff --git a/tui/src/main.rs b/tui/src/main.rs index 801e3b1d..04b749ab 100644 --- a/tui/src/main.rs +++ b/tui/src/main.rs @@ -15,7 +15,7 @@ use std::{ use crate::theme::Theme; use clap::Parser; use crossterm::{ - event::{self, DisableMouseCapture, Event, KeyEventKind}, + event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyEventKind}, style::ResetColor, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, @@ -41,6 +41,8 @@ fn main() -> io::Result<()> { let mut state = AppState::new(args.theme, args.override_validation); stdout().execute(EnterAlternateScreen)?; + stdout().execute(EnableMouseCapture)?; + enable_raw_mode()?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; terminal.clear()?; @@ -70,15 +72,23 @@ fn run( // It's guaranteed that the `read()` won't block when the `poll()` // function returns `true` - if let Event::Key(key) = event::read()? { - // We are only interested in Press and Repeat events - if key.kind != KeyEventKind::Press && key.kind != KeyEventKind::Repeat { + match event::read()? { + Event::Key(key) => { + if key.kind != KeyEventKind::Press && key.kind != KeyEventKind::Repeat { + continue; + } + + if !state.handle_key(&key) { + return Ok(()); + } + } + Event::Mouse(mouse_event) => { + if !state.handle_mouse(&mouse_event) { + return Ok(()); + } continue; } - - if !state.handle_key(&key) { - return Ok(()); - } + _ => {} } } } diff --git a/tui/src/running_command.rs b/tui/src/running_command.rs index 89daa755..5e1c35bd 100644 --- a/tui/src/running_command.rs +++ b/tui/src/running_command.rs @@ -1,5 +1,5 @@ use crate::{float::FloatContent, hint::Shortcut}; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; use linutil_core::Command; use oneshot::{channel, Receiver}; use portable_pty::{ @@ -91,6 +91,18 @@ impl FloatContent for RunningCommand { frame.render_widget(pseudo_term, area); } + fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool { + match event.kind { + MouseEventKind::ScrollUp => { + self.scroll_offset = self.scroll_offset.saturating_add(1); + } + MouseEventKind::ScrollDown => { + self.scroll_offset = self.scroll_offset.saturating_sub(1); + } + _ => {} + } + true + } /// Handle key events of the running command "window". Returns true when the "window" should be /// closed fn handle_key_event(&mut self, key: &KeyEvent) -> bool { diff --git a/tui/src/state.rs b/tui/src/state.rs index 9ed61771..4307253e 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -7,7 +7,7 @@ use crate::{ running_command::RunningCommand, theme::Theme, }; -use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; +use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind}; use ego_tree::NodeId; use linutil_core::{ListNode, Tab}; #[cfg(feature = "tips")] @@ -406,6 +406,39 @@ impl AppState { frame.render_widget(keybind_para, vertical[1]); } + pub fn handle_mouse(&mut self, event: &MouseEvent) -> bool { + if !self.drawable { + return true; + } + + match &mut self.focus { + Focus::TabList => match event.kind { + MouseEventKind::ScrollDown => { + self.current_tab.select_next(); + self.refresh_tab(); + } + MouseEventKind::ScrollUp => { + self.current_tab.select_next(); + self.refresh_tab(); + } + _ => {} + }, + Focus::List => match event.kind { + MouseEventKind::ScrollDown => self.selection.select_next(), + MouseEventKind::ScrollUp => self.selection.select_previous(), + _ => {} + }, + Focus::FloatingWindow(float) => { + float.content.handle_mouse_event(event); + } + Focus::ConfirmationPrompt(confirm) => { + confirm.content.handle_mouse_event(event); + } + _ => {} + } + true + } + pub fn handle_key(&mut self, key: &KeyEvent) -> bool { // This should be defined first to allow closing // the application even when not drawable ( If terminal is small ) From a6e948f1c9ef81504996aa0e9ecea113996d2fdc Mon Sep 17 00:00:00 2001 From: JEEVITHA KANNAN K S Date: Mon, 4 Nov 2024 12:20:47 +0530 Subject: [PATCH 2/6] revert: bluetooth-manager.sh --- core/tabs/utils/bluetooth-control.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tabs/utils/bluetooth-control.sh b/core/tabs/utils/bluetooth-control.sh index 261eccf8..da7ee23d 100644 --- a/core/tabs/utils/bluetooth-control.sh +++ b/core/tabs/utils/bluetooth-control.sh @@ -32,7 +32,7 @@ setupBluetooth() { # Function to display the main menu main_menu() { while true; do - + clear printf "%b\n" "${YELLOW}Bluetooth Manager${RC}" printf "%b\n" "${YELLOW}=================${RC}" printf "%b\n" "1. Scan for devices" @@ -58,7 +58,7 @@ main_menu() { # Function to scan for devices scan_devices() { - + clear printf "%b\n" "${YELLOW}Scanning for devices...${RC}" bluetoothctl --timeout 10 scan on devices=$(bluetoothctl devices) @@ -81,7 +81,7 @@ prompt_for_mac() { failure_msg=$5 while true; do - + clear devices=$(bluetoothctl devices) if [ -z "$devices" ]; then printf "%b\n" "${RED}No devices available. Please scan for devices first.${RC}" From 654cd615cfd9f1e13d29bb85b9a8f04d7ddd2431 Mon Sep 17 00:00:00 2001 From: JEEVITHA KANNAN K S Date: Mon, 4 Nov 2024 12:36:14 +0530 Subject: [PATCH 3/6] feat: mouse hori scrolls --- tui/src/floating_text.rs | 2 ++ tui/src/state.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tui/src/floating_text.rs b/tui/src/floating_text.rs index 12e0e197..bb1c9744 100644 --- a/tui/src/floating_text.rs +++ b/tui/src/floating_text.rs @@ -268,6 +268,8 @@ impl FloatContent for FloatingText { match event.kind { MouseEventKind::ScrollDown => self.scroll_down(), MouseEventKind::ScrollUp => self.scroll_up(), + MouseEventKind::ScrollLeft => self.scroll_left(), + MouseEventKind::ScrollRight => self.scroll_right(), _ => {} } false diff --git a/tui/src/state.rs b/tui/src/state.rs index b5ff9377..b7934feb 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -424,18 +424,24 @@ impl AppState { match &mut self.focus { Focus::TabList => match event.kind { MouseEventKind::ScrollDown => { - self.current_tab.select_next(); + if self.current_tab.selected().unwrap() != self.tabs.len() - 1 { + self.current_tab.select_next(); + } self.refresh_tab(); } MouseEventKind::ScrollUp => { - self.current_tab.select_next(); + if self.current_tab.selected().unwrap() != 0 { + self.current_tab.select_previous(); + } self.refresh_tab(); } + MouseEventKind::ScrollRight => self.focus = Focus::List, _ => {} }, Focus::List => match event.kind { MouseEventKind::ScrollDown => self.selection.select_next(), MouseEventKind::ScrollUp => self.selection.select_previous(), + MouseEventKind::ScrollLeft => self.focus = Focus::TabList, _ => {} }, Focus::FloatingWindow(float) => { From bb974b27015161e292fada5c1ae93a24d44de359 Mon Sep 17 00:00:00 2001 From: Jeevitha Kannan K S Date: Tue, 5 Nov 2024 14:14:30 +0530 Subject: [PATCH 4/6] Update tui/src/main.rs Co-authored-by: Liam <33645555+lj3954@users.noreply.github.com> --- tui/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tui/src/main.rs b/tui/src/main.rs index 04b749ab..3ce29cd3 100644 --- a/tui/src/main.rs +++ b/tui/src/main.rs @@ -86,7 +86,6 @@ fn run( if !state.handle_mouse(&mouse_event) { return Ok(()); } - continue; } _ => {} } From 5e4e336effbee73858b2d90704a2500958b40acf Mon Sep 17 00:00:00 2001 From: Jeevitha Kannan K S Date: Tue, 5 Nov 2024 14:19:59 +0530 Subject: [PATCH 5/6] Update tui/src/confirmation.rs Co-authored-by: Liam <33645555+lj3954@users.noreply.github.com> --- tui/src/confirmation.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tui/src/confirmation.rs b/tui/src/confirmation.rs index 1a4a8710..4a2a5c1a 100644 --- a/tui/src/confirmation.rs +++ b/tui/src/confirmation.rs @@ -88,14 +88,12 @@ impl FloatContent for ConfirmPrompt { match event.kind { MouseEventKind::ScrollDown => { self.scroll_down(); - ConfirmStatus::None } MouseEventKind::ScrollUp => { self.scroll_up(); - ConfirmStatus::None } - _ => ConfirmStatus::None, - }; + _ => {} + } false } From 3a0717d267a1fb580bf276a2858a2b9fc8d0dae1 Mon Sep 17 00:00:00 2001 From: Jeevitha Kannan K S Date: Tue, 5 Nov 2024 18:35:32 +0530 Subject: [PATCH 6/6] feat: implemetation of mouse position Suggested by Liam (<33645555+lj3954@users.noreply.github.com>) --- tui/src/state.rs | 62 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/tui/src/state.rs b/tui/src/state.rs index b7934feb..1286fbbb 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -13,7 +13,7 @@ use linutil_core::{ListNode, Tab}; #[cfg(feature = "tips")] use rand::Rng; use ratatui::{ - layout::{Alignment, Constraint, Direction, Flex, Layout}, + layout::{Alignment, Constraint, Direction, Flex, Layout, Position, Rect}, style::{Style, Stylize}, text::{Line, Span, Text}, widgets::{Block, Borders, List, ListState, Paragraph}, @@ -62,6 +62,7 @@ pub struct AppState { drawable: bool, #[cfg(feature = "tips")] tip: String, + areas: Option, } pub enum Focus { @@ -78,6 +79,11 @@ pub struct ListEntry { pub has_children: bool, } +pub struct Areas { + tab_list: Rect, + list: Rect, +} + enum SelectedItem { UpDir, Directory, @@ -104,6 +110,7 @@ impl AppState { drawable: false, #[cfg(feature = "tips")] tip: get_random_tip(), + areas: None, }; state.update_items(); @@ -282,6 +289,11 @@ impl AppState { .split(horizontal[0]); frame.render_widget(label, left_chunks[0]); + self.areas = Some(Areas { + tab_list: left_chunks[1], + list: horizontal[1], + }); + let tabs = self .tabs .iter() @@ -421,29 +433,43 @@ impl AppState { return true; } - match &mut self.focus { - Focus::TabList => match event.kind { - MouseEventKind::ScrollDown => { - if self.current_tab.selected().unwrap() != self.tabs.len() - 1 { - self.current_tab.select_next(); + if matches!(self.focus, Focus::TabList | Focus::List) { + 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); + + match event.kind { + MouseEventKind::Moved => { + if mouse_in_list { + self.focus = Focus::List + } else if mouse_in_tab_list { + self.focus = Focus::TabList + } + } + MouseEventKind::ScrollDown => { + 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.selection.select_next() } - self.refresh_tab(); } MouseEventKind::ScrollUp => { - if self.current_tab.selected().unwrap() != 0 { - self.current_tab.select_previous(); + if mouse_in_tab_list { + if self.current_tab.selected().unwrap() != 0 { + self.current_tab.select_previous(); + } + self.refresh_tab(); + } else if mouse_in_list { + self.selection.select_previous() } - self.refresh_tab(); } - MouseEventKind::ScrollRight => self.focus = Focus::List, _ => {} - }, - Focus::List => match event.kind { - MouseEventKind::ScrollDown => self.selection.select_next(), - MouseEventKind::ScrollUp => self.selection.select_previous(), - MouseEventKind::ScrollLeft => self.focus = Focus::TabList, - _ => {} - }, + } + } + match &mut self.focus { Focus::FloatingWindow(float) => { float.content.handle_mouse_event(event); }