summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--alacritty/src/event.rs91
-rw-r--r--alacritty/src/input.rs133
-rw-r--r--alacritty/src/window_context.rs5
4 files changed, 218 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index abada71e..701d9631 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support for horizontal scrolling in mouse mode and alternative scrolling modes
- Support for fractional scaling on Wayland with wp-fractional-scale protocol
- Support for running on GLES context
+- Touchscreen input for click/scroll/select/zoom
### Changed
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index 768b7a47..5eafc09c 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -2,7 +2,7 @@
use std::borrow::Cow;
use std::cmp::{max, min};
-use std::collections::{HashMap, VecDeque};
+use std::collections::{HashMap, HashSet, VecDeque};
use std::error::Error;
use std::ffi::OsStr;
use std::fmt::Debug;
@@ -19,7 +19,8 @@ use log::{debug, error, info, warn};
use wayland_client::{Display as WaylandDisplay, EventQueue};
use winit::dpi::PhysicalSize;
use winit::event::{
- ElementState, Event as WinitEvent, Ime, ModifiersState, MouseButton, StartCause, WindowEvent,
+ ElementState, Event as WinitEvent, Ime, ModifiersState, MouseButton, StartCause,
+ Touch as TouchEvent, WindowEvent,
};
use winit::event_loop::{
ControlFlow, DeviceEventFilter, EventLoop, EventLoopProxy, EventLoopWindowTarget,
@@ -66,6 +67,9 @@ const MAX_SEARCH_WHILE_TYPING: Option<usize> = Some(1000);
/// Maximum number of search terms stored in the history.
const MAX_SEARCH_HISTORY_SIZE: usize = 255;
+/// Touch zoom speed.
+const TOUCH_ZOOM_FACTOR: f32 = 0.01;
+
/// Alacritty events.
#[derive(Debug, Clone)]
pub struct Event {
@@ -186,6 +190,7 @@ pub struct ActionContext<'a, N, T> {
pub terminal: &'a mut Term<T>,
pub clipboard: &'a mut Clipboard,
pub mouse: &'a mut Mouse,
+ pub touch: &'a mut TouchPurpose,
pub received_count: &'a mut usize,
pub suppress_chars: &'a mut bool,
pub modifiers: &'a mut ModifiersState,
@@ -339,6 +344,11 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
#[inline]
+ fn touch_purpose(&mut self) -> &mut TouchPurpose {
+ self.touch
+ }
+
+ #[inline]
fn received_count(&mut self) -> &mut usize {
self.received_count
}
@@ -1016,12 +1026,68 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
}
}
-#[derive(Debug, Eq, PartialEq)]
-pub enum ClickState {
+/// Identified purpose of the touch input.
+#[derive(Debug)]
+pub enum TouchPurpose {
None,
- Click,
- DoubleClick,
- TripleClick,
+ Select(TouchEvent),
+ Scroll(TouchEvent),
+ Zoom(TouchZoom),
+ Tap(TouchEvent),
+ Invalid(HashSet<u64>),
+}
+
+impl Default for TouchPurpose {
+ fn default() -> Self {
+ Self::None
+ }
+}
+
+/// Touch zooming state.
+#[derive(Debug)]
+pub struct TouchZoom {
+ slots: (TouchEvent, TouchEvent),
+ fractions: f32,
+}
+
+impl TouchZoom {
+ pub fn new(slots: (TouchEvent, TouchEvent)) -> Self {
+ Self { slots, fractions: Default::default() }
+ }
+
+ /// Get slot distance change since last update.
+ pub fn font_delta(&mut self, slot: TouchEvent) -> f32 {
+ let old_distance = self.distance();
+
+ // Update touch slots.
+ if slot.id == self.slots.0.id {
+ self.slots.0 = slot;
+ } else {
+ self.slots.1 = slot;
+ }
+
+ // Calculate font change in `FONT_SIZE_STEP` increments.
+ let delta = (self.distance() - old_distance) * TOUCH_ZOOM_FACTOR + self.fractions;
+ let font_delta = (delta.abs() / FONT_SIZE_STEP).floor() * FONT_SIZE_STEP * delta.signum();
+ self.fractions = delta - font_delta;
+
+ font_delta
+ }
+
+ /// Get active touch slots.
+ pub fn slots(&self) -> HashSet<u64> {
+ let mut set = HashSet::new();
+ set.insert(self.slots.0.id);
+ set.insert(self.slots.1.id);
+ set
+ }
+
+ /// Calculate distance between slots.
+ fn distance(&self) -> f32 {
+ let delta_x = self.slots.0.location.x - self.slots.1.location.x;
+ let delta_y = self.slots.0.location.y - self.slots.1.location.y;
+ delta_x.hypot(delta_y) as f32
+ }
}
/// State of the mouse.
@@ -1081,6 +1147,14 @@ impl Mouse {
}
}
+#[derive(Debug, Eq, PartialEq)]
+pub enum ClickState {
+ None,
+ Click,
+ DoubleClick,
+ TripleClick,
+}
+
/// The amount of scroll accumulated from the pointer events.
#[derive(Default, Debug)]
pub struct AccumulatedScroll {
@@ -1217,6 +1291,7 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
self.ctx.window().set_mouse_visible(true);
self.mouse_wheel_input(delta, phase);
},
+ WindowEvent::Touch(touch) => self.touch(touch),
WindowEvent::Focused(is_focused) => {
self.ctx.terminal.is_focused = is_focused;
@@ -1290,7 +1365,6 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
| WindowEvent::Destroyed
| WindowEvent::ThemeChanged(_)
| WindowEvent::HoveredFile(_)
- | WindowEvent::Touch(_)
| WindowEvent::Moved(_) => (),
}
},
@@ -1592,7 +1666,6 @@ impl Processor {
| WindowEvent::HoveredFileCancelled
| WindowEvent::Destroyed
| WindowEvent::HoveredFile(_)
- | WindowEvent::Touch(_)
| WindowEvent::Moved(_)
),
WinitEvent::Suspended { .. }
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
}
diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs
index 6680f583..7c2754e0 100644
--- a/alacritty/src/window_context.rs
+++ b/alacritty/src/window_context.rs
@@ -42,7 +42,7 @@ use crate::clipboard::Clipboard;
use crate::config::UiConfig;
use crate::display::window::Window;
use crate::display::Display;
-use crate::event::{ActionContext, Event, EventProxy, EventType, Mouse, SearchState};
+use crate::event::{ActionContext, Event, EventProxy, EventType, Mouse, SearchState, TouchPurpose};
use crate::logging::LOG_TARGET_IPC_CONFIG;
use crate::message_bar::MessageBuffer;
use crate::scheduler::Scheduler;
@@ -62,6 +62,7 @@ pub struct WindowContext {
notifier: Notifier,
font_size: Size,
mouse: Mouse,
+ touch: TouchPurpose,
dirty: bool,
occluded: bool,
preserve_title: bool,
@@ -255,6 +256,7 @@ impl WindowContext {
ipc_config: Default::default(),
modifiers: Default::default(),
mouse: Default::default(),
+ touch: Default::default(),
dirty: Default::default(),
occluded: Default::default(),
})
@@ -441,6 +443,7 @@ impl WindowContext {
notifier: &mut self.notifier,
display: &mut self.display,
mouse: &mut self.mouse,
+ touch: &mut self.touch,
dirty: &mut self.dirty,
occluded: &mut self.occluded,
terminal: &mut terminal,