diff --git a/src/ansi.rs b/src/ansi.rs index eaa34430..c9341620 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -239,6 +239,8 @@ pub enum Mode { BlinkingCursor = 12, /// ?25 ShowCursor = 25, + /// ?1000 + ReportMouseClicks = 1000, /// ?1049 SwapScreenAndSetRestoreCursor = 1049, } @@ -254,6 +256,7 @@ impl Mode { 6 => Mode::Origin, 12 => Mode::BlinkingCursor, 25 => Mode::ShowCursor, + 1000 => Mode::ReportMouseClicks, 1049 => Mode::SwapScreenAndSetRestoreCursor, _ => return None }) diff --git a/src/event.rs b/src/event.rs index 5ec156c3..b220ae2c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -86,7 +86,12 @@ impl Processor { let processor = &mut self.input_processor; let notifier = &mut self.notifier; - processor.mouse_input(state, button, notifier, *terminal.mode()); + processor.mouse_input(state, button, notifier, &terminal); + }, + glutin::Event::MouseMoved(x, y) => { + if x > 0 && y > 0 { + self.input_processor.mouse_moved(x as u32, y as u32); + } }, glutin::Event::Focused(true) => { let mut terminal = self.terminal.lock(); diff --git a/src/input.rs b/src/input.rs index 9d70e772..2e90ee20 100644 --- a/src/input.rs +++ b/src/input.rs @@ -29,9 +29,11 @@ use copypasta::{Clipboard, Load}; use glutin::{ElementState, VirtualKeyCode, MouseButton}; use glutin::{Mods, mods}; +use index::{Line, Column}; use config::Config; use event_loop; -use term::mode::{TermMode}; +use term::mode::{self, TermMode}; +use term::Term; /// Processes input from glutin. /// @@ -43,6 +45,24 @@ use term::mode::{TermMode}; pub struct Processor { key_bindings: Vec, mouse_bindings: Vec, + mouse: Mouse, +} + +/// State of the mouse +pub struct Mouse { + x: u32, + y: u32, + left_button_state: ElementState, +} + +impl Default for Mouse { + fn default() -> Mouse { + Mouse { + x: 0, + y: 0, + left_button_state: ElementState::Pressed + } + } } /// Types that are notified of escape sequences from the input::Processor. @@ -219,23 +239,84 @@ impl Processor { Processor { key_bindings: config.key_bindings().to_vec(), mouse_bindings: config.mouse_bindings().to_vec(), + mouse: Mouse::default(), } } + #[inline] + pub fn mouse_moved(&mut self, x: u32, y: u32) { + // Record mouse position within window. Pixel coordinates are *not* + // translated to grid coordinates here since grid coordinates are rarely + // needed and the mouse position updates frequently. + self.mouse.x = x; + self.mouse.y = y; + } + + fn mouse_report( + &mut self, + button: u8, + notifier: &mut N, + terminal: &Term + ) { + if terminal.mode().contains(mode::MOUSE_REPORT_CLICK) { + let (line, column) = terminal.pixels_to_coords( + self.mouse.x as usize, + self.mouse.y as usize + ).unwrap(); + + if line < Line(223) && column < Column(223) { + let msg = vec![ + '\x1b' as u8, + '[' as u8, + 'M' as u8, + 32 + button, + 32 + 1 + column.0 as u8, + 32 + 1 + line.0 as u8, + ]; + + notifier.notify(msg); + } + + } + } + + pub fn on_mouse_press(&mut self, notifier: &mut N, terminal: &Term) { + self.mouse_report(0, notifier, terminal); + } + + pub fn on_mouse_release(&mut self, notifier: &mut N, terminal: &Term) { + self.mouse_report(3, notifier, terminal); + } + pub fn mouse_input( &mut self, state: ElementState, button: MouseButton, notifier: &mut N, - mode: TermMode + terminal: &Term ) { + if let MouseButton::Left = button { + // TODO handle state changes + if self.mouse.left_button_state != state { + self.mouse.left_button_state = state; + match state { + ElementState::Pressed => { + self.on_mouse_press(notifier, terminal); + }, + ElementState::Released => { + self.on_mouse_release(notifier, terminal); + } + } + } + } + if let ElementState::Released = state { return; } Processor::process_mouse_bindings( &self.mouse_bindings[..], - mode, + *terminal.mode(), notifier, mods::NONE, button diff --git a/src/term.rs b/src/term.rs index 33890ad5..d2d711b4 100644 --- a/src/term.rs +++ b/src/term.rs @@ -162,11 +162,12 @@ pub use self::cell::Cell; pub mod mode { bitflags! { pub flags TermMode: u8 { - const SHOW_CURSOR = 0b00000001, - const APP_CURSOR = 0b00000010, - const APP_KEYPAD = 0b00000100, - const ANY = 0b11111111, - const NONE = 0b00000000, + const SHOW_CURSOR = 0b00000001, + const APP_CURSOR = 0b00000010, + const APP_KEYPAD = 0b00000100, + const MOUSE_REPORT_CLICK = 0b00001000, + const ANY = 0b11111111, + const NONE = 0b00000000, } } @@ -288,6 +289,24 @@ impl Term { } } + /// Convert the given pixel values to a grid coordinate + /// + /// The mouse coordinates are expected to be relative to the top left. The + /// line and column returned are also relative to the top left. + /// + /// Returns None if the coordinates are outside the screen + pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<(Line, Column)> { + let size = self.size_info(); + if x > size.width as usize || y > size.height as usize { + return None; + } + + let col = Column(x / (size.cell_width as usize)); + let line = Line(y / (size.cell_height as usize)); + + Some((line, col)) + } + pub fn grid(&self) -> &Grid { &self.grid } @@ -815,6 +834,7 @@ impl ansi::Handler for Term { ansi::Mode::SwapScreenAndSetRestoreCursor => self.swap_alt(), ansi::Mode::ShowCursor => self.mode.insert(mode::SHOW_CURSOR), ansi::Mode::CursorKeys => self.mode.insert(mode::APP_CURSOR), + ansi::Mode::ReportMouseClicks => self.mode.insert(mode::MOUSE_REPORT_CLICK), _ => { debug_println!(".. ignoring set_mode"); } @@ -828,6 +848,7 @@ impl ansi::Handler for Term { ansi::Mode::SwapScreenAndSetRestoreCursor => self.swap_alt(), ansi::Mode::ShowCursor => self.mode.remove(mode::SHOW_CURSOR), ansi::Mode::CursorKeys => self.mode.remove(mode::APP_CURSOR), + ansi::Mode::ReportMouseClicks => self.mode.remove(mode::MOUSE_REPORT_CLICK), _ => { debug_println!(".. ignoring unset_mode"); }