Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement a greater extent of mouse interaction within the tui #946

Closed
15 changes: 12 additions & 3 deletions tui/src/confirmation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -87,15 +87,24 @@ 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::Down(MouseButton::Right) => {
self.status = ConfirmStatus::Abort;
false
}
MouseEventKind::ScrollDown => {
self.scroll_down();
false
}
MouseEventKind::ScrollUp => {
self.scroll_up();
false
}
_ => {}
_ => false,
}
false
}

fn handle_key_event(&mut self, key: &KeyEvent) -> bool {
Expand Down
12 changes: 9 additions & 3 deletions tui/src/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ratatui::{
Frame,
};

use crate::hint::Shortcut;
use crate::{event::MouseButton, event::MouseEventKind, hint::Shortcut};

pub trait FloatContent {
fn draw(&mut self, frame: &mut Frame, area: Rect);
Expand Down Expand Up @@ -54,8 +54,14 @@ impl<Content: FloatContent + ?Sized> Float<Content> {
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) => {
self.content.handle_mouse_event(event);
true
}
_ => self.content.handle_mouse_event(event),
}
}

// Returns true if the floating window is finished.
Expand Down
32 changes: 31 additions & 1 deletion tui/src/floating_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -33,6 +33,8 @@ pub struct FloatingText {
mode_title: String,
wrap_words: bool,
frame_height: usize,
drag_start_y: Option<u16>,
drag_start_scroll: Option<usize>,
}

macro_rules! style {
Expand Down Expand Up @@ -141,6 +143,8 @@ impl FloatingText {
h_scroll: 0,
wrap_words,
frame_height: 0,
drag_start_y: None,
drag_start_scroll: None,
}
}

Expand All @@ -165,6 +169,8 @@ impl FloatingText {
v_scroll: 0,
wrap_words: false,
frame_height: 0,
drag_start_y: None,
drag_start_scroll: None,
})
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(),
Expand Down
2 changes: 1 addition & 1 deletion tui/src/running_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
141 changes: 115 additions & 26 deletions tui/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -481,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]),
_ => {}
}

Expand All @@ -493,51 +495,138 @@ impl AppState {
return true;
}

if matches!(self.focus, Focus::TabList | Focus::List) {
match &mut self.focus {
Focus::FloatingWindow(float) => {
if float.handle_mouse_event(event) {
self.focus = Focus::List;
}
return true;
}
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;
}
_ => {}
}

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 {
self.focus = Focus::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;
let relative_y = position.y.saturating_sub(list_start);
let list_len = self.filter.item_list().len();
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));
}
}
} else if mouse_in_tab_list {
self.focus = Focus::TabList
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);
if relative_y < self.tabs.len() as u16 {
self.current_tab.select(Some(relative_y as usize));
self.refresh_tab();
}
}
}
}
MouseEventKind::ScrollDown => {
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::TabList;
}
}
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();
}
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();
if event.kind == MouseEventKind::ScrollDown {
self.scroll_down();
} else {
self.scroll_up();
}
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
}

Expand Down
Loading