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

feat: Mouse scroll #913

Merged
merged 8 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion tui/src/confirmation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*,
Expand Down Expand Up @@ -84,6 +84,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 {
Expand Down
7 changes: 6 additions & 1 deletion tui/src/float.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crossterm::event::{KeyCode, KeyEvent};
use crossterm::event::{KeyCode, KeyEvent, MouseEvent};
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
Frame,
Expand All @@ -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]>);
}
Expand Down Expand Up @@ -53,6 +54,10 @@ 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);
}

// Returns true if the floating window is finished.
pub fn handle_key_event(&mut self, key: &KeyEvent) -> bool {
match key.code {
Expand Down
13 changes: 12 additions & 1 deletion tui/src/floating_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -264,6 +264,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 {
Expand Down
25 changes: 17 additions & 8 deletions tui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()?;
Expand Down Expand Up @@ -70,15 +72,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(());
}
}
_ => {}
}
}
}
14 changes: 13 additions & 1 deletion tui/src/running_command.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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 {
Expand Down
69 changes: 67 additions & 2 deletions tui/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ 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")]
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},
Expand Down Expand Up @@ -62,6 +62,7 @@ pub struct AppState {
drawable: bool,
#[cfg(feature = "tips")]
tip: String,
areas: Option<Areas>,
}

pub enum Focus {
Expand All @@ -78,6 +79,11 @@ pub struct ListEntry {
pub has_children: bool,
}

pub struct Areas {
tab_list: Rect,
list: Rect,
}

enum SelectedItem {
UpDir,
Directory,
Expand All @@ -104,6 +110,7 @@ impl AppState {
drawable: false,
#[cfg(feature = "tips")]
tip: get_random_tip(),
areas: None,
};

state.update_items();
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -416,6 +428,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 )
Expand Down