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] 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 )