diff options
Diffstat (limited to 'src/input.rs')
-rw-r--r-- | src/input.rs | 202 |
1 files changed, 192 insertions, 10 deletions
diff --git a/src/input.rs b/src/input.rs index e6dc3993..e2b96c60 100644 --- a/src/input.rs +++ b/src/input.rs @@ -20,16 +20,18 @@ //! determine what to do when a non-modifier key is pressed. use std::borrow::Cow; use std::mem; +use std::time::Instant; use copypasta::{Clipboard, Load, Buffer}; use glutin::{ElementState, VirtualKeyCode, MouseButton}; use glutin::{Mods, mods}; use glutin::{TouchPhase, MouseScrollDelta}; -use event::{Mouse}; +use config; +use event::{ClickState, Mouse}; use index::{Line, Column, Side, Point}; -use term::mode::{self, TermMode}; use term::SizeInfo; +use term::mode::{self, TermMode}; use util::fmt::Red; /// Processes input from glutin. @@ -41,6 +43,7 @@ use util::fmt::Red; pub struct Processor<'a, A: 'a> { pub key_bindings: &'a [KeyBinding], pub mouse_bindings: &'a [MouseBinding], + pub mouse_config: &'a config::Mouse, pub ctx: A, } @@ -51,7 +54,10 @@ pub trait ActionContext { fn copy_selection(&self, Buffer); fn clear_selection(&mut self); fn update_selection(&mut self, Point, Side); + fn semantic_selection(&mut self, Point); + fn line_selection(&mut self, Point); fn mouse_mut(&mut self) -> &mut Mouse; + fn mouse_coords(&self) -> Option<Point>; } /// Describes a state and action to take in that state @@ -266,13 +272,43 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } } - pub fn on_mouse_press(&mut self) { - if self.ctx.terminal_mode().intersects(mode::MOUSE_REPORT_CLICK | mode::MOUSE_MOTION) { - self.mouse_report(0); - return; + pub fn on_mouse_double_click(&mut self) { + if let Some(point) = self.ctx.mouse_coords() { + self.ctx.semantic_selection(point); } + } - self.ctx.clear_selection(); + pub fn on_mouse_triple_click(&mut self) { + if let Some(point) = self.ctx.mouse_coords() { + self.ctx.line_selection(point); + } + } + + pub fn on_mouse_press(&mut self) { + let now = Instant::now(); + let elapsed = self.ctx.mouse_mut().last_click_timestamp.elapsed(); + self.ctx.mouse_mut().last_click_timestamp = now; + + self.ctx.mouse_mut().click_state = match self.ctx.mouse_mut().click_state { + ClickState::Click if elapsed < self.mouse_config.double_click.threshold => { + self.on_mouse_double_click(); + ClickState::DoubleClick + }, + ClickState::DoubleClick if elapsed < self.mouse_config.triple_click.threshold => { + self.on_mouse_triple_click(); + ClickState::TripleClick + }, + _ => { + let report_modes = mode::MOUSE_REPORT_CLICK | mode::MOUSE_MOTION; + if self.ctx.terminal_mode().intersects(report_modes) { + self.mouse_report(0); + return; + } + + self.ctx.clear_selection(); + ClickState::Click + } + }; } pub fn on_mouse_release(&mut self) { @@ -422,14 +458,136 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { #[cfg(test)] mod tests { - use glutin::{mods, VirtualKeyCode}; + use std::borrow::Cow; + use std::time::Duration; - use term::mode; + use glutin::{mods, VirtualKeyCode, Event, ElementState, MouseButton}; - use super::{Action, Binding}; + use term::{SizeInfo, Term, TermMode, mode}; + use event::{Mouse, ClickState}; + use config::{self, Config, ClickHandler}; + use selection::Selection; + use index::{Point, Side}; + + use super::{Action, Binding, Processor}; const KEY: VirtualKeyCode = VirtualKeyCode::Key0; + #[derive(PartialEq)] + enum MultiClick { + DoubleClick, + TripleClick, + None, + } + + struct ActionContext<'a> { + pub terminal: &'a mut Term, + pub selection: &'a mut Selection, + pub size_info: &'a SizeInfo, + pub mouse: &'a mut Mouse, + pub last_action: MultiClick, + } + + impl <'a>super::ActionContext for ActionContext<'a> { + fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _val: B) { + // STUBBED + } + + fn terminal_mode(&self) -> TermMode { + *self.terminal.mode() + } + + fn size_info(&self) -> SizeInfo { + *self.size_info + } + + fn copy_selection(&self, _buffer: ::copypasta::Buffer) { + // STUBBED + } + + fn clear_selection(&mut self) { } + + fn update_selection(&mut self, point: Point, side: Side) { + self.selection.update(point, side); + } + + fn semantic_selection(&mut self, _point: Point) { + // set something that we can check for here + self.last_action = MultiClick::DoubleClick; + } + + fn line_selection(&mut self, _point: Point) { + self.last_action = MultiClick::TripleClick; + } + + fn mouse_coords(&self) -> Option<Point> { + self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize) + } + + #[inline] + fn mouse_mut(&mut self) -> &mut Mouse { + self.mouse + } + } + + macro_rules! test_clickstate { + { + name: $name:ident, + initial_state: $initial_state:expr, + input: $input:expr, + end_state: $end_state:pat, + last_action: $last_action:expr + } => { + #[test] + fn $name() { + let config = Config::default(); + let size = SizeInfo { + width: 21.0, + height: 51.0, + cell_width: 3.0, + cell_height: 3.0, + }; + + let mut terminal = Term::new(&config, size); + + let mut mouse = Mouse::default(); + let mut selection = Selection::new(); + mouse.click_state = $initial_state; + + let context = ActionContext { + terminal: &mut terminal, + selection: &mut selection, + mouse: &mut mouse, + size_info: &size, + last_action: MultiClick::None, + }; + + let mut processor = Processor { + ctx: context, + mouse_config: &config::Mouse { + double_click: ClickHandler { + threshold: Duration::from_millis(1000), + }, + triple_click: ClickHandler { + threshold: Duration::from_millis(1000), + } + }, + key_bindings: &config.key_bindings()[..], + mouse_bindings: &config.mouse_bindings()[..], + }; + + if let Event::MouseInput(state, input) = $input { + processor.mouse_input(state, input); + }; + + assert!(match mouse.click_state { + $end_state => processor.ctx.last_action == $last_action, + _ => false + }); + } + } + } + macro_rules! test_process_binding { { name: $name:ident, @@ -449,6 +607,30 @@ mod tests { } } + test_clickstate! { + name: single_click, + initial_state: ClickState::None, + input: Event::MouseInput(ElementState::Pressed, MouseButton::Left), + end_state: ClickState::Click, + last_action: MultiClick::None + } + + test_clickstate! { + name: double_click, + initial_state: ClickState::Click, + input: Event::MouseInput(ElementState::Pressed, MouseButton::Left), + end_state: ClickState::DoubleClick, + last_action: MultiClick::DoubleClick + } + + test_clickstate! { + name: triple_click, + initial_state: ClickState::DoubleClick, + input: Event::MouseInput(ElementState::Pressed, MouseButton::Left), + end_state: ClickState::TripleClick, + last_action: MultiClick::TripleClick + } + test_process_binding! { name: process_binding_nomode_shiftmod_require_shift, binding: Binding { trigger: KEY, mods: mods::SHIFT, action: Action::from("\x1b[1;2D"), mode: mode::NONE, notmode: mode::NONE }, |