Handlers attached, things are looking good 2 go

Mikayla Maki created

Change summary

crates/terminal/src/connected_el.rs   | 259 +++++++---------------------
crates/terminal/src/mappings/mouse.rs | 120 ++++++++++--
crates/terminal/src/terminal.rs       | 127 +++++++++++--
3 files changed, 263 insertions(+), 243 deletions(-)

Detailed changes

crates/terminal/src/connected_el.rs 🔗

@@ -1,26 +1,22 @@
 use alacritty_terminal::{
     ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
-    grid::{Dimensions, Scroll},
-    index::{Column as GridCol, Direction, Line as GridLine, Point, Side},
+    grid::Dimensions,
+    index::Point,
     selection::SelectionRange,
-    term::{
-        cell::{Cell, Flags},
-        TermMode,
-    },
+    term::cell::{Cell, Flags},
 };
 use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
 use gpui::{
     color::Color,
-    elements::*,
     fonts::{Properties, Style::Italic, TextStyle, Underline, Weight},
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    json::json,
+    serde_json::json,
     text_layout::{Line, RunStyle},
-    Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseButtonEvent,
-    MouseRegion, PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle,
+    Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton,
+    MouseButtonEvent, MouseRegion, PaintContext, Quad, TextLayoutCache, WeakModelHandle,
     WeakViewHandle,
 };
 use itertools::Itertools;
@@ -29,12 +25,11 @@ use settings::Settings;
 use theme::TerminalStyle;
 use util::ResultExt;
 
+use std::fmt::Debug;
 use std::{
-    cmp::min,
     mem,
     ops::{Deref, Range},
 };
