aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/input.rs
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2023-02-13 22:35:18 +0000
committerGitHub <noreply@github.com>2023-02-13 22:35:18 +0000
commit40be4caca1805ae3b77e65600e8f3daeae56d5c3 (patch)
treeab11017869ceac94cd03c30c35e097c341d2138c /alacritty/src/input.rs
parentc82de4ccadcaf2e74dc8e8ef2da1ebe13e2ca5a9 (diff)
downloadr-alacritty-40be4caca1805ae3b77e65600e8f3daeae56d5c3.tar.gz
r-alacritty-40be4caca1805ae3b77e65600e8f3daeae56d5c3.tar.bz2
r-alacritty-40be4caca1805ae3b77e65600e8f3daeae56d5c3.zip
Add touch input support
This patch builds upon the prior work by @4z3 and @bytbox to add touchscreen support to Alacritty. While some inspiration was taken from @4z3's patch, it was rewritten from scratch. This patch supports 4 basic touch interactions: - Tap - Scroll - Select - Zoom Tap allows emulating the mouse to enter a single LMB click. While it would be possible to add more complicated mouse emulation including support for RMB and others, it's likely more confusing than anything else and could conflict with other more useful touch actions. Scroll and Select are started by horizontal or vertical dragging. While selection isn't particularly accurate with a fat finger, it works reasonably well and the separation from selection through horizontal and vertical start feels pretty natural. Since horizontal drag is reserved for selection we do not support horizontal scrolling inside the terminal. While it would be possible to somewhat support it by starting a selection with vertical movement and then scrolling horizontally afterwards, it would likely just confuse people so it was left out. Zoom is pretty simple in just changing the font size when a two-finger pinch gesture is used. Performance of this is pretty terrible especially on low-end hardware since this obviously isn't a cheap operation, but it seems like a worthwhile addition since small touchscreen devices are most likely to need frequent font size adjustment to make output readable. Closes #3671.
Diffstat (limited to 'alacritty/src/input.rs')
-rw-r--r--alacritty/src/input.rs133
1 files changed, 131 insertions, 2 deletions
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index d454746a..f4c81006 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -7,14 +7,17 @@
use std::borrow::Cow;
use std::cmp::{max, min, Ordering};
+use std::collections::HashSet;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::marker::PhantomData;
+use std::mem;
use std::time::{Duration, Instant};
use winit::dpi::PhysicalPosition;
use winit::event::{
- ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
+ ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta,
+ Touch as TouchEvent, TouchPhase,
};
use winit::event_loop::EventLoopWindowTarget;
#[cfg(target_os = "macos")]
@@ -35,7 +38,9 @@ use crate::config::{Action, BindingMode, Key, MouseAction, SearchAction, UiConfi
use crate::display::hint::HintMatch;
use crate::display::window::Window;
use crate::display::{Display, SizeInfo};
-use crate::event::{ClickState, Event, EventType, Mouse, TYPING_SEARCH_DELAY};
+use crate::event::{
+ ClickState, Event, EventType, Mouse, TouchPurpose, TouchZoom, TYPING_SEARCH_DELAY,
+};
use crate::message_bar::{self, Message};
use crate::scheduler::{Scheduler, TimerId, Topic};
@@ -51,6 +56,12 @@ 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.;
+/// Touch scroll speed.
+const TOUCH_SCROLL_FACTOR: f64 = 0.35;
+
+/// Distance before a touch input is considered a drag.
+const MAX_TAP_DISTANCE: f64 = 20.;
+
/// Processes input from winit.
///
/// An escape sequence may be emitted in case specific keys or key combinations
@@ -72,6 +83,7 @@ pub trait ActionContext<T: EventListener> {
fn selection_is_empty(&self) -> bool;
fn mouse_mut(&mut self) -> &mut Mouse;
fn mouse(&self) -> &Mouse;
+ fn touch_purpose(&mut self) -> &mut TouchPurpose;
fn received_count(&mut self) -> &mut usize;
fn suppress_chars(&mut self) -> &mut bool;
fn modifiers(&mut self) -> &mut ModifiersState;
@@ -735,6 +747,118 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
}
+ /// Handle touch input.
+ pub fn touch(&mut self, touch: TouchEvent) {
+ match touch.phase {
+ TouchPhase::Started => self.on_touch_start(touch),
+ TouchPhase::Moved => self.on_touch_motion(touch),
+ TouchPhase::Ended | TouchPhase::Cancelled => self.on_touch_end(touch),
+ }
+ }
+
+ /// Handle beginning of touch input.
+ pub fn on_touch_start(&mut self, touch: TouchEvent) {
+ let touch_purpose = self.ctx.touch_purpose();
+ *touch_purpose = match mem::take(touch_purpose) {
+ TouchPurpose::None => TouchPurpose::Tap(touch),
+ TouchPurpose::Tap(start) => TouchPurpose::Zoom(TouchZoom::new((start, touch))),
+ TouchPurpose::Zoom(zoom) => TouchPurpose::Invalid(zoom.slots()),
+ TouchPurpose::Scroll(event) | TouchPurpose::Select(event) => {
+ let mut set = HashSet::new();
+ set.insert(event.id);
+ TouchPurpose::Invalid(set)
+ },
+ TouchPurpose::Invalid(mut slots) => {
+ slots.insert(touch.id);
+ TouchPurpose::Invalid(slots)
+ },
+ };
+ }
+
+ /// Handle touch input movement.
+ pub fn on_touch_motion(&mut self, touch: TouchEvent) {
+ let touch_purpose = self.ctx.touch_purpose();
+ match touch_purpose {
+ TouchPurpose::None => (),
+ // Handle transition from tap to scroll/select.
+ TouchPurpose::Tap(start) => {
+ let delta_x = touch.location.x - start.location.x;
+ let delta_y = touch.location.y - start.location.y;
+ if delta_x.abs() > MAX_TAP_DISTANCE {
+ // Update gesture state.
+ let start_location = start.location;
+ *touch_purpose = TouchPurpose::Select(*start);
+
+ // Start simulated mouse input.
+ self.mouse_moved(start_location);
+ self.mouse_input(ElementState::Pressed, MouseButton::Left);
+
+ // Apply motion since touch start.
+ self.on_touch_motion(touch);
+ } else if delta_y.abs() > MAX_TAP_DISTANCE {
+ // Update gesture state.
+ *touch_purpose = TouchPurpose::Scroll(*start);
+
+ // Apply motion since touch start.
+ self.on_touch_motion(touch);
+ }
+ },
+ TouchPurpose::Zoom(zoom) => {
+ let font_delta = zoom.font_delta(touch);
+ self.ctx.change_font_size(font_delta);
+ },
+ TouchPurpose::Scroll(last_touch) => {
+ // Calculate delta and update last touch position.
+ let delta_y = touch.location.y - last_touch.location.y;
+ *touch_purpose = TouchPurpose::Scroll(touch);
+
+ self.scroll_terminal(0., delta_y * TOUCH_SCROLL_FACTOR);
+ },
+ TouchPurpose::Select(_) => self.mouse_moved(touch.location),
+ TouchPurpose::Invalid(_) => (),
+ }
+ }
+
+ /// Handle end of touch input.
+ pub fn on_touch_end(&mut self, touch: TouchEvent) {
+ // Finalize the touch motion up to the release point.
+ self.on_touch_motion(touch);
+
+ let touch_purpose = self.ctx.touch_purpose();
+ match touch_purpose {
+ // Simulate LMB clicks.
+ TouchPurpose::Tap(start) => {
+ let start_location = start.location;
+ *touch_purpose = Default::default();
+
+ self.mouse_moved(start_location);
+ self.mouse_input(ElementState::Pressed, MouseButton::Left);
+ self.mouse_input(ElementState::Released, MouseButton::Left);
+ },
+ // Invalidate zoom once a finger was released.
+ TouchPurpose::Zoom(zoom) => {
+ let mut slots = zoom.slots();
+ slots.remove(&touch.id);
+ *touch_purpose = TouchPurpose::Invalid(slots);
+ },
+ // Reset touch state once all slots were released.
+ TouchPurpose::Invalid(slots) => {
+ slots.remove(&touch.id);
+ if slots.is_empty() {
+ *touch_purpose = Default::default();
+ }
+ },
+ // Release simulated LMB.
+ TouchPurpose::Select(_) => {
+ *touch_purpose = Default::default();
+ self.mouse_input(ElementState::Released, MouseButton::Left);
+ },
+ // Reset touch state on scroll finish.
+ TouchPurpose::Scroll(_) => *touch_purpose = Default::default(),
+ TouchPurpose::None => (),
+ }
+ }
+
pub fn mouse_input(&mut self, state: ElementState, button: MouseButton) {
match button {
MouseButton::Left => self.ctx.mouse_mut().left_button_state = state,
@@ -1105,6 +1229,11 @@ mod tests {
self.mouse
}
+ #[inline]
+ fn touch_purpose(&mut self) -> &mut TouchPurpose {
+ unimplemented!();
+ }
+
fn received_count(&mut self) -> &mut usize {
&mut self.received_count
}