From 7070c30ce005762c230511450bc02cb0271e692b Mon Sep 17 00:00:00 2001 From: Takumasa Sakao Date: Thu, 29 Aug 2024 09:51:40 +0900 Subject: [PATCH] Support scrolling by mouse --- src/action.rs | 3 ++- src/app.rs | 9 ++++++--- src/components/execution_result.rs | 23 ++++++++++++++++++++++- src/components/history.rs | 21 ++++++++++++++++++++- src/utils.rs | 5 +++++ 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/action.rs b/src/action.rs index 6bd70dc..189ad91 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,7 +1,7 @@ use std::{fmt, string::ToString}; use chrono::{DateTime, Local}; -use crossterm::event::KeyEvent; +use crossterm::event::{KeyEvent, MouseEvent}; use serde::{ de::{self, Deserializer, Visitor}, Deserialize, Serialize, @@ -25,6 +25,7 @@ pub enum Action { Resume, Quit, Refresh, + MouseEvent(MouseEvent), Error(String), Help, StartExecution(ExecutionId, DateTime), diff --git a/src/app.rs b/src/app.rs index c090381..e067939 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anstyle::{Color, RgbColor, Style}; use chrono::Duration; use color_eyre::{eyre::Result, owo_colors::OwoColorize}; -use crossterm::event::{Event, KeyEvent}; +use crossterm::event::{Event, KeyEvent, MouseEvent}; use ratatui::{prelude::Rect, widgets::Block}; use serde::{Deserialize, Serialize}; use tokio::{ @@ -211,7 +211,7 @@ impl App { let mut tui = tui::Tui::new()? .tick_rate(self.tick_rate) .frame_rate(self.frame_rate); - // tui.mouse(true); + tui = tui.mouse(true); tui.enter()?; for component in self.components.iter_mut() { @@ -229,6 +229,9 @@ impl App { loop { if let Some(e) = tui.next().await { match e { + tui::Event::Mouse(me) => { + action_tx.send(Action::MouseEvent(me))?; + } tui::Event::Quit => action_tx.send(Action::Quit)?, tui::Event::Tick => action_tx.send(Action::Tick)?, tui::Event::Render => action_tx.send(Action::Render)?, @@ -498,7 +501,7 @@ impl App { tui = tui::Tui::new()? .tick_rate(self.tick_rate) .frame_rate(self.frame_rate); - // tui.mouse(true); + tui = tui.mouse(true); tui.enter()?; } else if self.should_quit { tui.stop()?; diff --git a/src/components/execution_result.rs b/src/components/execution_result.rs index 13f4825..d5deabc 100644 --- a/src/components/execution_result.rs +++ b/src/components/execution_result.rs @@ -4,7 +4,7 @@ use ansi_parser::{AnsiParser, AnsiSequence, Output}; use ansi_to_tui::IntoText; use chrono::{DateTime, Local}; use color_eyre::eyre::Result; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::{prelude::*, widgets::*}; use serde::{Deserialize, Serialize}; use symbols::scrollbar; @@ -17,6 +17,7 @@ use crate::{ action::Action, config::{Config, KeyBindings, RuntimeConfig}, termtext::{Char, Text}, + utils::is_in_area, }; pub struct ExecutionResult { @@ -33,6 +34,8 @@ pub struct ExecutionResult { y_area_size: u16, y_max_scroll_size: u16, fold: bool, + + rect: Rect, } impl ExecutionResult { @@ -49,6 +52,7 @@ impl ExecutionResult { y_max_scroll_size: 0, x_position: 0, y_position: 0, + rect: Rect::default(), } } @@ -109,6 +113,20 @@ impl ExecutionResult { fn set_fold(&mut self, is_fold: bool) { self.fold = is_fold; } + + fn handle_mouse_events(&mut self, event: MouseEvent) { + if !is_in_area(event.column, event.row, self.rect) { + return; + } + + match event.kind { + MouseEventKind::ScrollDown => self.scroll_down(), + MouseEventKind::ScrollUp => self.scroll_up(), + MouseEventKind::ScrollLeft => self.scroll_left(), + MouseEventKind::ScrollRight => self.scroll_right(), + _ => {} + } + } } fn text_width(text: &Text) -> usize { @@ -148,12 +166,15 @@ impl Component for ExecutionResult { Action::SetFold(is_fold) => self.set_fold(is_fold), Action::BottomOfPage => self.bottom_of_page(), Action::TopOfPage => self.top_of_page(), + Action::MouseEvent(e) => self.handle_mouse_events(e), _ => {} } Ok(None) } fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> { + self.rect = area; + let text = self.result.clone().unwrap_or(Text::new("")); let mut current = text.to_string(); let mut y_max; diff --git a/src/components/history.rs b/src/components/history.rs index 554e33b..2d90884 100644 --- a/src/components/history.rs +++ b/src/components/history.rs @@ -10,7 +10,7 @@ use color_eyre::{ eyre::{Ok, OptionExt, Result}, owo_colors::OwoColorize, }; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::{prelude::*, widgets::*}; use serde::{Deserialize, Serialize}; use symbols::scrollbar; @@ -23,6 +23,7 @@ use crate::{ config::{Config, KeyBindings, RuntimeConfig}, mode::Mode, types::ExecutionId, + utils::is_in_area, widget::history_item::HistoryItem, }; @@ -37,6 +38,7 @@ pub struct History { runtime_config: RuntimeConfig, timemachine_mode: bool, y_state: ScrollbarState, + rect: Rect, } impl History { @@ -54,6 +56,7 @@ impl History { runtime_config, timemachine_mode: false, y_state: ScrollbarState::default(), + rect: Rect::default(), } } @@ -196,6 +199,19 @@ impl History { self.select_latest() } + + fn handle_mouse_events(&mut self, event: MouseEvent) -> Result<()> { + log::debug!("Mouse event: {:?}", event); + if !is_in_area(event.column, event.row, self.rect) { + return Ok(()); + } + + match event.kind { + MouseEventKind::ScrollDown => self.go_to_past(), + MouseEventKind::ScrollUp => self.go_to_future(), + _ => Ok(()), + } + } } impl Component for History { @@ -225,6 +241,7 @@ impl Component for History { Action::GoToMorePast => self.go_to_more_past()?, Action::GoToOldest => self.go_to_oldest()?, Action::GoToCurrent => self.go_to_current()?, + Action::MouseEvent(e) => self.handle_mouse_events(e)?, _ => {} } @@ -232,6 +249,8 @@ impl Component for History { } fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> { + self.rect = area; + let block = Block::default() .title("History") .borders(Borders::ALL) diff --git a/src/utils.rs b/src/utils.rs index 7611ef2..ed65d38 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,7 @@ use std::{path::PathBuf, sync::LazyLock}; use color_eyre::eyre::Result; use directories::{BaseDirs, ProjectDirs}; use human_panic::Metadata; +use ratatui::layout::Rect; use tracing::error; use tracing_error::ErrorLayer; use tracing_subscriber::{ @@ -181,3 +182,7 @@ Config directory: {config_dir_path} Data directory: {data_dir_path}" ) } + +pub fn is_in_area(x: u16, y: u16, area: Rect) -> bool { + x >= area.x && x < area.x + area.width && y >= area.y && y < area.y + area.height +}