-use std::{fmt::Debug, ops::Sub};
 
 use crate::{
     connected_view::{ConnectedView, DeployContextMenu},
@@ -42,11 +37,6 @@ use crate::{
     Terminal, TerminalSize,
 };
 
-///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
-///Scroll multiplier that is set to 3 by default. This will be removed when I
-///Implement scroll bars.
-pub const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
-
 ///The information generated during layout that is nescessary for painting
 pub struct LayoutState {
     cells: Vec<LayoutCell>,
@@ -56,7 +46,6 @@ pub struct LayoutState {
     background_color: Color,
     selection_color: Color,
     size: TerminalSize,
-    display_offset: usize,
 }
 
 #[derive(Debug)]
@@ -420,22 +409,13 @@ impl TerminalEl {
     fn generic_button_handler(
         connection: WeakModelHandle<Terminal>,
         origin: Vector2F,
-        cur_size: TerminalSize,
-        display_offset: usize,
-        f: impl Fn(&mut Terminal, Point, Direction, MouseButtonEvent, &mut ModelContext<Terminal>),
+        f: impl Fn(&mut Terminal, Vector2F, MouseButtonEvent, &mut ModelContext<Terminal>),
     ) -> impl Fn(MouseButtonEvent, &mut EventContext) {
         move |event, cx| {
             cx.focus_parent_view();
             if let Some(conn_handle) = connection.upgrade(cx.app) {
                 conn_handle.update(cx.app, |terminal, cx| {
-                    let (point, side) = TerminalEl::mouse_to_cell_data(
-                        event.position,
-                        origin,
-                        cur_size,
-                        display_offset,
-                    );
-
-                    f(terminal, point, side, event, cx);
+                    f(terminal, origin, event, cx);
 
                     cx.notify();
                 })
@@ -448,8 +428,6 @@ impl TerminalEl {
         origin: Vector2F,
         view_id: usize,
         visible_bounds: RectF,
-        cur_size: TerminalSize,
-        display_offset: usize,
         cx: &mut PaintContext,
     ) {
         let connection = self.terminal;
@@ -459,34 +437,20 @@ impl TerminalEl {
                     if cx.is_parent_view_focused() {
                         if let Some(conn_handle) = connection.upgrade(cx.app) {
                             conn_handle.update(cx.app, |terminal, cx| {
-                                let (point, side) = TerminalEl::mouse_to_cell_data(
-                                    event.position,
-                                    origin,
-                                    cur_size,
-                                    display_offset,
-                                );
-
-                                terminal.mouse_move(point, side, &event);
-
+                                terminal.mouse_move(&event, origin);
                                 cx.notify();
                             })
                         }
                     }
                 })
                 .on_drag(MouseButton::Left, move |_prev, event, cx| {
-                    if let Some(conn_handle) = connection.upgrade(cx.app) {
-                        conn_handle.update(cx.app, |terminal, cx| {
-                            let (point, side) = TerminalEl::mouse_to_cell_data(
-                                event.position,
-                                origin,
-                                cur_size,
-                                display_offset,
-                            );
-
-                            terminal.mouse_drag(point, side);
-
-                            cx.notify();
-                        })
+                    if cx.is_parent_view_focused() {
+                        if let Some(conn_handle) = connection.upgrade(cx.app) {
+                            conn_handle.update(cx.app, |terminal, cx| {
+                                terminal.mouse_drag(event, origin);
+                                cx.notify();
+                            })
+                        }
                     }
                 })
                 .on_down(
@@ -494,10 +458,8 @@ impl TerminalEl {
                     TerminalEl::generic_button_handler(
                         connection,
                         origin,
-                        cur_size,
-                        display_offset,
-                        move |terminal, point, side, _e, _cx| {
-                            terminal.mouse_down(point, side);
+                        move |terminal, origin, e, _cx| {
+                            terminal.mouse_down(&e, origin);
                         },
                     ),
                 )
@@ -506,10 +468,8 @@ impl TerminalEl {
                     TerminalEl::generic_button_handler(
                         connection,
                         origin,
-                        cur_size,
-                        display_offset,
-                        move |terminal, point, side, _e, _cx| {
-                            terminal.mouse_down(point, side);
+                        move |terminal, origin, e, _cx| {
+                            terminal.mouse_down(&e, origin);
                         },
                     ),
                 )
@@ -518,65 +478,61 @@ impl TerminalEl {
                     TerminalEl::generic_button_handler(
                         connection,
                         origin,
-                        cur_size,
-                        display_offset,
-                        move |terminal, point, side, _e, _cx| {
-                            terminal.mouse_down(point, side);
+                        move |terminal, origin, e, _cx| {
+                            terminal.mouse_down(&e, origin);
                         },
                     ),
                 )
-                //TODO
-                .on_click(
+                .on_up(
                     MouseButton::Left,
                     TerminalEl::generic_button_handler(
                         connection,
                         origin,
-                        cur_size,
-                        display_offset,
-                        move |terminal, point, side, e, _cx| {
-                            terminal.click(point, side, e.click_count);
+                        move |terminal, origin, e, _cx| {
+                            terminal.mouse_up(&e, origin);
                         },
                     ),
                 )
-                .on_click(
+                .on_up(
+                    MouseButton::Right,
+                    TerminalEl::generic_button_handler(
+                        connection,
+                        origin,
+                        move |terminal, origin, e, _cx| {
+                            terminal.mouse_up(&e, origin);
+                        },
+                    ),
+                )
+                .on_up(
                     MouseButton::Middle,
                     TerminalEl::generic_button_handler(
                         connection,
                         origin,
-                        cur_size,
-                        display_offset,
-                        move |terminal, point, side, e, _cx| {
-                            terminal.click(point, side, e.click_count);
+                        move |terminal, origin, e, _cx| {
+                            terminal.mouse_up(&e, origin);
+                        },
+                    ),
+                )
+                .on_click(
+                    MouseButton::Left,
+                    TerminalEl::generic_button_handler(
+                        connection,
+                        origin,
+                        move |terminal, origin, e, _cx| {
+                            terminal.left_click(&e, origin);
                         },
                     ),
                 )
                 .on_click(
                     MouseButton::Right,
                     move |e @ MouseButtonEvent { position, .. }, cx| {
-                        //Attempt to check the mode
-                        if let Some(conn_handle) = connection.upgrade(cx.app) {
-                            let handled = conn_handle.update(cx.app, |terminal, _cx| {
-                                //Finally, we can check the mode!
-                                if terminal.last_mode.intersects(TermMode::MOUSE_MODE) {
-                                    let (point, side) = TerminalEl::mouse_to_cell_data(
-                                        position,
-                                        origin,
-                                        cur_size,
-                                        display_offset,
-                                    );
-
-                                    terminal.click(point, side, e.click_count);
-                                    true
-                                } else {
-                                    false
-                                }
-                            });
-
-                            //If I put this up by the true, then we're in the wrong 'cx'
-                            if !handled {
-                                cx.dispatch_action(DeployContextMenu { position });
-                            }
+                        let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
+                            conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
                         } else {
+                            //If we can't get the model handle, probably can't deploy the context menu
+                            true
+                        };
+                        if !mouse_mode {
                             cx.dispatch_action(DeployContextMenu { position });
                         }
                     },
@@ -615,47 +571,6 @@ impl TerminalEl {
             underline: Default::default(),
         }
     }
-
-    pub fn mouse_to_cell_data(
-        pos: Vector2F,
-        origin: Vector2F,
-        cur_size: TerminalSize,
-        display_offset: usize,
-    ) -> (Point, alacritty_terminal::index::Direction) {
-        let pos = pos.sub(origin);
-        let point = {
-            let col = pos.x() / cur_size.cell_width; //TODO: underflow...
-            let col = min(GridCol(col as usize), cur_size.last_column());
-
-            let line = pos.y() / cur_size.line_height;
-            let line = min(line as i32, cur_size.bottommost_line().0);
-
-            Point::new(GridLine(line - display_offset as i32), col)
-        };
-
-        //Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
-        let side = {
-            let x = pos.0.x() as usize;
-            let cell_x =
-                x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize;
-            let half_cell_width = (cur_size.cell_width / 2.0) as usize;
-
-            let additional_padding =
-                (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width;
-            let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding;
-            //Width: Pixels or columns?
-            if cell_x > half_cell_width
-            // Edge case when mouse leaves the window.
-            || x as f32 >= end_of_grid
-            {
-                Side::Right
-            } else {
-                Side::Left
-            }
-        };
-
-        (point, side)
-    }
 }
 
 impl Element for TerminalEl {
@@ -712,7 +627,7 @@ impl Element for TerminalEl {
 
                     (
                         cells,
-                        dbg!(content.selection),
+                        content.selection,
                         content.cursor,
                         content.display_offset,
                         cursor_text,
@@ -794,7 +709,6 @@ impl Element for TerminalEl {
                 size: dimensions,
                 rects,
                 highlights,
-                display_offset,
             },
         )
     }
@@ -813,14 +727,7 @@ impl Element for TerminalEl {
             let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
 
             //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
-            self.attach_mouse_handlers(
-                origin,
-                self.view.id(),
-                visible_bounds,
-                layout.size,
-                layout.display_offset,
-                cx,
-            );
+            self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, cx);
 
             cx.paint_layer(clip_bounds, |cx| {
                 //Start with a background color
@@ -884,28 +791,22 @@ impl Element for TerminalEl {
     fn dispatch_event(
         &mut self,
         event: &gpui::Event,
-        _bounds: gpui::geometry::rect::RectF,
+        bounds: gpui::geometry::rect::RectF,
         visible_bounds: gpui::geometry::rect::RectF,
         layout: &mut Self::LayoutState,
         _paint: &mut Self::PaintState,
         cx: &mut gpui::EventContext,
     ) -> bool {
         match event {
-            Event::ScrollWheel(ScrollWheelEvent {
-                delta, position, ..
-            }) => visible_bounds
-                .contains_point(*position)
+            Event::ScrollWheel(e) => visible_bounds
+                .contains_point(e.position)
                 .then(|| {
-                    let scroll_lines =
-                        (delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER;
+                    let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
 
                     if let Some(terminal) = self.terminal.upgrade(cx.app) {
-                        terminal.update(cx.app, |term, _| {
-                            term.scroll(Scroll::Delta(scroll_lines.round() as i32))
-                        });
+                        terminal.update(cx.app, |term, _| term.scroll(e, origin));
+                        cx.notify();
                     }
-
-                    cx.notify();
                 })
                 .is_some(),
             Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
@@ -913,7 +814,6 @@ impl Element for TerminalEl {
                     return false;
                 }
 
-                //TODO Talk to keith about how to catch events emitted from an element.
                 if let Some(view) = self.view.upgrade(cx.app) {
                     view.update(cx.app, |view, cx| {
                         view.clear_bel(cx);
@@ -969,36 +869,3 @@ impl Element for TerminalEl {
         Some(layout.cursor.as_ref()?.bounding_rect(origin))
     }
 }
-
-mod test {
-
-    #[test]
-    fn test_mouse_to_selection() {
-        let term_width = 100.;
-        let term_height = 200.;
-        let cell_width = 10.;
-        let line_height = 20.;
-        let mouse_pos_x = 100.; //Window relative
-        let mouse_pos_y = 100.; //Window relative
-        let origin_x = 10.;
-        let origin_y = 20.;
-
-        let cur_size = crate::connected_el::TerminalSize::new(
-            line_height,
-            cell_width,
-            gpui::geometry::vector::vec2f(term_width, term_height),
-        );
-
-        let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
-        let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
-        let (point, _) =
-            crate::connected_el::TerminalEl::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
-        assert_eq!(
-            point,
-            alacritty_terminal::index::Point::new(
-                alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
-                alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
-            )
-        );
-    }
-}

crates/terminal/src/mappings/mouse.rs 🔗

@@ -1,16 +1,23 @@
+use std::cmp::min;
+use std::iter::repeat;
+
+use alacritty_terminal::grid::Dimensions;
 /// Most of the code, and specifically the constants, in this are copied from Alacritty,
 /// with modifications for our circumstances
-use alacritty_terminal::{index::Point, term::TermMode};
-use gpui::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
+use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side};
+use alacritty_terminal::term::TermMode;
+use gpui::{geometry::vector::Vector2F, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
+
+use crate::TerminalSize;
 
-pub struct Modifiers {
+struct Modifiers {
     ctrl: bool,
     shift: bool,
     alt: bool,
 }
 
 impl Modifiers {
-    pub fn from_moved(e: &MouseMovedEvent) -> Self {
+    fn from_moved(e: &MouseMovedEvent) -> Self {
         Modifiers {
             ctrl: e.ctrl,
             shift: e.shift,
@@ -18,7 +25,7 @@ impl Modifiers {
         }
     }
 
-    pub fn from_button(e: &MouseButtonEvent) -> Self {
+    fn from_button(e: &MouseButtonEvent) -> Self {
         Modifiers {
             ctrl: e.ctrl,
             shift: e.shift,
@@ -27,7 +34,7 @@ impl Modifiers {
     }
 
     //TODO: Determine if I should add modifiers into the ScrollWheelEvent type
-    pub fn from_scroll() -> Self {
+    fn from_scroll() -> Self {
         Modifiers {
             ctrl: false,
             shift: false,
@@ -36,13 +43,13 @@ impl Modifiers {
     }
 }
 
-pub enum MouseFormat {
+enum MouseFormat {
     SGR,
     Normal(bool),
 }
 
 impl MouseFormat {
-    pub fn from_mode(mode: TermMode) -> Self {
+    fn from_mode(mode: TermMode) -> Self {
         if mode.contains(TermMode::SGR_MOUSE) {
             MouseFormat::SGR
         } else if mode.contains(TermMode::UTF8_MOUSE) {
@@ -53,7 +60,7 @@ impl MouseFormat {
     }
 }
 
-pub enum MouseButton {
+enum MouseButton {
     LeftButton = 0,
     MiddleButton = 1,
     RightButton = 2,
@@ -67,7 +74,7 @@ pub enum MouseButton {
 }
 
 impl MouseButton {
-    pub fn from_move(e: &MouseMovedEvent) -> Self {
+    fn from_move(e: &MouseMovedEvent) -> Self {
         match e.pressed_button {
             Some(b) => match b {
                 gpui::MouseButton::Left => MouseButton::LeftMove,
@@ -79,7 +86,7 @@ impl MouseButton {
         }
     }
 
-    pub fn from_button(e: &MouseButtonEvent) -> Self {
+    fn from_button(e: &MouseButtonEvent) -> Self {
         match e.button {
             gpui::MouseButton::Left => MouseButton::LeftButton,
             gpui::MouseButton::Right => MouseButton::MiddleButton,
@@ -88,7 +95,7 @@ impl MouseButton {
         }
     }
 
-    pub fn from_scroll(e: &ScrollWheelEvent) -> Self {
+    fn from_scroll(e: &ScrollWheelEvent) -> Self {
         if e.delta.y() > 0. {
             MouseButton::ScrollUp
         } else {
@@ -96,7 +103,7 @@ impl MouseButton {
         }
     }
 
-    pub fn is_other(&self) -> bool {
+    fn is_other(&self) -> bool {
         match self {
             MouseButton::Other => true,
             _ => false,
@@ -109,24 +116,31 @@ pub fn scroll_report(
     scroll_lines: i32,
     e: &ScrollWheelEvent,
     mode: TermMode,
-) -> Option<Vec<Vec<u8>>> {
+) -> Option<impl Iterator<Item = Vec<u8>>> {
     if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 {
-        if let Some(report) = mouse_report(
+        mouse_report(
             point,
             MouseButton::from_scroll(e),
             true,
             Modifiers::from_scroll(),
             MouseFormat::from_mode(mode),
-        ) {
-            let mut res = vec![];
-            for _ in 0..scroll_lines.abs() {
-                res.push(report.clone());
-            }
-            return Some(res);
-        }
+        )
+        .map(|report| repeat(report).take(scroll_lines as usize))
+    } else {
+        None
     }
+}
 
-    None
+pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
+    let cmd = if scroll_lines > 0 { b'A' } else { b'B' };
+
+    let mut content = Vec::with_capacity(scroll_lines as usize * 3);
+    for _ in 0..scroll_lines {
+        content.push(0x1b);
+        content.push(b'O');
+        content.push(cmd);
+    }
+    content
 }
 
 pub fn mouse_button_report(
@@ -164,6 +178,31 @@ pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) ->
     }
 }
 
+pub fn mouse_side(pos: Vector2F, cur_size: TerminalSize) -> alacritty_terminal::index::Direction {
+    let x = pos.0.x() as usize;
+    let cell_x = x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize;
+    let half_cell_width = (cur_size.cell_width / 2.0) as usize;
+    let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width;
+    let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding;
+    //Width: Pixels or columns?
+    if cell_x > half_cell_width
+    // Edge case when mouse leaves the window.
+    || x as f32 >= end_of_grid
+    {
+        Side::Right
+    } else {
+        Side::Left
+    }
+}
+
+pub fn mouse_point(pos: Vector2F, cur_size: TerminalSize, display_offset: usize) -> Point {
+    let col = pos.x() / cur_size.cell_width;
+    let col = min(GridCol(col as usize), cur_size.last_column());
+    let line = pos.y() / cur_size.line_height;
+    let line = min(line as i32, cur_size.bottommost_line().0);
+    Point::new(GridLine(line - display_offset as i32), col)
+}
+
 ///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
 fn mouse_report(
     point: Point,
@@ -246,3 +285,38 @@ fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
 
     msg
 }
+
+#[cfg(test)]
+mod test {
+    use crate::mappings::mouse::mouse_point;
+
+    #[test]
+    fn test_mouse_to_selection() {
+        let term_width = 100.;
+        let term_height = 200.;
+        let cell_width = 10.;
+        let line_height = 20.;
+        let mouse_pos_x = 100.; //Window relative
+        let mouse_pos_y = 100.; //Window relative
+        let origin_x = 10.;
+        let origin_y = 20.;
+
+        let cur_size = crate::TerminalSize::new(
+            line_height,
+            cell_width,
+            gpui::geometry::vector::vec2f(term_width, term_height),
+        );
+
+        let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
+        let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
+        let mouse_pos = mouse_pos - origin;
+        let point = mouse_point(mouse_pos, cur_size, 0);
+        assert_eq!(
+            point,
+            alacritty_terminal::index::Point::new(
+                alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
+                alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
+            )
+        );
+    }
+}

crates/terminal/src/terminal.rs 🔗

@@ -24,16 +24,19 @@ use futures::{
     FutureExt,
 };
 
-use mappings::mouse::mouse_moved_report;
+use mappings::mouse::{
+    alt_scroll, mouse_button_report, mouse_moved_report, mouse_point, mouse_side, scroll_report,
+};
 use modal::deploy_modal;
 use settings::{Settings, Shell, TerminalBlink};
-use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration};
+use std::{collections::HashMap, fmt::Display, ops::Sub, path::PathBuf, sync::Arc, time::Duration};
 use thiserror::Error;
 
 use gpui::{
     geometry::vector::{vec2f, Vector2F},
     keymap::Keystroke,
-    ClipboardItem, Entity, ModelContext, MouseMovedEvent, MutableAppContext,
+    ClipboardItem, Entity, ModelContext, MouseButtonEvent, MouseMovedEvent, MutableAppContext,
+    ScrollWheelEvent,
 };
 
 use crate::mappings::{
@@ -49,6 +52,11 @@ pub fn init(cx: &mut MutableAppContext) {
     connected_view::init(cx);
 }
 
+///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
+///Scroll multiplier that is set to 3 by default. This will be removed when I
+///Implement scroll bars.
+pub const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
+
 const DEBUG_TERMINAL_WIDTH: f32 = 500.;
 const DEBUG_TERMINAL_HEIGHT: f32 = 30.;
 const DEBUG_CELL_WIDTH: f32 = 5.;
@@ -348,6 +356,7 @@ impl TerminalBuilder {
             last_mode: TermMode::NONE,
             cur_size: initial_size,
             last_mouse: None,
+            last_offset: 0,
         };
 
         Ok(TerminalBuilder {
@@ -417,6 +426,7 @@ pub struct Terminal {
     title: String,
     cur_size: TerminalSize,
     last_mode: TermMode,
+    last_offset: usize,
     last_mouse: Option<(Point, Direction)>,
 }
 
@@ -509,7 +519,7 @@ impl Terminal {
     }
 
     pub fn input(&mut self, input: String) {
-        self.scroll(Scroll::Bottom);
+        self.events.push(InternalEvent::Scroll(Scroll::Bottom));
         self.events.push(InternalEvent::SetSelection(None));
         self.write_to_pty(input);
     }
@@ -563,11 +573,12 @@ impl Terminal {
             self.process_terminal_event(&e, &mut term, cx)
         }
 
-        // self.utilization = Self::estimate_utilization(term.take_last_processed_bytes());
         self.last_mode = *term.mode();
 
         let content = term.renderable_content();
 
+        self.last_offset = content.display_offset;
+
         let cursor_text = term.grid()[content.cursor.point].c;
 
         f(content, cursor_text)
@@ -602,23 +613,45 @@ impl Terminal {
         }
     }
 
-    /// Handle a mouse move
-    pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) {
-        if self.mouse_changed(point, side) {
+    pub fn mouse_mode(&self, shift: bool) -> bool {
+        self.last_mode.intersects(TermMode::MOUSE_MODE) && !shift
+    }
+
+    pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) {
+        let position = e.position.sub(origin);
+
+        let point = mouse_point(position, self.cur_size, self.last_offset);
+        let side = mouse_side(position, self.cur_size);
+
+        if self.mouse_changed(point, side) && self.mouse_mode(e.shift) {
             if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) {
                 self.pty_tx.notify(bytes);
             }
         }
     }
 
-    pub fn mouse_drag(&mut self, point: Point, side: Direction) {
-        self.events
-            .push(InternalEvent::UpdateSelection((point, side)));
+    pub fn mouse_drag(&mut self, e: MouseMovedEvent, origin: Vector2F) {
+        let position = e.position.sub(origin);
+
+        if !self.mouse_mode(e.shift) {
+            let point = mouse_point(position, self.cur_size, self.last_offset);
+            let side = mouse_side(position, self.cur_size);
+
+            self.events
+                .push(InternalEvent::UpdateSelection((point, side)));
+        }
     }
 
-    pub fn mouse_down(&mut self, point: Point, side: Direction) {
-        if self.last_mode.intersects(TermMode::MOUSE_REPORT_CLICK) {
-            //TODE: MOUSE MODE
+    pub fn mouse_down(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
+        let position = e.position.sub(origin);
+
+        let point = mouse_point(position, self.cur_size, self.last_offset);
+        let side = mouse_side(position, self.cur_size);
+
+        if self.mouse_mode(e.shift) {
+            if let Some(bytes) = mouse_button_report(point, e, true, self.last_mode) {
+                self.pty_tx.notify(bytes);
+            }
         } else {
             self.events
                 .push(InternalEvent::SetSelection(Some(Selection::new(
@@ -629,11 +662,15 @@ impl Terminal {
         }
     }
 
-    pub fn click(&mut self, point: Point, side: Direction, clicks: usize) {
-        if self.last_mode.intersects(TermMode::MOUSE_MODE) {
-            //TODE: MOUSE MODE
-        } else {
-            let selection_type = match clicks {
+    pub fn left_click(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
+        let position = e.position.sub(origin);
+        //TODO: Alt-click cursor position
+
+        if !self.mouse_mode(e.shift) {
+            let point = mouse_point(position, self.cur_size, self.last_offset);
+            let side = mouse_side(position, self.cur_size);
+
+            let selection_type = match e.click_count {
                 0 => return, //This is a release
                 1 => Some(SelectionType::Simple),
                 2 => Some(SelectionType::Semantic),
@@ -648,13 +685,55 @@ impl Terminal {
         }
     }
 
-    ///Scroll the terminal
-    pub fn scroll(&mut self, scroll: Scroll) {
-        if self.last_mode.intersects(TermMode::MOUSE_MODE) {
-            //TODE: MOUSE MODE
+    pub fn mouse_up(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
+        let position = e.position.sub(origin);
+
+        if self.mouse_mode(e.shift) {
+            let point = mouse_point(position, self.cur_size, self.last_offset);
+
+            if let Some(bytes) = mouse_button_report(point, e, false, self.last_mode) {
+                self.pty_tx.notify(bytes);
+            }
+        } else {
+            // Seems pretty standard to automatically copy on mouse_up for terminals,
+            // so let's do that here
+            self.copy();
         }
+    }
 
-        self.events.push(InternalEvent::Scroll(scroll));
+    ///Scroll the terminal
+    pub fn scroll(&mut self, scroll: &ScrollWheelEvent, origin: Vector2F) {
+        if self.mouse_mode(false) {
+            //TODO: Currently this only sends the current scroll reports as they come in. Alacritty
+            //Sends the *entire* scroll delta on *every* scroll event, only resetting it when
+            //The scroll enters 'TouchPhase::Started'. Do I need to replicate this?
+            //This would be consistent with a scroll model based on 'distance from origin'...
+            let scroll_lines = (scroll.delta.y() / self.cur_size.line_height) as i32;
+            let point = mouse_point(scroll.position.sub(origin), self.cur_size, self.last_offset);
+
+            if let Some(scrolls) = scroll_report(point, scroll_lines as i32, scroll, self.last_mode)
+            {
+                for scroll in scrolls {
+                    self.pty_tx.notify(scroll);
+                }
+            };
+        } else if self
+            .last_mode
+            .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
+        {
+            //TODO: See above TODO, also applies here.
+            let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER)
+                / self.cur_size.line_height) as i32;
+
+            self.pty_tx.notify(alt_scroll(scroll_lines))
+        } else {
+            let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER)
+                / self.cur_size.line_height) as i32;
+            if scroll_lines != 0 {
+                let scroll = Scroll::Delta(scroll_lines);
+                self.events.push(InternalEvent::Scroll(scroll));
+            }
+        }
     }
 }