diff --git a/tui/src/confirmation.rs b/tui/src/confirmation.rs index 96ab06ca..2ed5898b 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}, + crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind}, layout::Alignment, prelude::*, widgets::{Block, Borders, Clear, List}, @@ -85,6 +85,19 @@ 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(); + } + MouseEventKind::ScrollUp => { + self.scroll_up(); + } + _ => {} + } + 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 993684b0..4d6ac006 100644 --- a/tui/src/float.rs +++ b/tui/src/float.rs @@ -1,5 +1,5 @@ use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, + crossterm::event::{KeyCode, KeyEvent, MouseEvent}, 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 c307b854..6a2546cc 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}, + crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind}, layout::Rect, style::{Style, Stylize}, text::Line, @@ -283,6 +283,17 @@ 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(), + MouseEventKind::ScrollLeft => self.scroll_left(), + MouseEventKind::ScrollRight => self.scroll_right(), + _ => {} + } + 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 7a9f4067..a47819ca 100644 --- a/tui/src/main.rs +++ b/tui/src/main.rs @@ -19,7 +19,7 @@ use clap::Parser; use ratatui::{ backend::CrosstermBackend, crossterm::{ - event::{self, DisableMouseCapture, Event, KeyEventKind}, + event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyEventKind}, style::ResetColor, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, @@ -63,6 +63,8 @@ fn main() -> io::Result<()> { ); stdout().execute(EnterAlternateScreen)?; + stdout().execute(EnableMouseCapture)?; + enable_raw_mode()?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; terminal.clear()?; @@ -92,15 +94,22 @@ 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 { - continue; - } + match event::read()? { + Event::Key(key) => { + if key.kind != KeyEventKind::Press && key.kind != KeyEventKind::Repeat { + continue; + } - if !state.handle_key(&key) { - return Ok(()); + if !state.handle_key(&key) { + return Ok(()); + } } + Event::Mouse(mouse_event) => { + if !state.handle_mouse(&mouse_event) { + return Ok(()); + } + } + _ => {} } } } diff --git a/tui/src/running_command.rs b/tui/src/running_command.rs index 779a0b3c..c3b3d3d4 100644 --- a/tui/src/running_command.rs +++ b/tui/src/running_command.rs @@ -5,7 +5,7 @@ use portable_pty::{ ChildKiller, CommandBuilder, ExitStatus, MasterPty, NativePtySystem, PtySize, PtySystem, }; use ratatui::{ - crossterm::event::{KeyCode, KeyEvent, KeyModifiers}, + crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}, layout::{Rect, Size}, style::{Color, Style, Stylize}, text::{Line, Span}, @@ -104,6 +104,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 5ee34079..dbd13a1d 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -12,8 +12,8 @@ use linutil_core::{ego_tree::NodeId, Config, ListNode, TabList}; #[cfg(feature = "tips")] use rand::Rng; use ratatui::{ - crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, - layout::{Alignment, Constraint, Direction, Flex, Layout}, + crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind}, + layout::{Alignment, Constraint, Direction, Flex, Layout, Position, Rect}, style::{Style, Stylize}, text::{Line, Span, Text}, widgets::{Block, Borders, List, ListState, Paragraph}, @@ -41,6 +41,8 @@ P* - privileged * "; pub struct AppState { + /// Areas of tabs + areas: Option, /// Selected theme theme: Theme, /// Currently focused area @@ -79,6 +81,11 @@ pub struct ListEntry { pub has_children: bool, } +pub struct Areas { + tab_list: Rect, + list: Rect, +} + enum SelectedItem { UpDir, Directory, @@ -100,6 +107,7 @@ impl AppState { let auto_execute_commands = config_path.map(|path| Config::from_file(&path).auto_execute); let mut state = Self { + areas: None, theme, focus: Focus::List, tabs, @@ -315,6 +323,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() @@ -469,6 +482,59 @@ impl AppState { frame.render_widget(keybind_para, vertical[1]); } + pub fn handle_mouse(&mut self, event: &MouseEvent) -> bool { + if !self.drawable { + return true; + } + + 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() + } + } + MouseEventKind::ScrollUp => { + 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() + } + } + _ => {} + } + } + match &mut self.focus { + 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 )