aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/input.rs
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2020-06-18 01:02:56 +0000
committerGitHub <noreply@github.com>2020-06-18 01:02:56 +0000
commit87e5b1aa25ea61937fa5f79668d2a46e88707c5e (patch)
tree59205d2e00072a5f8737dc104f57fc940db0c2ec /alacritty/src/input.rs
parentd526649ee67237a1bedad8228f1f017035ddad3f (diff)
downloadr-alacritty-87e5b1aa25ea61937fa5f79668d2a46e88707c5e.tar.gz
r-alacritty-87e5b1aa25ea61937fa5f79668d2a46e88707c5e.tar.bz2
r-alacritty-87e5b1aa25ea61937fa5f79668d2a46e88707c5e.zip
Add automatic scrolling during selection
This adds a new `Scheduler` which allows for staging events to be processed at a later time. If there is a selection active and the mouse is above or below the window, the viewport will now scroll torwards the direction of the mouse. The amount of lines scrolled depends on the distance of the mouse to the boundaries used for selection scrolling. To make it possible to scroll while in fullscreen, the selection scrolling area includes the padding of the window and is at least 5 pixels high in case there is not enough padding present.
Diffstat (limited to 'alacritty/src/input.rs')
-rw-r--r--alacritty/src/input.rs140
1 files changed, 97 insertions, 43 deletions
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index 2025f108..d43bb26f 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -6,12 +6,13 @@
//! determine what to do when a non-modifier key is pressed.
use std::borrow::Cow;
-use std::cmp::{min, Ordering};
+use std::cmp::{max, min, Ordering};
use std::marker::PhantomData;
-use std::time::Instant;
+use std::time::{Duration, Instant};
use log::{debug, trace, warn};
+use glutin::dpi::PhysicalPosition;
use glutin::event::{
ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
};
@@ -21,7 +22,7 @@ use glutin::platform::macos::EventLoopWindowTargetExtMacOS;
use glutin::window::CursorIcon;
use alacritty_terminal::ansi::{ClearMode, Handler};
-use alacritty_terminal::event::{Event, EventListener};
+use alacritty_terminal::event::EventListener;
use alacritty_terminal::grid::Scroll;
use alacritty_terminal::index::{Column, Line, Point, Side};
use alacritty_terminal::message_bar::{self, Message};
@@ -33,13 +34,23 @@ use alacritty_terminal::vi_mode::ViMotion;
use crate::clipboard::Clipboard;
use crate::config::{Action, Binding, Config, Key, ViAction};
-use crate::event::{ClickState, Mouse};
+use crate::event::{ClickState, Event, Mouse};
+use crate::scheduler::{Scheduler, TimerId};
use crate::url::{Url, Urls};
use crate::window::Window;
/// Font size change interval.
pub const FONT_SIZE_STEP: f32 = 0.5;
+/// Interval for mouse scrolling during selection outside of the boundaries.
+const SELECTION_SCROLLING_INTERVAL: Duration = Duration::from_millis(15);
+
+/// Minimum number of pixels at the bottom/top where selection scrolling is performed.
+const MIN_SELECTION_SCROLLING_HEIGHT: f64 = 5.;
+
+/// Number of pixels for increasing the selection scrolling speed factor by one.
+const SELECTION_SCROLLING_STEP: f64 = 20.;
+
/// Processes input from glutin.
///
/// An escape sequence may be emitted in case specific keys or key combinations
@@ -77,10 +88,11 @@ pub trait ActionContext<T: EventListener> {
fn message(&self) -> Option<&Message>;
fn config(&self) -> &Config;
fn event_loop(&self) -> &EventLoopWindowTarget<Event>;
- fn clipboard(&mut self) -> &mut Clipboard;
fn urls(&self) -> &Urls;
fn launch_url(&self, url: Url);
fn mouse_mode(&self) -> bool;
+ fn clipboard_mut(&mut self) -> &mut Clipboard;
+ fn scheduler_mut(&mut self) -> &mut Scheduler;
}
trait Execute<T: EventListener> {
@@ -128,11 +140,11 @@ impl<T: EventListener> Execute<T> for Action {
#[cfg(not(any(target_os = "macos", windows)))]
Action::CopySelection => ctx.copy_selection(ClipboardType::Selection),
Action::Paste => {
- let text = ctx.clipboard().load(ClipboardType::Clipboard);
+ let text = ctx.clipboard_mut().load(ClipboardType::Clipboard);
paste(ctx, &text);
},
Action::PasteSelection => {
- let text = ctx.clipboard().load(ClipboardType::Selection);
+ let text = ctx.clipboard_mut().load(ClipboardType::Selection);
paste(ctx, &text);
},
Action::Command(ref program) => {
@@ -304,9 +316,19 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
}
#[inline]
- pub fn mouse_moved(&mut self, x: usize, y: usize) {
+ pub fn mouse_moved(&mut self, position: PhysicalPosition<f64>) {
let size_info = self.ctx.size_info();
+ let (x, y) = position.into();
+
+ let lmb_pressed = self.ctx.mouse().left_button_state == ElementState::Pressed;
+ if !self.ctx.selection_is_empty() && lmb_pressed {
+ self.update_selection_scrolling(y);
+ }
+
+ let x = min(max(x, 0), size_info.width as i32 - 1) as usize;
+ let y = min(max(y, 0), size_info.height as i32 - 1) as usize;
+
self.ctx.mouse_mut().x = x;
self.ctx.mouse_mut().y = y;
@@ -339,9 +361,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
let last_term_line = self.ctx.terminal().grid().num_lines() - 1;
- if self.ctx.mouse().left_button_state == ElementState::Pressed
- && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode())
- {
+ if lmb_pressed && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) {
// Treat motion over message bar like motion over the last line.
let line = min(point.line, last_term_line);
@@ -356,7 +376,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
&& point.line <= last_term_line
&& self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG)
{
- if self.ctx.mouse().left_button_state == ElementState::Pressed {
+ if lmb_pressed {
self.mouse_report(32, ElementState::Pressed);
} else if self.ctx.mouse().middle_button_state == ElementState::Pressed {
self.mouse_report(33, ElementState::Pressed);
@@ -548,6 +568,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
self.ctx.launch_url(url);
}
+ self.ctx.scheduler_mut().unschedule(TimerId::SelectionScrolling);
self.copy_selection();
}
@@ -870,32 +891,61 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
MouseState::Text
}
}
+
+ /// Handle automatic scrolling when selecting above/below the window.
+ fn update_selection_scrolling(&mut self, mouse_y: i32) {
+ let size_info = self.ctx.size_info();
+ let scheduler = self.ctx.scheduler_mut();
+
+ // Scale constants by DPI.
+ let min_height = (MIN_SELECTION_SCROLLING_HEIGHT * size_info.dpr) as i32;
+ let step = (SELECTION_SCROLLING_STEP * size_info.dpr) as i32;
+
+ // Compute the height of the scrolling areas.
+ let end_top = max(min_height, size_info.padding_y as i32);
+ let height_bottom = max(min_height, size_info.padding_bottom() as i32);
+ let start_bottom = size_info.height as i32 - height_bottom;
+
+ // Get distance from closest window boundary.
+ let delta = if mouse_y < end_top {
+ end_top - mouse_y + step
+ } else if mouse_y >= start_bottom {
+ start_bottom - mouse_y - step
+ } else {
+ scheduler.unschedule(TimerId::SelectionScrolling);
+ return;
+ };
+
+ // Scale number of lines scrolled based on distance to boundary.
+ let delta = delta as isize / step as isize;
+ let event = Event::Scroll(Scroll::Lines(delta));
+
+ // Schedule event.
+ match scheduler.get_mut(TimerId::SelectionScrolling) {
+ Some(timer) => timer.event = event.into(),
+ None => {
+ scheduler.schedule(
+ event.into(),
+ SELECTION_SCROLLING_INTERVAL,
+ true,
+ TimerId::SelectionScrolling,
+ );
+ },
+ }
+ }
}
#[cfg(test)]
mod tests {
- use std::borrow::Cow;
- use std::time::Duration;
-
- use glutin::event::{
- ElementState, Event, ModifiersState, MouseButton, VirtualKeyCode, WindowEvent,
- };
- use glutin::event_loop::EventLoopWindowTarget;
+ use super::*;
- use alacritty_terminal::event::{Event as TerminalEvent, EventListener};
- use alacritty_terminal::grid::Scroll;
- use alacritty_terminal::index::{Point, Side};
- use alacritty_terminal::message_bar::{Message, MessageBuffer};
- use alacritty_terminal::selection::{Selection, SelectionType};
- use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
+ use glutin::event::{Event as GlutinEvent, VirtualKeyCode, WindowEvent};
- use crate::clipboard::Clipboard;
- use crate::config::{ClickHandler, Config};
- use crate::event::{ClickState, Mouse};
- use crate::url::{Url, Urls};
- use crate::window::Window;
+ use alacritty_terminal::event::Event as TerminalEvent;
+ use alacritty_terminal::message_bar::MessageBuffer;
+ use alacritty_terminal::selection::Selection;
- use super::{Action, Binding, Processor};
+ use crate::config::ClickHandler;
const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
@@ -1014,7 +1064,11 @@ mod tests {
self.config
}
- fn event_loop(&self) -> &EventLoopWindowTarget<TerminalEvent> {
+ fn clipboard_mut(&mut self) -> &mut Clipboard {
+ self.clipboard
+ }
+
+ fn event_loop(&self) -> &EventLoopWindowTarget<Event> {
unimplemented!();
}
@@ -1022,11 +1076,11 @@ mod tests {
unimplemented!();
}
- fn clipboard(&mut self) -> &mut Clipboard {
- self.clipboard
+ fn launch_url(&self, _: Url) {
+ unimplemented!();
}
- fn launch_url(&self, _: Url) {
+ fn scheduler_mut (&mut self) -> &mut Scheduler {
unimplemented!();
}
}
@@ -1089,8 +1143,8 @@ mod tests {
let mut processor = Processor::new(context, &None);
- let event: Event::<'_, TerminalEvent> = $input;
- if let Event::WindowEvent {
+ let event: GlutinEvent::<'_, TerminalEvent> = $input;
+ if let GlutinEvent::WindowEvent {
event: WindowEvent::MouseInput {
state,
button,
@@ -1130,7 +1184,7 @@ mod tests {
name: single_click,
initial_state: ClickState::None,
initial_button: MouseButton::Other(0),
- input: Event::WindowEvent {
+ input: GlutinEvent::WindowEvent {
event: WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
@@ -1146,7 +1200,7 @@ mod tests {
name: single_right_click,
initial_state: ClickState::None,
initial_button: MouseButton::Other(0),
- input: Event::WindowEvent {
+ input: GlutinEvent::WindowEvent {
event: WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Right,
@@ -1162,7 +1216,7 @@ mod tests {
name: single_middle_click,
initial_state: ClickState::None,
initial_button: MouseButton::Other(0),
- input: Event::WindowEvent {
+ input: GlutinEvent::WindowEvent {
event: WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Middle,
@@ -1178,7 +1232,7 @@ mod tests {
name: double_click,
initial_state: ClickState::Click,
initial_button: MouseButton::Left,
- input: Event::WindowEvent {
+ input: GlutinEvent::WindowEvent {
event: WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
@@ -1194,7 +1248,7 @@ mod tests {
name: triple_click,
initial_state: ClickState::DoubleClick,
initial_button: MouseButton::Left,
- input: Event::WindowEvent {
+ input: GlutinEvent::WindowEvent {
event: WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
@@ -1210,7 +1264,7 @@ mod tests {
name: multi_click_separate_buttons,
initial_state: ClickState::DoubleClick,
initial_button: MouseButton::Left,
- input: Event::WindowEvent {
+ input: GlutinEvent::WindowEvent {
event: WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Right,