2022-11-03 16:37:54 +00:00
|
|
|
//! Handle input from winit.
|
2016-06-23 16:48:31 +00:00
|
|
|
//!
|
2020-05-05 22:50:23 +00:00
|
|
|
//! Certain key combinations should send some escape sequence back to the PTY.
|
2016-06-23 16:48:31 +00:00
|
|
|
//! In order to figure that out, state about which modifier keys are pressed
|
|
|
|
//! needs to be tracked. Additionally, we need a bit of a state machine to
|
|
|
|
//! determine what to do when a non-modifier key is pressed.
|
2020-06-06 18:49:14 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
use std::borrow::Cow;
|
2020-06-18 01:02:56 +00:00
|
|
|
use std::cmp::{max, min, Ordering};
|
2023-02-13 22:35:18 +00:00
|
|
|
use std::collections::HashSet;
|
2021-12-18 22:18:42 +00:00
|
|
|
use std::ffi::OsStr;
|
|
|
|
use std::fmt::Debug;
|
2019-10-05 00:29:26 +00:00
|
|
|
use std::marker::PhantomData;
|
2023-02-13 22:35:18 +00:00
|
|
|
use std::mem;
|
2020-06-18 01:02:56 +00:00
|
|
|
use std::time::{Duration, Instant};
|
2017-01-02 02:28:49 +00:00
|
|
|
|
2023-04-09 20:29:48 +00:00
|
|
|
use log::debug;
|
2022-11-03 16:37:54 +00:00
|
|
|
use winit::dpi::PhysicalPosition;
|
|
|
|
use winit::event::{
|
2023-12-06 05:26:07 +00:00
|
|
|
ElementState, Modifiers, MouseButton, MouseScrollDelta, Touch as TouchEvent, TouchPhase,
|
2019-03-19 19:14:17 +00:00
|
|
|
};
|
2022-11-03 16:37:54 +00:00
|
|
|
use winit::event_loop::EventLoopWindowTarget;
|
2023-12-06 05:26:07 +00:00
|
|
|
use winit::keyboard::ModifiersState;
|
2020-02-07 13:44:11 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
2023-12-06 05:26:07 +00:00
|
|
|
use winit::platform::macos::EventLoopWindowTargetExtMacOS;
|
2022-11-03 16:37:54 +00:00
|
|
|
use winit::window::CursorIcon;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
use alacritty_terminal::event::EventListener;
|
2020-07-09 21:45:22 +00:00
|
|
|
use alacritty_terminal::grid::{Dimensions, Scroll};
|
2021-04-13 03:24:42 +00:00
|
|
|
use alacritty_terminal::index::{Boundary, Column, Direction, Point, Side};
|
2020-03-18 02:35:08 +00:00
|
|
|
use alacritty_terminal::selection::SelectionType;
|
2021-01-24 21:45:36 +00:00
|
|
|
use alacritty_terminal::term::search::Match;
|
2022-04-06 10:06:39 +00:00
|
|
|
use alacritty_terminal::term::{ClipboardType, Term, TermMode};
|
2020-03-18 02:35:08 +00:00
|
|
|
use alacritty_terminal::vi_mode::ViMotion;
|
2023-11-10 14:16:22 +00:00
|
|
|
use alacritty_terminal::vte::ansi::{ClearMode, Handler};
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-06-06 21:33:20 +00:00
|
|
|
use crate::clipboard::Clipboard;
|
2023-12-06 05:26:07 +00:00
|
|
|
use crate::config::{Action, BindingMode, MouseAction, SearchAction, UiConfig, ViAction};
|
2021-04-13 03:24:42 +00:00
|
|
|
use crate::display::hint::HintMatch;
|
2021-01-24 21:45:36 +00:00
|
|
|
use crate::display::window::Window;
|
2022-04-06 10:06:39 +00:00
|
|
|
use crate::display::{Display, SizeInfo};
|
2023-02-13 22:35:18 +00:00
|
|
|
use crate::event::{
|
2023-10-20 09:33:38 +00:00
|
|
|
ClickState, Event, EventType, InlineSearchState, Mouse, TouchPurpose, TouchZoom,
|
2023-02-13 22:35:18 +00:00
|
|
|
};
|
2020-07-11 17:03:09 +00:00
|
|
|
use crate::message_bar::{self, Message};
|
2021-10-23 07:16:47 +00:00
|
|
|
use crate::scheduler::{Scheduler, TimerId, Topic};
|
2018-07-21 17:17:41 +00:00
|
|
|
|
2023-12-06 05:26:07 +00:00
|
|
|
pub mod keyboard;
|
|
|
|
|
2023-12-07 21:33:33 +00:00
|
|
|
/// Font size change interval in px.
|
2023-12-14 03:15:40 +00:00
|
|
|
pub const FONT_SIZE_STEP: f32 = 1.;
|
2019-11-03 19:02:26 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
/// 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.;
|
|
|
|
|
2023-02-13 22:35:18 +00:00
|
|
|
/// Distance before a touch input is considered a drag.
|
|
|
|
const MAX_TAP_DISTANCE: f64 = 20.;
|
|
|
|
|
2023-06-29 13:17:54 +00:00
|
|
|
/// Threshold used for double_click/triple_click.
|
2023-07-13 05:31:26 +00:00
|
|
|
const CLICK_THRESHOLD: Duration = Duration::from_millis(400);
|
2023-06-29 13:17:54 +00:00
|
|
|
|
2022-11-03 16:37:54 +00:00
|
|
|
/// Processes input from winit.
|
2016-06-23 16:48:31 +00:00
|
|
|
///
|
|
|
|
/// An escape sequence may be emitted in case specific keys or key combinations
|
|
|
|
/// are activated.
|
2021-01-24 21:45:36 +00:00
|
|
|
pub struct Processor<T: EventListener, A: ActionContext<T>> {
|
2017-02-02 06:13:08 +00:00
|
|
|
pub ctx: A,
|
2019-10-05 00:29:26 +00:00
|
|
|
_phantom: PhantomData<T>,
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
pub trait ActionContext<T: EventListener> {
|
2021-04-03 23:52:44 +00:00
|
|
|
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&self, _data: B) {}
|
2021-01-24 21:45:36 +00:00
|
|
|
fn mark_dirty(&mut self) {}
|
2017-02-02 06:13:08 +00:00
|
|
|
fn size_info(&self) -> SizeInfo;
|
2021-01-24 21:45:36 +00:00
|
|
|
fn copy_selection(&mut self, _ty: ClipboardType) {}
|
|
|
|
fn start_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {}
|
|
|
|
fn toggle_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {}
|
|
|
|
fn update_selection(&mut self, _point: Point, _side: Side) {}
|
|
|
|
fn clear_selection(&mut self) {}
|
2018-10-22 19:39:26 +00:00
|
|
|
fn selection_is_empty(&self) -> bool;
|
2017-02-02 06:13:08 +00:00
|
|
|
fn mouse_mut(&mut self) -> &mut Mouse;
|
2018-09-27 12:12:49 +00:00
|
|
|
fn mouse(&self) -> &Mouse;
|
2023-02-13 22:35:18 +00:00
|
|
|
fn touch_purpose(&mut self) -> &mut TouchPurpose;
|
2023-07-11 02:22:14 +00:00
|
|
|
fn modifiers(&mut self) -> &mut Modifiers;
|
2021-01-24 21:45:36 +00:00
|
|
|
fn scroll(&mut self, _scroll: Scroll) {}
|
2021-04-13 03:24:42 +00:00
|
|
|
fn window(&mut self) -> &mut Window;
|
|
|
|
fn display(&mut self) -> &mut Display;
|
2019-10-05 00:29:26 +00:00
|
|
|
fn terminal(&self) -> &Term<T>;
|
|
|
|
fn terminal_mut(&mut self) -> &mut Term<T>;
|
2021-01-24 21:45:36 +00:00
|
|
|
fn spawn_new_instance(&mut self) {}
|
2023-07-13 07:46:25 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
fn create_new_window(&mut self, _tabbing_id: Option<String>) {}
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
2021-10-23 07:16:47 +00:00
|
|
|
fn create_new_window(&mut self) {}
|
2023-12-14 03:15:40 +00:00
|
|
|
fn change_font_size(&mut self, _delta: f32) {}
|
2021-01-24 21:45:36 +00:00
|
|
|
fn reset_font_size(&mut self) {}
|
|
|
|
fn pop_message(&mut self) {}
|
2019-10-05 00:29:26 +00:00
|
|
|
fn message(&self) -> Option<&Message>;
|
2021-11-22 18:34:09 +00:00
|
|
|
fn config(&self) -> &UiConfig;
|
2020-02-07 13:44:11 +00:00
|
|
|
fn event_loop(&self) -> &EventLoopWindowTarget<Event>;
|
2020-03-18 02:35:08 +00:00
|
|
|
fn mouse_mode(&self) -> bool;
|
2020-06-18 01:02:56 +00:00
|
|
|
fn clipboard_mut(&mut self) -> &mut Clipboard;
|
|
|
|
fn scheduler_mut(&mut self) -> &mut Scheduler;
|
2021-01-24 21:45:36 +00:00
|
|
|
fn start_search(&mut self, _direction: Direction) {}
|
|
|
|
fn confirm_search(&mut self) {}
|
|
|
|
fn cancel_search(&mut self) {}
|
|
|
|
fn search_input(&mut self, _c: char) {}
|
|
|
|
fn search_pop_word(&mut self) {}
|
|
|
|
fn search_history_previous(&mut self) {}
|
|
|
|
fn search_history_next(&mut self) {}
|
2021-03-30 23:25:38 +00:00
|
|
|
fn search_next(&mut self, origin: Point, direction: Direction, side: Side) -> Option<Match>;
|
2021-01-24 21:45:36 +00:00
|
|
|
fn advance_search_origin(&mut self, _direction: Direction) {}
|
2020-07-09 21:45:22 +00:00
|
|
|
fn search_direction(&self) -> Direction;
|
|
|
|
fn search_active(&self) -> bool;
|
2021-01-24 21:45:36 +00:00
|
|
|
fn on_typing_start(&mut self) {}
|
|
|
|
fn toggle_vi_mode(&mut self) {}
|
2023-10-20 09:33:38 +00:00
|
|
|
fn inline_search_state(&mut self) -> &mut InlineSearchState;
|
|
|
|
fn start_inline_search(&mut self, _direction: Direction, _stop_short: bool) {}
|
|
|
|
fn inline_search_next(&mut self) {}
|
|
|
|
fn inline_search_previous(&mut self) {}
|
2021-03-01 19:50:39 +00:00
|
|
|
fn hint_input(&mut self, _character: char) {}
|
2021-04-13 03:24:42 +00:00
|
|
|
fn trigger_hint(&mut self, _hint: &HintMatch) {}
|
2021-07-29 15:40:51 +00:00
|
|
|
fn expand_selection(&mut self) {}
|
2023-04-09 22:44:57 +00:00
|
|
|
fn on_terminal_input_start(&mut self) {}
|
|
|
|
fn paste(&mut self, _text: &str, _bracketed: bool) {}
|
2021-12-18 22:18:42 +00:00
|
|
|
fn spawn_daemon<I, S>(&self, _program: &str, _args: I)
|
|
|
|
where
|
|
|
|
I: IntoIterator<Item = S> + Debug + Copy,
|
|
|
|
S: AsRef<OsStr>,
|
|
|
|
{
|
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
impl Action {
|
|
|
|
fn toggle_selection<T, A>(ctx: &mut A, ty: SelectionType)
|
|
|
|
where
|
|
|
|
A: ActionContext<T>,
|
2021-01-24 21:45:36 +00:00
|
|
|
T: EventListener,
|
2020-03-18 02:35:08 +00:00
|
|
|
{
|
2021-03-30 23:25:38 +00:00
|
|
|
ctx.toggle_selection(ty, ctx.terminal().vi_mode_cursor.point, Side::Left);
|
2020-03-18 02:35:08 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Make sure initial selection is not empty.
|
2020-05-30 20:45:44 +00:00
|
|
|
if let Some(selection) = &mut ctx.terminal_mut().selection {
|
2020-03-18 02:35:08 +00:00
|
|
|
selection.include_all();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-01 19:50:39 +00:00
|
|
|
trait Execute<T: EventListener> {
|
|
|
|
fn execute<A: ActionContext<T>>(&self, ctx: &mut A);
|
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
impl<T: EventListener> Execute<T> for Action {
|
2016-11-15 17:38:50 +00:00
|
|
|
#[inline]
|
2020-01-10 00:44:41 +00:00
|
|
|
fn execute<A: ActionContext<T>>(&self, ctx: &mut A) {
|
2021-03-01 19:50:39 +00:00
|
|
|
match self {
|
2023-04-09 22:44:57 +00:00
|
|
|
Action::Esc(s) => ctx.paste(s, false),
|
2021-12-18 22:18:42 +00:00
|
|
|
Action::Command(program) => ctx.spawn_daemon(program.program(), program.args()),
|
2021-03-01 19:50:39 +00:00
|
|
|
Action::Hint(hint) => {
|
2021-04-13 03:24:42 +00:00
|
|
|
ctx.display().hint_state.start(hint.clone());
|
2021-03-01 19:50:39 +00:00
|
|
|
ctx.mark_dirty();
|
2017-05-25 16:19:00 +00:00
|
|
|
},
|
2022-01-21 09:48:15 +00:00
|
|
|
Action::ToggleViMode => {
|
|
|
|
ctx.on_typing_start();
|
|
|
|
ctx.toggle_vi_mode()
|
|
|
|
},
|
2023-04-09 20:29:48 +00:00
|
|
|
action @ (Action::ViMotion(_) | Action::Vi(_))
|
|
|
|
if !ctx.terminal().mode().contains(TermMode::VI) =>
|
|
|
|
{
|
|
|
|
debug!("Ignoring {action:?}: Vi mode inactive");
|
|
|
|
},
|
2020-07-09 21:45:22 +00:00
|
|
|
Action::ViMotion(motion) => {
|
2020-11-23 23:11:03 +00:00
|
|
|
ctx.on_typing_start();
|
2021-03-01 19:50:39 +00:00
|
|
|
ctx.terminal_mut().vi_motion(*motion);
|
2021-01-24 21:45:36 +00:00
|
|
|
ctx.mark_dirty();
|
2020-07-09 21:45:22 +00:00
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::ToggleNormalSelection) => {
|
2021-03-01 19:50:39 +00:00
|
|
|
Self::toggle_selection(ctx, SelectionType::Simple);
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::ToggleLineSelection) => {
|
2021-03-01 19:50:39 +00:00
|
|
|
Self::toggle_selection(ctx, SelectionType::Lines);
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::ToggleBlockSelection) => {
|
2021-03-01 19:50:39 +00:00
|
|
|
Self::toggle_selection(ctx, SelectionType::Block);
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::ToggleSemanticSelection) => {
|
2021-03-01 19:50:39 +00:00
|
|
|
Self::toggle_selection(ctx, SelectionType::Semantic);
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::Open) => {
|
2021-04-13 03:24:42 +00:00
|
|
|
let hint = ctx.display().vi_highlighted_hint.take();
|
|
|
|
if let Some(hint) = &hint {
|
|
|
|
ctx.mouse_mut().block_hint_launcher = false;
|
|
|
|
ctx.trigger_hint(hint);
|
2020-03-18 02:35:08 +00:00
|
|
|
}
|
2021-04-13 03:24:42 +00:00
|
|
|
ctx.display().vi_highlighted_hint = hint;
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::SearchNext) => {
|
2022-03-25 12:02:44 +00:00
|
|
|
ctx.on_typing_start();
|
|
|
|
|
2021-01-03 11:24:04 +00:00
|
|
|
let terminal = ctx.terminal();
|
2020-07-09 21:45:22 +00:00
|
|
|
let direction = ctx.search_direction();
|
2021-03-30 23:25:38 +00:00
|
|
|
let vi_point = terminal.vi_mode_cursor.point;
|
2021-01-08 16:40:45 +00:00
|
|
|
let origin = match direction {
|
2021-03-30 23:25:38 +00:00
|
|
|
Direction::Right => vi_point.add(terminal, Boundary::None, 1),
|
|
|
|
Direction::Left => vi_point.sub(terminal, Boundary::None, 1),
|
2021-01-08 16:40:45 +00:00
|
|
|
};
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
if let Some(regex_match) = ctx.search_next(origin, direction, Side::Left) {
|
2020-07-09 21:45:22 +00:00
|
|
|
ctx.terminal_mut().vi_goto_point(*regex_match.start());
|
2021-01-24 21:45:36 +00:00
|
|
|
ctx.mark_dirty();
|
2020-05-06 21:58:43 +00:00
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::SearchPrevious) => {
|
2022-03-25 12:02:44 +00:00
|
|
|
ctx.on_typing_start();
|
|
|
|
|
2021-01-03 11:24:04 +00:00
|
|
|
let terminal = ctx.terminal();
|
2020-07-09 21:45:22 +00:00
|
|
|
let direction = ctx.search_direction().opposite();
|
2021-03-30 23:25:38 +00:00
|
|
|
let vi_point = terminal.vi_mode_cursor.point;
|
2021-01-08 16:40:45 +00:00
|
|
|
let origin = match direction {
|
2021-03-30 23:25:38 +00:00
|
|
|
Direction::Right => vi_point.add(terminal, Boundary::None, 1),
|
|
|
|
Direction::Left => vi_point.sub(terminal, Boundary::None, 1),
|
2021-01-08 16:40:45 +00:00
|
|
|
};
|
2020-05-06 21:58:43 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
if let Some(regex_match) = ctx.search_next(origin, direction, Side::Left) {
|
2020-07-09 21:45:22 +00:00
|
|
|
ctx.terminal_mut().vi_goto_point(*regex_match.start());
|
2021-01-24 21:45:36 +00:00
|
|
|
ctx.mark_dirty();
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::SearchStart) => {
|
2020-07-14 15:17:29 +00:00
|
|
|
let terminal = ctx.terminal();
|
2021-03-30 23:25:38 +00:00
|
|
|
let origin = terminal.vi_mode_cursor.point.sub(terminal, Boundary::None, 1);
|
2021-01-03 11:24:04 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
if let Some(regex_match) = ctx.search_next(origin, Direction::Left, Side::Left) {
|
2020-07-14 15:17:29 +00:00
|
|
|
ctx.terminal_mut().vi_goto_point(*regex_match.start());
|
2021-01-24 21:45:36 +00:00
|
|
|
ctx.mark_dirty();
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
2020-05-06 21:58:43 +00:00
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Vi(ViAction::SearchEnd) => {
|
2020-07-14 15:17:29 +00:00
|
|
|
let terminal = ctx.terminal();
|
2021-03-30 23:25:38 +00:00
|
|
|
let origin = terminal.vi_mode_cursor.point.add(terminal, Boundary::None, 1);
|
2021-01-03 11:24:04 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
if let Some(regex_match) = ctx.search_next(origin, Direction::Right, Side::Right) {
|
2020-07-09 21:45:22 +00:00
|
|
|
ctx.terminal_mut().vi_goto_point(*regex_match.end());
|
2021-01-24 21:45:36 +00:00
|
|
|
ctx.mark_dirty();
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
},
|
2022-06-09 21:08:18 +00:00
|
|
|
Action::Vi(ViAction::CenterAroundViCursor) => {
|
|
|
|
let term = ctx.terminal();
|
|
|
|
let display_offset = term.grid().display_offset() as i32;
|
|
|
|
let target = -display_offset + term.screen_lines() as i32 / 2 - 1;
|
|
|
|
let line = term.vi_mode_cursor.point.line;
|
|
|
|
let scroll_lines = target - line.0;
|
|
|
|
|
|
|
|
ctx.scroll(Scroll::Delta(scroll_lines));
|
|
|
|
},
|
2023-10-20 09:33:38 +00:00
|
|
|
Action::Vi(ViAction::InlineSearchForward) => {
|
|
|
|
ctx.start_inline_search(Direction::Right, false)
|
|
|
|
},
|
|
|
|
Action::Vi(ViAction::InlineSearchBackward) => {
|
|
|
|
ctx.start_inline_search(Direction::Left, false)
|
|
|
|
},
|
|
|
|
Action::Vi(ViAction::InlineSearchForwardShort) => {
|
|
|
|
ctx.start_inline_search(Direction::Right, true)
|
|
|
|
},
|
|
|
|
Action::Vi(ViAction::InlineSearchBackwardShort) => {
|
|
|
|
ctx.start_inline_search(Direction::Left, true)
|
|
|
|
},
|
|
|
|
Action::Vi(ViAction::InlineSearchNext) => ctx.inline_search_next(),
|
|
|
|
Action::Vi(ViAction::InlineSearchPrevious) => ctx.inline_search_previous(),
|
2023-04-09 20:29:48 +00:00
|
|
|
action @ Action::Search(_) if !ctx.search_active() => {
|
|
|
|
debug!("Ignoring {action:?}: Search mode inactive");
|
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Search(SearchAction::SearchFocusNext) => {
|
2020-12-19 04:07:20 +00:00
|
|
|
ctx.advance_search_origin(ctx.search_direction());
|
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Search(SearchAction::SearchFocusPrevious) => {
|
2020-12-19 04:07:20 +00:00
|
|
|
let direction = ctx.search_direction().opposite();
|
|
|
|
ctx.advance_search_origin(direction);
|
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Search(SearchAction::SearchConfirm) => ctx.confirm_search(),
|
|
|
|
Action::Search(SearchAction::SearchCancel) => ctx.cancel_search(),
|
|
|
|
Action::Search(SearchAction::SearchClear) => {
|
2020-12-19 04:07:20 +00:00
|
|
|
let direction = ctx.search_direction();
|
|
|
|
ctx.cancel_search();
|
|
|
|
ctx.start_search(direction);
|
|
|
|
},
|
2021-07-03 03:06:52 +00:00
|
|
|
Action::Search(SearchAction::SearchDeleteWord) => ctx.search_pop_word(),
|
|
|
|
Action::Search(SearchAction::SearchHistoryPrevious) => ctx.search_history_previous(),
|
|
|
|
Action::Search(SearchAction::SearchHistoryNext) => ctx.search_history_next(),
|
2021-07-29 15:40:51 +00:00
|
|
|
Action::Mouse(MouseAction::ExpandSelection) => ctx.expand_selection(),
|
2020-07-15 21:27:32 +00:00
|
|
|
Action::SearchForward => ctx.start_search(Direction::Right),
|
|
|
|
Action::SearchBackward => ctx.start_search(Direction::Left),
|
2020-12-19 04:07:20 +00:00
|
|
|
Action::Copy => ctx.copy_selection(ClipboardType::Clipboard),
|
|
|
|
#[cfg(not(any(target_os = "macos", windows)))]
|
|
|
|
Action::CopySelection => ctx.copy_selection(ClipboardType::Selection),
|
|
|
|
Action::ClearSelection => ctx.clear_selection(),
|
|
|
|
Action::Paste => {
|
|
|
|
let text = ctx.clipboard_mut().load(ClipboardType::Clipboard);
|
2023-04-09 22:44:57 +00:00
|
|
|
ctx.paste(&text, true);
|
2020-12-19 04:07:20 +00:00
|
|
|
},
|
|
|
|
Action::PasteSelection => {
|
|
|
|
let text = ctx.clipboard_mut().load(ClipboardType::Selection);
|
2023-04-09 22:44:57 +00:00
|
|
|
ctx.paste(&text, true);
|
2020-12-19 04:07:20 +00:00
|
|
|
},
|
2021-04-13 03:24:42 +00:00
|
|
|
Action::ToggleFullscreen => ctx.window().toggle_fullscreen(),
|
2022-02-27 18:35:23 +00:00
|
|
|
Action::ToggleMaximized => ctx.window().toggle_maximized(),
|
2019-04-23 19:05:47 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
2021-04-13 03:24:42 +00:00
|
|
|
Action::ToggleSimpleFullscreen => ctx.window().toggle_simple_fullscreen(),
|
2020-02-07 13:44:11 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::Hide => ctx.event_loop().hide_application(),
|
2021-04-08 21:01:47 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::HideOtherApplications => ctx.event_loop().hide_other_applications(),
|
2020-02-07 13:44:11 +00:00
|
|
|
#[cfg(not(target_os = "macos"))]
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::Hide => ctx.window().set_visible(false),
|
2020-01-05 01:54:14 +00:00
|
|
|
Action::Minimize => ctx.window().set_minimized(true),
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::Quit => ctx.terminal_mut().exit(),
|
|
|
|
Action::IncreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP),
|
2023-12-07 21:33:33 +00:00
|
|
|
Action::DecreaseFontSize => ctx.change_font_size(-FONT_SIZE_STEP),
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::ResetFontSize => ctx.reset_font_size(),
|
2023-11-14 19:17:59 +00:00
|
|
|
Action::ScrollPageUp
|
|
|
|
| Action::ScrollPageDown
|
|
|
|
| Action::ScrollHalfPageUp
|
|
|
|
| Action::ScrollHalfPageDown => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = ctx.terminal_mut();
|
2023-11-14 19:17:59 +00:00
|
|
|
let (scroll, amount) = match self {
|
|
|
|
Action::ScrollPageUp => (Scroll::PageUp, term.screen_lines() as i32),
|
|
|
|
Action::ScrollPageDown => (Scroll::PageDown, -(term.screen_lines() as i32)),
|
|
|
|
Action::ScrollHalfPageUp => {
|
|
|
|
let amount = term.screen_lines() as i32 / 2;
|
|
|
|
(Scroll::Delta(amount), amount)
|
|
|
|
},
|
|
|
|
Action::ScrollHalfPageDown => {
|
|
|
|
let amount = -(term.screen_lines() as i32 / 2);
|
|
|
|
(Scroll::Delta(amount), amount)
|
|
|
|
},
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
2020-03-18 02:35:08 +00:00
|
|
|
|
2023-11-14 19:17:59 +00:00
|
|
|
let old_vi_cursor = term.vi_mode_cursor;
|
|
|
|
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, amount);
|
|
|
|
if old_vi_cursor != term.vi_mode_cursor {
|
|
|
|
ctx.mark_dirty();
|
|
|
|
}
|
2020-03-18 02:35:08 +00:00
|
|
|
|
2023-11-14 19:17:59 +00:00
|
|
|
ctx.scroll(scroll);
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
2021-03-30 23:25:38 +00:00
|
|
|
Action::ScrollLineUp => ctx.scroll(Scroll::Delta(1)),
|
|
|
|
Action::ScrollLineDown => ctx.scroll(Scroll::Delta(-1)),
|
2020-03-18 02:35:08 +00:00
|
|
|
Action::ScrollToTop => {
|
|
|
|
ctx.scroll(Scroll::Top);
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2021-03-30 23:25:38 +00:00
|
|
|
let topmost_line = ctx.terminal().topmost_line();
|
|
|
|
ctx.terminal_mut().vi_mode_cursor.point.line = topmost_line;
|
2020-03-18 02:35:08 +00:00
|
|
|
ctx.terminal_mut().vi_motion(ViMotion::FirstOccupied);
|
2021-01-24 21:45:36 +00:00
|
|
|
ctx.mark_dirty();
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
|
|
|
Action::ScrollToBottom => {
|
|
|
|
ctx.scroll(Scroll::Bottom);
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = ctx.terminal_mut();
|
2021-03-30 23:25:38 +00:00
|
|
|
term.vi_mode_cursor.point.line = term.bottommost_line();
|
2020-05-16 00:16:21 +00:00
|
|
|
|
|
|
|
// Move to beginning twice, to always jump across linewraps.
|
|
|
|
term.vi_motion(ViMotion::FirstOccupied);
|
2020-03-18 02:35:08 +00:00
|
|
|
term.vi_motion(ViMotion::FirstOccupied);
|
2021-01-24 21:45:36 +00:00
|
|
|
ctx.mark_dirty();
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::ClearHistory => ctx.terminal_mut().clear_screen(ClearMode::Saved),
|
|
|
|
Action::ClearLogNotice => ctx.pop_message(),
|
2023-07-13 07:46:25 +00:00
|
|
|
#[cfg(not(target_os = "macos"))]
|
2021-10-23 07:16:47 +00:00
|
|
|
Action::CreateNewWindow => ctx.create_new_window(),
|
2023-07-13 07:46:25 +00:00
|
|
|
Action::SpawnNewInstance => ctx.spawn_new_instance(),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::CreateNewWindow => ctx.create_new_window(None),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::CreateNewTab => {
|
|
|
|
let tabbing_id = Some(ctx.window().tabbing_id());
|
|
|
|
ctx.create_new_window(tabbing_id);
|
|
|
|
},
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectNextTab => ctx.window().select_next_tab(),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectPreviousTab => ctx.window().select_previous_tab(),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab1 => ctx.window().select_tab_at_index(0),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab2 => ctx.window().select_tab_at_index(1),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab3 => ctx.window().select_tab_at_index(2),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab4 => ctx.window().select_tab_at_index(3),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab5 => ctx.window().select_tab_at_index(4),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab6 => ctx.window().select_tab_at_index(5),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab7 => ctx.window().select_tab_at_index(6),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab8 => ctx.window().select_tab_at_index(7),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectTab9 => ctx.window().select_tab_at_index(8),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::SelectLastTab => ctx.window().select_last_tab(),
|
2019-09-28 00:37:22 +00:00
|
|
|
Action::ReceiveChar | Action::None => (),
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-01 19:35:37 +00:00
|
|
|
}
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|
|
|
pub fn new(ctx: A) -> Self {
|
|
|
|
Self { ctx, _phantom: Default::default() }
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2016-11-24 04:25:37 +00:00
|
|
|
#[inline]
|
2020-06-18 01:02:56 +00:00
|
|
|
pub fn mouse_moved(&mut self, position: PhysicalPosition<f64>) {
|
2019-11-03 20:59:28 +00:00
|
|
|
let size_info = self.ctx.size_info();
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
let (x, y) = position.into();
|
|
|
|
|
|
|
|
let lmb_pressed = self.ctx.mouse().left_button_state == ElementState::Pressed;
|
2020-07-27 19:05:25 +00:00
|
|
|
let rmb_pressed = self.ctx.mouse().right_button_state == ElementState::Pressed;
|
2020-11-13 05:40:09 +00:00
|
|
|
if !self.ctx.selection_is_empty() && (lmb_pressed || rmb_pressed) {
|
2020-06-18 01:02:56 +00:00
|
|
|
self.update_selection_scrolling(y);
|
|
|
|
}
|
|
|
|
|
2021-04-15 22:16:31 +00:00
|
|
|
let display_offset = self.ctx.terminal().grid().display_offset();
|
|
|
|
let old_point = self.ctx.mouse().point(&size_info, display_offset);
|
|
|
|
|
2022-10-12 04:40:46 +00:00
|
|
|
let x = x.clamp(0, size_info.width() as i32 - 1) as usize;
|
|
|
|
let y = y.clamp(0, size_info.height() as i32 - 1) as usize;
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.mouse_mut().x = x;
|
|
|
|
self.ctx.mouse_mut().y = y;
|
2016-12-12 06:02:03 +00:00
|
|
|
|
2020-08-14 07:26:42 +00:00
|
|
|
let inside_text_area = size_info.contains_point(x, y);
|
2021-03-30 23:25:38 +00:00
|
|
|
let cell_side = self.cell_side(x);
|
2018-03-13 18:00:14 +00:00
|
|
|
|
2021-04-15 22:16:31 +00:00
|
|
|
let point = self.ctx.mouse().point(&size_info, display_offset);
|
|
|
|
let cell_changed = old_point != point;
|
2019-07-08 18:13:55 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// If the mouse hasn't changed cells, do nothing.
|
2019-11-03 20:59:28 +00:00
|
|
|
if !cell_changed
|
|
|
|
&& self.ctx.mouse().cell_side == cell_side
|
2020-08-14 07:26:42 +00:00
|
|
|
&& self.ctx.mouse().inside_text_area == inside_text_area
|
2019-11-03 20:59:28 +00:00
|
|
|
{
|
2019-07-08 18:13:55 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-10-22 19:39:26 +00:00
|
|
|
|
2020-08-14 07:26:42 +00:00
|
|
|
self.ctx.mouse_mut().inside_text_area = inside_text_area;
|
2019-11-03 20:59:28 +00:00
|
|
|
self.ctx.mouse_mut().cell_side = cell_side;
|
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
// Update mouse state and check for URL change.
|
|
|
|
let mouse_state = self.cursor_state();
|
|
|
|
self.ctx.window().set_mouse_cursor(mouse_state);
|
|
|
|
|
|
|
|
// Prompt hint highlight update.
|
|
|
|
self.ctx.mouse_mut().hint_highlight_dirty = true;
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Don't launch URLs if mouse has moved.
|
2021-04-13 03:24:42 +00:00
|
|
|
self.ctx.mouse_mut().block_hint_launcher = true;
|
2019-04-28 22:36:02 +00:00
|
|
|
|
2023-07-11 02:22:14 +00:00
|
|
|
if (lmb_pressed || rmb_pressed)
|
|
|
|
&& (self.ctx.modifiers().state().shift_key() || !self.ctx.mouse_mode())
|
2020-06-24 08:29:07 +00:00
|
|
|
{
|
2020-08-14 07:26:42 +00:00
|
|
|
self.ctx.update_selection(point, cell_side);
|
2020-12-13 02:59:30 +00:00
|
|
|
} else if cell_changed
|
2019-11-03 20:59:28 +00:00
|
|
|
&& self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG)
|
2018-03-13 18:00:14 +00:00
|
|
|
{
|
2020-06-18 01:02:56 +00:00
|
|
|
if lmb_pressed {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(32, ElementState::Pressed);
|
2018-09-27 12:12:49 +00:00
|
|
|
} else if self.ctx.mouse().middle_button_state == ElementState::Pressed {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(33, ElementState::Pressed);
|
2018-09-27 12:12:49 +00:00
|
|
|
} else if self.ctx.mouse().right_button_state == ElementState::Pressed {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(34, ElementState::Pressed);
|
2019-02-07 22:36:45 +00:00
|
|
|
} else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(35, ElementState::Pressed);
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
2016-12-12 06:02:03 +00:00
|
|
|
}
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2021-03-30 23:25:38 +00:00
|
|
|
/// Check which side of a cell an X coordinate lies on.
|
|
|
|
fn cell_side(&self, x: usize) -> Side {
|
2018-10-22 19:39:26 +00:00
|
|
|
let size_info = self.ctx.size_info();
|
|
|
|
|
2020-09-27 22:36:08 +00:00
|
|
|
let cell_x =
|
|
|
|
x.saturating_sub(size_info.padding_x() as usize) % size_info.cell_width() as usize;
|
|
|
|
let half_cell_width = (size_info.cell_width() / 2.0) as usize;
|
2018-10-22 19:39:26 +00:00
|
|
|
|
2019-02-07 22:36:45 +00:00
|
|
|
let additional_padding =
|
2020-09-27 22:36:08 +00:00
|
|
|
(size_info.width() - size_info.padding_x() * 2.) % size_info.cell_width();
|
|
|
|
let end_of_grid = size_info.width() - size_info.padding_x() - additional_padding;
|
2018-10-22 19:39:26 +00:00
|
|
|
|
|
|
|
if cell_x > half_cell_width
|
2020-05-05 22:50:23 +00:00
|
|
|
// Edge case when mouse leaves the window.
|
2018-10-22 19:39:26 +00:00
|
|
|
|| x as f32 >= end_of_grid
|
|
|
|
{
|
|
|
|
Side::Right
|
|
|
|
} else {
|
|
|
|
Side::Left
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 22:16:31 +00:00
|
|
|
fn mouse_report(&mut self, button: u8, state: ElementState) {
|
|
|
|
let display_offset = self.ctx.terminal().grid().display_offset();
|
|
|
|
let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
|
|
|
|
|
|
|
|
// Assure the mouse point is not in the scrollback.
|
2021-04-16 20:23:54 +00:00
|
|
|
if point.line < 0 {
|
2021-04-15 22:16:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate modifiers value.
|
|
|
|
let mut mods = 0;
|
2023-07-11 02:22:14 +00:00
|
|
|
let modifiers = self.ctx.modifiers().state();
|
|
|
|
if modifiers.shift_key() {
|
2021-04-15 22:16:31 +00:00
|
|
|
mods += 4;
|
|
|
|
}
|
2023-07-11 02:22:14 +00:00
|
|
|
if modifiers.alt_key() {
|
2021-04-15 22:16:31 +00:00
|
|
|
mods += 8;
|
|
|
|
}
|
2023-07-11 02:22:14 +00:00
|
|
|
if modifiers.control_key() {
|
2021-04-15 22:16:31 +00:00
|
|
|
mods += 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Report mouse events.
|
|
|
|
if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
|
|
|
|
self.sgr_mouse_report(point, button + mods, state);
|
|
|
|
} else if let ElementState::Released = state {
|
|
|
|
self.normal_mouse_report(point, 3 + mods);
|
|
|
|
} else {
|
|
|
|
self.normal_mouse_report(point, button + mods);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn normal_mouse_report(&mut self, point: Point, button: u8) {
|
|
|
|
let Point { line, column } = point;
|
2019-11-04 19:41:13 +00:00
|
|
|
let utf8 = self.ctx.terminal().mode().contains(TermMode::UTF8_MOUSE);
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2019-11-04 19:41:13 +00:00
|
|
|
let max_point = if utf8 { 2015 } else { 223 };
|
|
|
|
|
2021-04-15 22:16:31 +00:00
|
|
|
if line >= max_point || column >= max_point {
|
2019-11-04 19:41:13 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
|
|
|
|
|
|
|
|
let mouse_pos_encode = |pos: usize| -> Vec<u8> {
|
|
|
|
let pos = 32 + 1 + pos;
|
|
|
|
let first = 0xC0 + pos / 64;
|
|
|
|
let second = 0x80 + (pos & 63);
|
|
|
|
vec![first as u8, second as u8]
|
|
|
|
};
|
|
|
|
|
|
|
|
if utf8 && column >= Column(95) {
|
|
|
|
msg.append(&mut mouse_pos_encode(column.0));
|
|
|
|
} else {
|
|
|
|
msg.push(32 + 1 + column.0 as u8);
|
|
|
|
}
|
|
|
|
|
2021-03-30 23:25:38 +00:00
|
|
|
if utf8 && line >= 95 {
|
2021-04-15 22:16:31 +00:00
|
|
|
msg.append(&mut mouse_pos_encode(line.0 as usize));
|
2019-11-04 19:41:13 +00:00
|
|
|
} else {
|
2021-04-15 22:16:31 +00:00
|
|
|
msg.push(32 + 1 + line.0 as u8);
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2019-11-04 19:41:13 +00:00
|
|
|
|
|
|
|
self.ctx.write_to_pty(msg);
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2021-04-15 22:16:31 +00:00
|
|
|
fn sgr_mouse_report(&mut self, point: Point, button: u8, state: ElementState) {
|
2018-01-06 19:39:33 +00:00
|
|
|
let c = match state {
|
|
|
|
ElementState::Pressed => 'M',
|
|
|
|
ElementState::Released => 'm',
|
|
|
|
};
|
2017-01-02 02:28:49 +00:00
|
|
|
|
2021-04-15 22:16:31 +00:00
|
|
|
let msg = format!("\x1b[<{};{};{}{}", button, point.column + 1, point.line + 1, c);
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.write_to_pty(msg.into_bytes());
|
2017-01-02 02:28:49 +00:00
|
|
|
}
|
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
fn on_mouse_press(&mut self, button: MouseButton) {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Handle mouse mode.
|
2023-07-11 02:22:14 +00:00
|
|
|
if !self.ctx.modifiers().state().shift_key() && self.ctx.mouse_mode() {
|
2020-04-10 18:23:50 +00:00
|
|
|
self.ctx.mouse_mut().click_state = ClickState::None;
|
|
|
|
|
|
|
|
let code = match button {
|
|
|
|
MouseButton::Left => 0,
|
|
|
|
MouseButton::Middle => 1,
|
|
|
|
MouseButton::Right => 2,
|
2020-05-05 22:50:23 +00:00
|
|
|
// Can't properly report more than three buttons..
|
2023-07-11 02:22:14 +00:00
|
|
|
MouseButton::Back | MouseButton::Forward | MouseButton::Other(_) => return,
|
2020-04-10 18:23:50 +00:00
|
|
|
};
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
self.mouse_report(code, ElementState::Pressed);
|
|
|
|
} else {
|
2020-06-23 09:57:15 +00:00
|
|
|
// Calculate time since the last click to handle double/triple clicks.
|
|
|
|
let now = Instant::now();
|
|
|
|
let elapsed = now - self.ctx.mouse().last_click_timestamp;
|
|
|
|
self.ctx.mouse_mut().last_click_timestamp = now;
|
|
|
|
|
|
|
|
// Update multi-click state.
|
|
|
|
self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
|
|
|
|
// Reset click state if button has changed.
|
|
|
|
_ if button != self.ctx.mouse().last_click_button => {
|
|
|
|
self.ctx.mouse_mut().last_click_button = button;
|
|
|
|
ClickState::Click
|
|
|
|
},
|
2023-06-29 13:17:54 +00:00
|
|
|
ClickState::Click if elapsed < CLICK_THRESHOLD => ClickState::DoubleClick,
|
|
|
|
ClickState::DoubleClick if elapsed < CLICK_THRESHOLD => ClickState::TripleClick,
|
2020-06-23 09:57:15 +00:00
|
|
|
_ => ClickState::Click,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Load mouse point, treating message bar and padding as the closest cell.
|
2021-03-30 23:25:38 +00:00
|
|
|
let display_offset = self.ctx.terminal().grid().display_offset();
|
2021-04-15 22:16:31 +00:00
|
|
|
let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
|
2020-06-23 09:57:15 +00:00
|
|
|
|
2021-07-29 15:40:51 +00:00
|
|
|
if let MouseButton::Left = button {
|
|
|
|
self.on_left_click(point)
|
2020-06-23 09:57:15 +00:00
|
|
|
}
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 09:57:15 +00:00
|
|
|
/// Handle left click selection and vi mode cursor movement.
|
|
|
|
fn on_left_click(&mut self, point: Point) {
|
2020-04-10 18:23:50 +00:00
|
|
|
let side = self.ctx.mouse().cell_side;
|
|
|
|
|
2020-06-23 09:57:15 +00:00
|
|
|
match self.ctx.mouse().click_state {
|
|
|
|
ClickState::Click => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Don't launch URLs if this click cleared the selection.
|
2021-04-13 03:24:42 +00:00
|
|
|
self.ctx.mouse_mut().block_hint_launcher = !self.ctx.selection_is_empty();
|
2020-03-19 12:39:00 +00:00
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
self.ctx.clear_selection();
|
2020-03-18 02:35:08 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Start new empty selection.
|
2023-07-11 02:22:14 +00:00
|
|
|
if self.ctx.modifiers().state().control_key() {
|
2020-04-10 18:23:50 +00:00
|
|
|
self.ctx.start_selection(SelectionType::Block, point, side);
|
|
|
|
} else {
|
|
|
|
self.ctx.start_selection(SelectionType::Simple, point, side);
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
2019-03-30 16:48:36 +00:00
|
|
|
},
|
2020-06-23 09:57:15 +00:00
|
|
|
ClickState::DoubleClick => {
|
2021-04-13 03:24:42 +00:00
|
|
|
self.ctx.mouse_mut().block_hint_launcher = true;
|
2020-06-23 09:57:15 +00:00
|
|
|
self.ctx.start_selection(SelectionType::Semantic, point, side);
|
|
|
|
},
|
|
|
|
ClickState::TripleClick => {
|
2021-04-13 03:24:42 +00:00
|
|
|
self.ctx.mouse_mut().block_hint_launcher = true;
|
2020-06-23 09:57:15 +00:00
|
|
|
self.ctx.start_selection(SelectionType::Lines, point, side);
|
|
|
|
},
|
|
|
|
ClickState::None => (),
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
2020-04-10 18:23:50 +00:00
|
|
|
|
2020-06-24 08:29:07 +00:00
|
|
|
// Move vi mode cursor to mouse click position.
|
2020-11-13 05:40:09 +00:00
|
|
|
if self.ctx.terminal().mode().contains(TermMode::VI) && !self.ctx.search_active() {
|
2020-04-10 18:23:50 +00:00
|
|
|
self.ctx.terminal_mut().vi_mode_cursor.point = point;
|
2022-07-24 23:17:57 +00:00
|
|
|
self.ctx.mark_dirty();
|
2020-04-10 18:23:50 +00:00
|
|
|
}
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
fn on_mouse_release(&mut self, button: MouseButton) {
|
2023-07-11 02:22:14 +00:00
|
|
|
if !self.ctx.modifiers().state().shift_key() && self.ctx.mouse_mode() {
|
2019-02-07 22:36:45 +00:00
|
|
|
let code = match button {
|
|
|
|
MouseButton::Left => 0,
|
|
|
|
MouseButton::Middle => 1,
|
|
|
|
MouseButton::Right => 2,
|
2018-01-26 21:28:43 +00:00
|
|
|
// Can't properly report more than three buttons.
|
2023-07-11 02:22:14 +00:00
|
|
|
MouseButton::Back | MouseButton::Forward | MouseButton::Other(_) => return,
|
2018-01-26 21:28:43 +00:00
|
|
|
};
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(code, ElementState::Released);
|
2016-12-22 18:43:06 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-12-30 01:39:30 +00:00
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
// Trigger hints highlighted by the mouse.
|
|
|
|
let hint = self.ctx.display().highlighted_hint.take();
|
|
|
|
if let Some(hint) = hint.as_ref().filter(|_| button == MouseButton::Left) {
|
|
|
|
self.ctx.trigger_hint(hint);
|
|
|
|
}
|
|
|
|
self.ctx.display().highlighted_hint = hint;
|
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
let timer_id = TimerId::new(Topic::SelectionScrolling, self.ctx.window().id());
|
|
|
|
self.ctx.scheduler_mut().unschedule(timer_id);
|
2021-04-17 19:28:23 +00:00
|
|
|
|
2022-04-16 18:39:26 +00:00
|
|
|
if let MouseButton::Left | MouseButton::Right = button {
|
|
|
|
// Copy selection on release, to prevent flooding the display server.
|
|
|
|
self.ctx.copy_selection(ClipboardType::Selection);
|
|
|
|
}
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
pub fn mouse_wheel_input(&mut self, delta: MouseScrollDelta, phase: TouchPhase) {
|
2023-11-10 14:16:22 +00:00
|
|
|
let multiplier = self.ctx.config().scrolling.multiplier;
|
2016-11-25 03:50:34 +00:00
|
|
|
match delta {
|
2023-01-16 17:22:01 +00:00
|
|
|
MouseScrollDelta::LineDelta(columns, lines) => {
|
|
|
|
let new_scroll_px_x = columns * self.ctx.size_info().cell_width();
|
|
|
|
let new_scroll_px_y = lines * self.ctx.size_info().cell_height();
|
2023-08-05 19:08:38 +00:00
|
|
|
self.scroll_terminal(
|
|
|
|
new_scroll_px_x as f64,
|
|
|
|
new_scroll_px_y as f64,
|
|
|
|
multiplier as f64,
|
|
|
|
);
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
2023-02-19 14:38:57 +00:00
|
|
|
MouseScrollDelta::PixelDelta(mut lpos) => {
|
2016-11-25 03:50:34 +00:00
|
|
|
match phase {
|
|
|
|
TouchPhase::Started => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Reset offset to zero.
|
2023-01-16 17:22:01 +00:00
|
|
|
self.ctx.mouse_mut().accumulated_scroll = Default::default();
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
|
|
|
TouchPhase::Moved => {
|
2023-02-19 14:38:57 +00:00
|
|
|
// When the angle between (x, 0) and (x, y) is lower than ~25 degrees
|
|
|
|
// (cosine is larger that 0.9) we consider this scrolling as horizontal.
|
|
|
|
if lpos.x.abs() / lpos.x.hypot(lpos.y) > 0.9 {
|
|
|
|
lpos.y = 0.;
|
|
|
|
} else {
|
|
|
|
lpos.x = 0.;
|
|
|
|
}
|
|
|
|
|
2023-08-05 19:08:38 +00:00
|
|
|
self.scroll_terminal(lpos.x, lpos.y, multiplier as f64);
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
|
|
|
_ => (),
|
|
|
|
}
|
2019-03-30 16:48:36 +00:00
|
|
|
},
|
2016-11-25 03:50:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-05 19:08:38 +00:00
|
|
|
fn scroll_terminal(&mut self, new_scroll_x_px: f64, new_scroll_y_px: f64, multiplier: f64) {
|
2023-01-16 17:22:01 +00:00
|
|
|
const MOUSE_WHEEL_UP: u8 = 64;
|
|
|
|
const MOUSE_WHEEL_DOWN: u8 = 65;
|
|
|
|
const MOUSE_WHEEL_LEFT: u8 = 66;
|
|
|
|
const MOUSE_WHEEL_RIGHT: u8 = 67;
|
|
|
|
|
|
|
|
let width = f64::from(self.ctx.size_info().cell_width());
|
2020-09-27 22:36:08 +00:00
|
|
|
let height = f64::from(self.ctx.size_info().cell_height());
|
2018-03-09 22:02:45 +00:00
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
if self.ctx.mouse_mode() {
|
2023-01-16 17:22:01 +00:00
|
|
|
self.ctx.mouse_mut().accumulated_scroll.x += new_scroll_x_px;
|
|
|
|
self.ctx.mouse_mut().accumulated_scroll.y += new_scroll_y_px;
|
2019-01-27 21:06:27 +00:00
|
|
|
|
2023-01-16 17:22:01 +00:00
|
|
|
let code = if new_scroll_y_px > 0. { MOUSE_WHEEL_UP } else { MOUSE_WHEEL_DOWN };
|
|
|
|
let lines = (self.ctx.mouse().accumulated_scroll.y / height).abs() as i32;
|
2019-01-27 21:06:27 +00:00
|
|
|
|
|
|
|
for _ in 0..lines {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(code, ElementState::Pressed);
|
2019-01-27 21:06:27 +00:00
|
|
|
}
|
2023-01-16 17:22:01 +00:00
|
|
|
|
|
|
|
let code = if new_scroll_x_px > 0. { MOUSE_WHEEL_LEFT } else { MOUSE_WHEEL_RIGHT };
|
|
|
|
let columns = (self.ctx.mouse().accumulated_scroll.x / width).abs() as i32;
|
|
|
|
|
|
|
|
for _ in 0..columns {
|
|
|
|
self.mouse_report(code, ElementState::Pressed);
|
|
|
|
}
|
2019-11-03 20:59:28 +00:00
|
|
|
} else if self
|
|
|
|
.ctx
|
|
|
|
.terminal()
|
|
|
|
.mode()
|
|
|
|
.contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
|
2023-07-11 02:22:14 +00:00
|
|
|
&& !self.ctx.modifiers().state().shift_key()
|
2019-11-03 20:59:28 +00:00
|
|
|
{
|
2023-01-16 17:22:01 +00:00
|
|
|
self.ctx.mouse_mut().accumulated_scroll.x += new_scroll_x_px * multiplier;
|
|
|
|
self.ctx.mouse_mut().accumulated_scroll.y += new_scroll_y_px * multiplier;
|
|
|
|
|
|
|
|
// The chars here are the same as for the respective arrow keys.
|
|
|
|
let line_cmd = if new_scroll_y_px > 0. { b'A' } else { b'B' };
|
|
|
|
let column_cmd = if new_scroll_x_px > 0. { b'D' } else { b'C' };
|
|
|
|
|
|
|
|
let lines = (self.ctx.mouse().accumulated_scroll.y / height).abs() as usize;
|
|
|
|
let columns = (self.ctx.mouse().accumulated_scroll.x / width).abs() as usize;
|
|
|
|
|
|
|
|
let mut content = Vec::with_capacity(3 * (lines + columns));
|
2019-01-27 21:06:27 +00:00
|
|
|
|
|
|
|
for _ in 0..lines {
|
2018-03-09 10:17:47 +00:00
|
|
|
content.push(0x1b);
|
|
|
|
content.push(b'O');
|
2023-01-16 17:22:01 +00:00
|
|
|
content.push(line_cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
for _ in 0..columns {
|
|
|
|
content.push(0x1b);
|
|
|
|
content.push(b'O');
|
|
|
|
content.push(column_cmd);
|
2018-03-09 10:17:47 +00:00
|
|
|
}
|
2023-01-16 17:22:01 +00:00
|
|
|
|
2018-03-09 10:17:47 +00:00
|
|
|
self.ctx.write_to_pty(content);
|
2018-02-16 02:35:49 +00:00
|
|
|
} else {
|
2023-01-16 17:22:01 +00:00
|
|
|
self.ctx.mouse_mut().accumulated_scroll.y += new_scroll_y_px * multiplier;
|
2019-01-27 21:06:27 +00:00
|
|
|
|
2023-01-16 17:22:01 +00:00
|
|
|
let lines = (self.ctx.mouse().accumulated_scroll.y / height) as i32;
|
2019-01-27 21:06:27 +00:00
|
|
|
|
2022-07-24 23:17:57 +00:00
|
|
|
if lines != 0 {
|
|
|
|
self.ctx.scroll(Scroll::Delta(lines));
|
|
|
|
}
|
2017-12-25 21:15:41 +00:00
|
|
|
}
|
2019-01-27 21:06:27 +00:00
|
|
|
|
2023-01-16 17:22:01 +00:00
|
|
|
self.ctx.mouse_mut().accumulated_scroll.x %= width;
|
|
|
|
self.ctx.mouse_mut().accumulated_scroll.y %= height;
|
2017-12-25 21:15:41 +00:00
|
|
|
}
|
|
|
|
|
2017-05-29 16:51:49 +00:00
|
|
|
pub fn on_focus_change(&mut self, is_focused: bool) {
|
2019-02-07 22:36:45 +00:00
|
|
|
if self.ctx.terminal().mode().contains(TermMode::FOCUS_IN_OUT) {
|
2019-03-30 16:48:36 +00:00
|
|
|
let chr = if is_focused { "I" } else { "O" };
|
2017-05-29 16:51:49 +00:00
|
|
|
|
|
|
|
let msg = format!("\x1b[{}", chr);
|
|
|
|
self.ctx.write_to_pty(msg.into_bytes());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-13 22:35:18 +00:00
|
|
|
/// 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) => {
|
2023-07-24 06:11:25 +00:00
|
|
|
let mut set = HashSet::default();
|
2023-02-13 22:35:18 +00:00
|
|
|
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);
|
2023-12-14 03:15:40 +00:00
|
|
|
self.ctx.change_font_size(font_delta);
|
2023-02-13 22:35:18 +00:00
|
|
|
},
|
|
|
|
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);
|
|
|
|
|
2023-08-05 19:08:38 +00:00
|
|
|
// Use a fixed scroll factor for touchscreens, to accurately track finger motion.
|
|
|
|
self.scroll_terminal(0., delta_y, 1.0);
|
2023-02-13 22:35:18 +00:00
|
|
|
},
|
|
|
|
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 => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-11 02:22:14 +00:00
|
|
|
/// Reset mouse cursor based on modifier and terminal state.
|
|
|
|
#[inline]
|
|
|
|
pub fn reset_mouse_cursor(&mut self) {
|
|
|
|
let mouse_state = self.cursor_state();
|
|
|
|
self.ctx.window().set_mouse_cursor(mouse_state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Modifier state change.
|
|
|
|
pub fn modifiers_input(&mut self, modifiers: Modifiers) {
|
|
|
|
*self.ctx.modifiers() = modifiers;
|
|
|
|
|
|
|
|
// Prompt hint highlight update.
|
|
|
|
self.ctx.mouse_mut().hint_highlight_dirty = true;
|
|
|
|
|
|
|
|
// Update mouse state and check for URL change.
|
|
|
|
let mouse_state = self.cursor_state();
|
|
|
|
self.ctx.window().set_mouse_cursor(mouse_state);
|
|
|
|
}
|
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
pub fn mouse_input(&mut self, state: ElementState, button: MouseButton) {
|
2019-01-03 01:56:28 +00:00
|
|
|
match button {
|
|
|
|
MouseButton::Left => self.ctx.mouse_mut().left_button_state = state,
|
|
|
|
MouseButton::Middle => self.ctx.mouse_mut().middle_button_state = state,
|
|
|
|
MouseButton::Right => self.ctx.mouse_mut().right_button_state = state,
|
|
|
|
_ => (),
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Skip normal mouse events if the message bar has been clicked.
|
2023-07-11 02:22:14 +00:00
|
|
|
if self.message_bar_cursor_state() == Some(CursorIcon::Pointer)
|
2020-09-27 22:36:08 +00:00
|
|
|
&& state == ElementState::Pressed
|
|
|
|
{
|
|
|
|
let size = self.ctx.size_info();
|
|
|
|
|
2022-07-20 08:24:27 +00:00
|
|
|
let current_lines = self.ctx.message().map_or(0, |m| m.text(&size).len());
|
2020-09-27 22:36:08 +00:00
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
self.ctx.clear_selection();
|
|
|
|
self.ctx.pop_message();
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Reset cursor when message bar height changed or all messages are gone.
|
2022-07-20 08:24:27 +00:00
|
|
|
let new_lines = self.ctx.message().map_or(0, |m| m.text(&size).len());
|
2019-11-03 20:59:28 +00:00
|
|
|
|
|
|
|
let new_icon = match current_lines.cmp(&new_lines) {
|
|
|
|
Ordering::Less => CursorIcon::Default,
|
2023-07-11 02:22:14 +00:00
|
|
|
Ordering::Equal => CursorIcon::Pointer,
|
2019-11-03 23:14:23 +00:00
|
|
|
Ordering::Greater => {
|
2020-03-18 02:35:08 +00:00
|
|
|
if self.ctx.mouse_mode() {
|
2019-11-03 23:14:23 +00:00
|
|
|
CursorIcon::Default
|
|
|
|
} else {
|
|
|
|
CursorIcon::Text
|
|
|
|
}
|
2019-11-03 20:59:28 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
self.ctx.window().set_mouse_cursor(new_icon);
|
2020-11-13 05:40:09 +00:00
|
|
|
} else {
|
2019-02-07 22:36:45 +00:00
|
|
|
match state {
|
|
|
|
ElementState::Pressed => {
|
2021-07-29 15:40:51 +00:00
|
|
|
// Process mouse press before bindings to update the `click_state`.
|
2020-01-05 01:00:50 +00:00
|
|
|
self.on_mouse_press(button);
|
2021-07-29 15:40:51 +00:00
|
|
|
self.process_mouse_bindings(button);
|
2019-02-07 22:36:45 +00:00
|
|
|
},
|
2020-01-05 01:00:50 +00:00
|
|
|
ElementState::Released => self.on_mouse_release(button),
|
2019-02-07 22:36:45 +00:00
|
|
|
}
|
2016-09-27 05:22:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-11 02:22:14 +00:00
|
|
|
/// Attempt to find a binding and execute its action.
|
|
|
|
///
|
|
|
|
/// The provided mode, mods, and key must match what is allowed by a binding
|
|
|
|
/// for its action to be executed.
|
|
|
|
fn process_mouse_bindings(&mut self, button: MouseButton) {
|
|
|
|
let mode = BindingMode::new(self.ctx.terminal().mode(), self.ctx.search_active());
|
|
|
|
let mouse_mode = self.ctx.mouse_mode();
|
|
|
|
let mods = self.ctx.modifiers().state();
|
2023-10-25 00:04:32 +00:00
|
|
|
let mouse_bindings = self.ctx.config().mouse_bindings().to_owned();
|
2023-07-11 02:22:14 +00:00
|
|
|
|
2023-10-25 00:04:32 +00:00
|
|
|
// If mouse mode is active, also look for bindings without shift.
|
|
|
|
let mut check_fallback = mouse_mode && mods.contains(ModifiersState::SHIFT);
|
2023-07-11 02:22:14 +00:00
|
|
|
|
2023-10-25 00:04:32 +00:00
|
|
|
for binding in &mouse_bindings {
|
2023-12-08 05:09:01 +00:00
|
|
|
// Don't trigger normal bindings in mouse mode unless Shift is pressed.
|
|
|
|
if binding.is_triggered_by(mode, mods, &button) && (check_fallback || !mouse_mode) {
|
2023-07-11 02:22:14 +00:00
|
|
|
binding.action.execute(&mut self.ctx);
|
2023-10-25 00:04:32 +00:00
|
|
|
check_fallback = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if check_fallback {
|
|
|
|
let fallback_mods = mods & !ModifiersState::SHIFT;
|
|
|
|
for binding in &mouse_bindings {
|
|
|
|
if binding.is_triggered_by(mode, fallback_mods, &button) {
|
|
|
|
binding.action.execute(&mut self.ctx);
|
|
|
|
}
|
2023-07-11 02:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
/// Check mouse icon state in relation to the message bar.
|
|
|
|
fn message_bar_cursor_state(&self) -> Option<CursorIcon> {
|
2020-07-09 21:45:22 +00:00
|
|
|
// Since search is above the message bar, the button is offset by search's height.
|
2022-10-12 04:40:46 +00:00
|
|
|
let search_height = usize::from(self.ctx.search_active());
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2020-09-27 22:36:08 +00:00
|
|
|
// Calculate Y position of the end of the last terminal line.
|
|
|
|
let size = self.ctx.size_info();
|
|
|
|
let terminal_end = size.padding_y() as usize
|
2021-03-30 23:25:38 +00:00
|
|
|
+ size.cell_height() as usize * (size.screen_lines() + search_height);
|
2020-09-27 22:36:08 +00:00
|
|
|
|
|
|
|
let mouse = self.ctx.mouse();
|
2021-04-15 22:16:31 +00:00
|
|
|
let display_offset = self.ctx.terminal().grid().display_offset();
|
|
|
|
let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
|
|
|
|
|
2020-09-27 22:36:08 +00:00
|
|
|
if self.ctx.message().is_none() || (mouse.y <= terminal_end) {
|
|
|
|
None
|
|
|
|
} else if mouse.y <= terminal_end + size.cell_height() as usize
|
2021-04-15 22:16:31 +00:00
|
|
|
&& point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns()
|
2020-09-27 22:36:08 +00:00
|
|
|
{
|
2023-07-11 02:22:14 +00:00
|
|
|
Some(CursorIcon::Pointer)
|
2020-09-27 22:36:08 +00:00
|
|
|
} else {
|
2021-04-13 03:24:42 +00:00
|
|
|
Some(CursorIcon::Default)
|
2019-11-03 20:59:28 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
2019-08-24 23:18:51 +00:00
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
/// Icon state of the cursor.
|
|
|
|
fn cursor_state(&mut self) -> CursorIcon {
|
|
|
|
let display_offset = self.ctx.terminal().grid().display_offset();
|
2021-04-15 22:16:31 +00:00
|
|
|
let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
|
2022-07-10 17:11:28 +00:00
|
|
|
let hyperlink = self.ctx.terminal().grid()[point].hyperlink();
|
2021-04-15 22:16:31 +00:00
|
|
|
|
|
|
|
// Function to check if mouse is on top of a hint.
|
2022-07-10 17:11:28 +00:00
|
|
|
let hint_highlighted = |hint: &HintMatch| hint.should_highlight(point, hyperlink.as_ref());
|
2019-11-11 21:05:24 +00:00
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
if let Some(mouse_state) = self.message_bar_cursor_state() {
|
|
|
|
mouse_state
|
|
|
|
} else if self.ctx.display().highlighted_hint.as_ref().map_or(false, hint_highlighted) {
|
2023-07-11 02:22:14 +00:00
|
|
|
CursorIcon::Pointer
|
|
|
|
} else if !self.ctx.modifiers().state().shift_key() && self.ctx.mouse_mode() {
|
2021-04-13 03:24:42 +00:00
|
|
|
CursorIcon::Default
|
2019-11-11 21:05:24 +00:00
|
|
|
} else {
|
2021-04-13 03:24:42 +00:00
|
|
|
CursorIcon::Text
|
2019-11-11 21:05:24 +00:00
|
|
|
}
|
2019-08-24 23:18:51 +00:00
|
|
|
}
|
2020-06-18 01:02:56 +00:00
|
|
|
|
|
|
|
/// Handle automatic scrolling when selecting above/below the window.
|
|
|
|
fn update_selection_scrolling(&mut self, mouse_y: i32) {
|
2022-02-17 22:27:10 +00:00
|
|
|
let scale_factor = self.ctx.window().scale_factor;
|
2020-09-27 22:36:08 +00:00
|
|
|
let size = self.ctx.size_info();
|
2021-10-23 07:16:47 +00:00
|
|
|
let window_id = self.ctx.window().id();
|
2020-06-18 01:02:56 +00:00
|
|
|
let scheduler = self.ctx.scheduler_mut();
|
|
|
|
|
|
|
|
// Scale constants by DPI.
|
2022-02-17 22:27:10 +00:00
|
|
|
let min_height = (MIN_SELECTION_SCROLLING_HEIGHT * scale_factor) as i32;
|
|
|
|
let step = (SELECTION_SCROLLING_STEP * scale_factor) as i32;
|
2020-06-18 01:02:56 +00:00
|
|
|
|
|
|
|
// Compute the height of the scrolling areas.
|
2020-09-27 22:36:08 +00:00
|
|
|
let end_top = max(min_height, size.padding_y() as i32);
|
2021-03-30 23:25:38 +00:00
|
|
|
let text_area_bottom = size.padding_y() + size.screen_lines() as f32 * size.cell_height();
|
2020-09-27 22:36:08 +00:00
|
|
|
let start_bottom = min(size.height() as i32 - min_height, text_area_bottom as i32);
|
2020-06-18 01:02:56 +00:00
|
|
|
|
|
|
|
// 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 {
|
2021-10-23 07:16:47 +00:00
|
|
|
scheduler.unschedule(TimerId::new(Topic::SelectionScrolling, window_id));
|
2020-06-18 01:02:56 +00:00
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Scale number of lines scrolled based on distance to boundary.
|
2022-10-12 04:40:46 +00:00
|
|
|
let event = Event::new(EventType::Scroll(Scroll::Delta(delta / step)), Some(window_id));
|
2020-06-18 01:02:56 +00:00
|
|
|
|
|
|
|
// Schedule event.
|
2021-10-23 07:16:47 +00:00
|
|
|
let timer_id = TimerId::new(Topic::SelectionScrolling, window_id);
|
|
|
|
scheduler.unschedule(timer_id);
|
|
|
|
scheduler.schedule(event, SELECTION_SCROLLING_INTERVAL, true, timer_id);
|
2020-06-18 01:02:56 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-06-18 01:02:56 +00:00
|
|
|
use super::*;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2023-07-11 02:22:14 +00:00
|
|
|
use winit::event::{DeviceId, Event as WinitEvent, WindowEvent};
|
|
|
|
use winit::keyboard::Key;
|
2022-11-03 16:37:54 +00:00
|
|
|
use winit::window::WindowId;
|
2016-09-27 15:27:39 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
use alacritty_terminal::event::Event as TerminalEvent;
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2021-03-01 19:50:39 +00:00
|
|
|
use crate::config::Binding;
|
2020-07-11 17:03:09 +00:00
|
|
|
use crate::message_bar::MessageBuffer;
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2023-07-11 02:22:14 +00:00
|
|
|
const KEY: Key<&'static str> = Key::Character("0");
|
2016-11-15 17:38:50 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
struct MockEventProxy;
|
2021-01-24 21:45:36 +00:00
|
|
|
impl EventListener for MockEventProxy {}
|
2019-07-15 21:32:48 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
struct ActionContext<'a, T> {
|
|
|
|
pub terminal: &'a mut Term<T>,
|
2017-01-15 01:53:48 +00:00
|
|
|
pub size_info: &'a SizeInfo,
|
|
|
|
pub mouse: &'a mut Mouse,
|
2020-06-06 21:33:20 +00:00
|
|
|
pub clipboard: &'a mut Clipboard,
|
2019-10-05 00:29:26 +00:00
|
|
|
pub message_buffer: &'a mut MessageBuffer,
|
2023-07-11 02:22:14 +00:00
|
|
|
pub modifiers: Modifiers,
|
2021-11-22 18:34:09 +00:00
|
|
|
config: &'a UiConfig,
|
2023-10-20 09:33:38 +00:00
|
|
|
inline_search_state: &'a mut InlineSearchState,
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
impl<'a, T: EventListener> super::ActionContext<T> for ActionContext<'a, T> {
|
2021-01-24 21:45:36 +00:00
|
|
|
fn search_next(
|
|
|
|
&mut self,
|
2021-03-30 23:25:38 +00:00
|
|
|
_origin: Point,
|
2021-01-24 21:45:36 +00:00
|
|
|
_direction: Direction,
|
|
|
|
_side: Side,
|
|
|
|
) -> Option<Match> {
|
|
|
|
None
|
|
|
|
}
|
2020-07-15 21:27:32 +00:00
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
fn search_direction(&self) -> Direction {
|
|
|
|
Direction::Right
|
|
|
|
}
|
|
|
|
|
2023-10-20 09:33:38 +00:00
|
|
|
fn inline_search_state(&mut self) -> &mut InlineSearchState {
|
|
|
|
self.inline_search_state
|
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
fn search_active(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
fn terminal(&self) -> &Term<T> {
|
2021-07-03 03:06:52 +00:00
|
|
|
self.terminal
|
2019-02-07 22:36:45 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
fn terminal_mut(&mut self) -> &mut Term<T> {
|
2021-12-23 10:23:06 +00:00
|
|
|
self.terminal
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn size_info(&self) -> SizeInfo {
|
|
|
|
*self.size_info
|
|
|
|
}
|
|
|
|
|
2018-10-22 19:39:26 +00:00
|
|
|
fn selection_is_empty(&self) -> bool {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2018-03-25 19:09:18 +00:00
|
|
|
fn scroll(&mut self, scroll: Scroll) {
|
|
|
|
self.terminal.scroll_display(scroll);
|
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
fn mouse_mode(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
#[inline]
|
|
|
|
fn mouse_mut(&mut self) -> &mut Mouse {
|
|
|
|
self.mouse
|
|
|
|
}
|
2018-09-27 12:12:49 +00:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn mouse(&self) -> &Mouse {
|
|
|
|
self.mouse
|
|
|
|
}
|
|
|
|
|
2023-02-13 22:35:18 +00:00
|
|
|
#[inline]
|
|
|
|
fn touch_purpose(&mut self) -> &mut TouchPurpose {
|
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
|
2023-07-11 02:22:14 +00:00
|
|
|
fn modifiers(&mut self) -> &mut Modifiers {
|
2019-08-24 23:18:51 +00:00
|
|
|
&mut self.modifiers
|
2017-07-20 17:50:50 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
fn window(&mut self) -> &mut Window {
|
2019-10-05 00:29:26 +00:00
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
fn display(&mut self) -> &mut Display {
|
2019-10-05 00:29:26 +00:00
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pop_message(&mut self) {
|
|
|
|
self.message_buffer.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn message(&self) -> Option<&Message> {
|
|
|
|
self.message_buffer.message()
|
|
|
|
}
|
2019-11-03 19:02:26 +00:00
|
|
|
|
2021-11-22 18:34:09 +00:00
|
|
|
fn config(&self) -> &UiConfig {
|
2019-11-03 19:02:26 +00:00
|
|
|
self.config
|
|
|
|
}
|
2020-02-07 13:44:11 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
fn clipboard_mut(&mut self) -> &mut Clipboard {
|
|
|
|
self.clipboard
|
|
|
|
}
|
|
|
|
|
|
|
|
fn event_loop(&self) -> &EventLoopWindowTarget<Event> {
|
2020-02-07 13:44:11 +00:00
|
|
|
unimplemented!();
|
|
|
|
}
|
2020-03-18 02:35:08 +00:00
|
|
|
|
2020-06-23 09:57:15 +00:00
|
|
|
fn scheduler_mut(&mut self) -> &mut Scheduler {
|
2020-03-18 02:35:08 +00:00
|
|
|
unimplemented!();
|
|
|
|
}
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! test_clickstate {
|
|
|
|
{
|
|
|
|
name: $name:ident,
|
|
|
|
initial_state: $initial_state:expr,
|
2019-01-08 07:37:58 +00:00
|
|
|
initial_button: $initial_button:expr,
|
2017-01-15 01:53:48 +00:00
|
|
|
input: $input:expr,
|
2020-04-10 18:23:50 +00:00
|
|
|
end_state: $end_state:expr,
|
2023-06-29 13:17:54 +00:00
|
|
|
input_delay: $input_delay:expr,
|
2017-01-15 01:53:48 +00:00
|
|
|
} => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
2020-12-21 02:44:38 +00:00
|
|
|
let mut clipboard = Clipboard::new_nop();
|
2021-11-22 18:34:09 +00:00
|
|
|
let cfg = UiConfig::default();
|
2020-09-27 22:36:08 +00:00
|
|
|
let size = SizeInfo::new(
|
|
|
|
21.0,
|
|
|
|
51.0,
|
|
|
|
3.0,
|
|
|
|
3.0,
|
|
|
|
0.,
|
|
|
|
0.,
|
|
|
|
false,
|
|
|
|
);
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2023-11-10 14:16:22 +00:00
|
|
|
let mut terminal = Term::new(cfg.term_options(), &size, MockEventProxy);
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2021-01-01 05:07:39 +00:00
|
|
|
let mut mouse = Mouse {
|
|
|
|
click_state: $initial_state,
|
2021-07-29 15:40:51 +00:00
|
|
|
last_click_button: $initial_button,
|
2023-06-29 13:17:54 +00:00
|
|
|
last_click_timestamp: Instant::now() - $input_delay,
|
2021-01-01 05:07:39 +00:00
|
|
|
..Mouse::default()
|
|
|
|
};
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2023-10-20 09:33:38 +00:00
|
|
|
let mut inline_search_state = InlineSearchState::default();
|
2021-10-23 07:16:47 +00:00
|
|
|
let mut message_buffer = MessageBuffer::default();
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
let context = ActionContext {
|
|
|
|
terminal: &mut terminal,
|
|
|
|
mouse: &mut mouse,
|
|
|
|
size_info: &size,
|
2020-06-06 21:33:20 +00:00
|
|
|
clipboard: &mut clipboard,
|
2019-11-11 21:05:24 +00:00
|
|
|
modifiers: Default::default(),
|
2019-10-05 00:29:26 +00:00
|
|
|
message_buffer: &mut message_buffer,
|
2023-10-20 09:33:38 +00:00
|
|
|
inline_search_state: &mut inline_search_state,
|
2019-11-03 19:02:26 +00:00
|
|
|
config: &cfg,
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
let mut processor = Processor::new(context);
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2023-07-13 07:06:54 +00:00
|
|
|
let event: WinitEvent::<TerminalEvent> = $input;
|
2022-11-03 16:37:54 +00:00
|
|
|
if let WinitEvent::WindowEvent {
|
2019-11-03 20:59:28 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state,
|
|
|
|
button,
|
|
|
|
..
|
|
|
|
},
|
|
|
|
..
|
2020-01-10 01:51:37 +00:00
|
|
|
} = event
|
2019-11-03 20:59:28 +00:00
|
|
|
{
|
2020-01-05 01:00:50 +00:00
|
|
|
processor.mouse_input(state, button);
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
assert_eq!(processor.ctx.mouse.click_state, $end_state);
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
macro_rules! test_process_binding {
|
|
|
|
{
|
|
|
|
name: $name:ident,
|
|
|
|
binding: $binding:expr,
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: $triggers:expr,
|
2016-06-23 16:48:31 +00:00
|
|
|
mode: $mode:expr,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: $mods:expr,
|
2016-06-23 16:48:31 +00:00
|
|
|
} => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
2016-12-27 00:00:27 +00:00
|
|
|
if $triggers {
|
2020-01-10 00:44:41 +00:00
|
|
|
assert!($binding.is_triggered_by($mode, $mods, &KEY));
|
2016-12-27 00:00:27 +00:00
|
|
|
} else {
|
2020-01-10 00:44:41 +00:00
|
|
|
assert!(!$binding.is_triggered_by($mode, $mods, &KEY));
|
2016-12-27 00:00:27 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
test_clickstate! {
|
|
|
|
name: single_click,
|
|
|
|
initial_state: ClickState::None,
|
2019-01-08 07:37:58 +00:00
|
|
|
initial_button: MouseButton::Other(0),
|
2022-11-03 16:37:54 +00:00
|
|
|
input: WinitEvent::WindowEvent {
|
2017-07-20 17:50:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
2022-07-08 22:37:22 +00:00
|
|
|
device_id: unsafe { DeviceId::dummy() },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2022-07-08 22:37:22 +00:00
|
|
|
window_id: unsafe { WindowId::dummy() },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::Click,
|
2023-06-29 13:17:54 +00:00
|
|
|
input_delay: Duration::ZERO,
|
2020-04-10 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: single_right_click,
|
|
|
|
initial_state: ClickState::None,
|
|
|
|
initial_button: MouseButton::Other(0),
|
2022-11-03 16:37:54 +00:00
|
|
|
input: WinitEvent::WindowEvent {
|
2020-04-10 18:23:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Right,
|
2022-07-08 22:37:22 +00:00
|
|
|
device_id: unsafe { DeviceId::dummy() },
|
2020-04-10 18:23:50 +00:00
|
|
|
},
|
2022-07-08 22:37:22 +00:00
|
|
|
window_id: unsafe { WindowId::dummy() },
|
2020-04-10 18:23:50 +00:00
|
|
|
},
|
2020-06-23 09:57:15 +00:00
|
|
|
end_state: ClickState::Click,
|
2023-06-29 13:17:54 +00:00
|
|
|
input_delay: Duration::ZERO,
|
2020-04-10 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: single_middle_click,
|
|
|
|
initial_state: ClickState::None,
|
|
|
|
initial_button: MouseButton::Other(0),
|
2022-11-03 16:37:54 +00:00
|
|
|
input: WinitEvent::WindowEvent {
|
2020-04-10 18:23:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Middle,
|
2022-07-08 22:37:22 +00:00
|
|
|
device_id: unsafe { DeviceId::dummy() },
|
2020-04-10 18:23:50 +00:00
|
|
|
},
|
2022-07-08 22:37:22 +00:00
|
|
|
window_id: unsafe { WindowId::dummy() },
|
2020-04-10 18:23:50 +00:00
|
|
|
},
|
2021-07-29 15:40:51 +00:00
|
|
|
end_state: ClickState::Click,
|
2023-06-29 13:17:54 +00:00
|
|
|
input_delay: Duration::ZERO,
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: double_click,
|
|
|
|
initial_state: ClickState::Click,
|
2019-01-08 07:37:58 +00:00
|
|
|
initial_button: MouseButton::Left,
|
2022-11-03 16:37:54 +00:00
|
|
|
input: WinitEvent::WindowEvent {
|
2017-07-20 17:50:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
2022-07-08 22:37:22 +00:00
|
|
|
device_id: unsafe { DeviceId::dummy() },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2022-07-08 22:37:22 +00:00
|
|
|
window_id: unsafe { WindowId::dummy() },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::DoubleClick,
|
2023-06-29 13:17:54 +00:00
|
|
|
input_delay: Duration::ZERO,
|
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: double_click_failed,
|
|
|
|
initial_state: ClickState::Click,
|
|
|
|
initial_button: MouseButton::Left,
|
|
|
|
input: WinitEvent::WindowEvent {
|
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
|
|
|
device_id: unsafe { DeviceId::dummy() },
|
|
|
|
},
|
|
|
|
window_id: unsafe { WindowId::dummy() },
|
|
|
|
},
|
|
|
|
end_state: ClickState::Click,
|
|
|
|
input_delay: CLICK_THRESHOLD,
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: triple_click,
|
|
|
|
initial_state: ClickState::DoubleClick,
|
2019-01-08 07:37:58 +00:00
|
|
|
initial_button: MouseButton::Left,
|
2022-11-03 16:37:54 +00:00
|
|
|
input: WinitEvent::WindowEvent {
|
2017-07-20 17:50:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
2022-07-08 22:37:22 +00:00
|
|
|
device_id: unsafe { DeviceId::dummy() },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2022-07-08 22:37:22 +00:00
|
|
|
window_id: unsafe { WindowId::dummy() },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::TripleClick,
|
2023-06-29 13:17:54 +00:00
|
|
|
input_delay: Duration::ZERO,
|
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: triple_click_failed,
|
|
|
|
initial_state: ClickState::DoubleClick,
|
|
|
|
initial_button: MouseButton::Left,
|
|
|
|
input: WinitEvent::WindowEvent {
|
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
|
|
|
device_id: unsafe { DeviceId::dummy() },
|
|
|
|
},
|
|
|
|
window_id: unsafe { WindowId::dummy() },
|
|
|
|
},
|
|
|
|
end_state: ClickState::Click,
|
|
|
|
input_delay: CLICK_THRESHOLD,
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 07:37:58 +00:00
|
|
|
test_clickstate! {
|
|
|
|
name: multi_click_separate_buttons,
|
|
|
|
initial_state: ClickState::DoubleClick,
|
|
|
|
initial_button: MouseButton::Left,
|
2022-11-03 16:37:54 +00:00
|
|
|
input: WinitEvent::WindowEvent {
|
2019-01-08 07:37:58 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Right,
|
2022-07-08 22:37:22 +00:00
|
|
|
device_id: unsafe { DeviceId::dummy() },
|
2019-01-08 07:37:58 +00:00
|
|
|
},
|
2022-07-08 22:37:22 +00:00
|
|
|
window_id: unsafe { WindowId::dummy() },
|
2019-01-08 07:37:58 +00:00
|
|
|
},
|
2020-06-23 09:57:15 +00:00
|
|
|
end_state: ClickState::Click,
|
2023-06-29 13:17:54 +00:00
|
|
|
input_delay: Duration::ZERO,
|
2019-01-08 07:37:58 +00:00
|
|
|
}
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_shiftmod_require_shift,
|
2020-12-19 04:07:20 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::SHIFT, action: Action::from("\x1b[1;2D"), mode: BindingMode::empty(), notmode: BindingMode::empty() },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::empty(),
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::SHIFT,
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_shift,
|
2020-12-19 04:07:20 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::SHIFT, action: Action::from("\x1b[1;2D"), mode: BindingMode::empty(), notmode: BindingMode::empty() },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::empty(),
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_controlmod,
|
2023-07-11 02:22:14 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::CONTROL, action: Action::from("\x1b[1;5D"), mode: BindingMode::empty(), notmode: BindingMode::empty() },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::empty(),
|
2023-07-11 02:22:14 +00:00
|
|
|
mods: ModifiersState::CONTROL,
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_not_appcursor,
|
2020-12-19 04:07:20 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1b[D"), mode: BindingMode::empty(), notmode: BindingMode::APP_CURSOR },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::empty(),
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_appcursormode_nomod_require_appcursor,
|
2020-12-19 04:07:20 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: BindingMode::APP_CURSOR, notmode: BindingMode::empty() },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::APP_CURSOR,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_appcursor,
|
2020-12-19 04:07:20 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: BindingMode::APP_CURSOR, notmode: BindingMode::empty() },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::empty(),
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
2016-07-30 01:27:45 +00:00
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor,
|
2020-12-19 04:07:20 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: BindingMode::APP_CURSOR, notmode: BindingMode::empty() },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::APP_CURSOR | BindingMode::APP_KEYPAD,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-07-30 01:27:45 +00:00
|
|
|
}
|
2016-09-27 15:27:39 +00:00
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_fail_with_extra_mods,
|
2023-07-11 02:22:14 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::SUPER, action: Action::from("arst"), mode: BindingMode::empty(), notmode: BindingMode::empty() },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::empty(),
|
2023-07-11 02:22:14 +00:00
|
|
|
mods: ModifiersState::ALT | ModifiersState::SUPER,
|
2016-09-27 15:27:39 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|