Detailed changes
@@ -235,6 +235,7 @@ impl Presenter {
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
let mut invalidated_views = Vec::new();
let mut mouse_down_out_handlers = Vec::new();
+ let mut mouse_moved_region = None;
let mut mouse_down_region = None;
let mut clicked_region = None;
let mut dragged_region = None;
@@ -290,6 +291,15 @@ impl Presenter {
*prev_drag_position = *position;
}
+ for (region, _) in self.mouse_regions.iter().rev() {
+ if region.bounds.contains_point(*position) {
+ invalidated_views.push(region.view_id);
+ mouse_moved_region =
+ Some((region.clone(), MouseRegionEvent::Move(e.clone())));
+ break;
+ }
+ }
+
self.last_mouse_moved_event = Some(event.clone());
}
_ => {}
@@ -313,6 +323,17 @@ impl Presenter {
}
}
+ if let Some((move_moved_region, region_event)) = mouse_moved_region {
+ handled = true;
+ if let Some(mouse_moved_callback) =
+ move_moved_region.handlers.get(®ion_event.handler_key())
+ {
+ event_cx.with_current_view(move_moved_region.view_id, |event_cx| {
+ mouse_moved_callback(region_event, event_cx);
+ })
+ }
+ }
+
if let Some((clicked_region, region_event)) = clicked_region {
handled = true;
if let Some(click_callback) =
@@ -1,6 +1,7 @@
use std::{any::TypeId, mem::Discriminant, rc::Rc};
use collections::HashMap;
+
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
@@ -97,6 +98,14 @@ impl MouseRegion {
self.handlers = self.handlers.on_hover(handler);
self
}
+
+ pub fn on_move(
+ mut self,
+ handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static,
+ ) -> Self {
+ self.handlers = self.handlers.on_move(handler);
+ self
+ }
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
@@ -267,6 +276,23 @@ impl HandlerSet {
}));
self
}
+
+ pub fn on_move(
+ mut self,
+ handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static,
+ ) -> Self {
+ self.set.insert((MouseRegionEvent::move_disc(), None),
+ Rc::new(move |region_event, cx| {
+ if let MouseRegionEvent::Move(move_event)= region_event {
+ handler(move_event, cx);
+ } else {
+ panic!(
+ "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
+ region_event);
+ }
+ }));
+ self
+ }
}
#[derive(Debug)]
@@ -1,9 +1,12 @@
use alacritty_terminal::{
ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
grid::{Dimensions, Scroll},
- index::{Column as GridCol, Line as GridLine, Point, Side},
+ index::{Column as GridCol, Direction, Line as GridLine, Point, Side},
selection::SelectionRange,
- term::cell::{Cell, Flags},
+ term::{
+ cell::{Cell, Flags},
+ TermMode,
+ },
};
use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
use gpui::{
@@ -16,8 +19,9 @@ use gpui::{
},
json::json,
text_layout::{Line, RunStyle},
- Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion,
- PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle,
+ Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseButtonEvent,
+ MouseRegion, PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle,
+ WeakViewHandle,
};
use itertools::Itertools;
use ordered_float::OrderedFloat;
@@ -413,6 +417,32 @@ 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>),
+ ) -> 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);
+
+ cx.notify();
+ })
+ }
+ }
+ }
+
fn attach_mouse_handlers(
&self,
origin: Vector2F,
@@ -422,77 +452,116 @@ impl TerminalEl {
display_offset: usize,
cx: &mut PaintContext,
) {
- let mouse_down_connection = self.terminal;
- let click_connection = self.terminal;
- let drag_connection = self.terminal;
+ let connection = self.terminal;
cx.scene.push_mouse_region(
MouseRegion::new(view_id, None, visible_bounds)
- .on_down(
- MouseButton::Left,
- move |MouseButtonEvent { position, .. }, cx| {
- if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) {
+ .on_move(move |event, cx| {
+ 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(
- position,
+ event.position,
origin,
cur_size,
display_offset,
);
- terminal.mouse_down(point, side);
+ terminal.mouse_move(point, side, &event);
cx.notify();
})
}
- },
+ }
+ })
+ .on_down(
+ MouseButton::Left,
+ TerminalEl::generic_button_handler(
+ connection,
+ origin,
+ cur_size,
+ display_offset,
+ move |terminal, point, side, _e, _cx| {
+ terminal.mouse_down(point, side);
+ },
+ ),
)
+ .on_down(
+ MouseButton::Right,
+ TerminalEl::generic_button_handler(
+ connection,
+ origin,
+ cur_size,
+ display_offset,
+ move |terminal, point, side, _e, _cx| {
+ terminal.mouse_down(point, side);
+ },
+ ),
+ )
+ .on_down(
+ MouseButton::Middle,
+ TerminalEl::generic_button_handler(
+ connection,
+ origin,
+ cur_size,
+ display_offset,
+ move |terminal, point, side, _e, _cx| {
+ terminal.mouse_down(point, side);
+ },
+ ),
+ )
+ //TODO
.on_click(
MouseButton::Left,
- move |MouseButtonEvent {
- position,
- click_count,
- ..
- },
- cx| {
- cx.focus_parent_view();
- if let Some(conn_handle) = click_connection.upgrade(cx.app) {
- conn_handle.update(cx.app, |terminal, cx| {
- let (point, side) = TerminalEl::mouse_to_cell_data(
- position,
- origin,
- cur_size,
- display_offset,
- );
-
- terminal.click(point, side, click_count);
-
- cx.notify();
- });
- }
- },
+ TerminalEl::generic_button_handler(
+ connection,
+ origin,
+ cur_size,
+ display_offset,
+ move |terminal, point, side, e, _cx| {
+ terminal.click(point, side, e.click_count);
+ },
+ ),
)
.on_click(
- MouseButton::Right,
- move |MouseButtonEvent { position, .. }, cx| {
- cx.dispatch_action(DeployContextMenu { position });
- },
+ 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);
+ },
+ ),
)
- .on_drag(
- MouseButton::Left,
- move |_, MouseMovedEvent { position, .. }, cx| {
- if let Some(conn_handle) = drag_connection.upgrade(cx.app) {
- conn_handle.update(cx.app, |terminal, cx| {
- let (point, side) = TerminalEl::mouse_to_cell_data(
- position,
- origin,
- cur_size,
- display_offset,
- );
-
- terminal.drag(point, side);
-
- cx.notify()
+ .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 });
+ }
+ } else {
+ cx.dispatch_action(DeployContextMenu { position });
}
},
),
@@ -811,12 +880,12 @@ impl Element for TerminalEl {
}) => visible_bounds
.contains_point(*position)
.then(|| {
- let vertical_scroll =
+ let scroll_lines =
(delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER;
if let Some(terminal) = self.terminal.upgrade(cx.app) {
terminal.update(cx.app, |term, _| {
- term.scroll(Scroll::Delta(vertical_scroll.round() as i32))
+ term.scroll(Scroll::Delta(scroll_lines.round() as i32))
});
}
@@ -1,3 +1,4 @@
+/// The mappings defined in this file where created from reading the alacritty source
use alacritty_terminal::term::TermMode;
use gpui::keymap::Keystroke;
@@ -1,2 +1,3 @@
pub mod colors;
pub mod keys;
+pub mod mouse;
@@ -0,0 +1,248 @@
+/// 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};
+
+pub struct Modifiers {
+ ctrl: bool,
+ shift: bool,
+ alt: bool,
+}
+
+impl Modifiers {
+ pub fn from_moved(e: &MouseMovedEvent) -> Self {
+ Modifiers {
+ ctrl: e.ctrl,
+ shift: e.shift,
+ alt: e.alt,
+ }
+ }
+
+ pub fn from_button(e: &MouseButtonEvent) -> Self {
+ Modifiers {
+ ctrl: e.ctrl,
+ shift: e.shift,
+ alt: e.alt,
+ }
+ }
+
+ //TODO: Determine if I should add modifiers into the ScrollWheelEvent type
+ pub fn from_scroll() -> Self {
+ Modifiers {
+ ctrl: false,
+ shift: false,
+ alt: false,
+ }
+ }
+}
+
+pub enum MouseFormat {
+ SGR,
+ Normal(bool),
+}
+
+impl MouseFormat {
+ pub fn from_mode(mode: TermMode) -> Self {
+ if mode.contains(TermMode::SGR_MOUSE) {
+ MouseFormat::SGR
+ } else if mode.contains(TermMode::UTF8_MOUSE) {
+ MouseFormat::Normal(true)
+ } else {
+ MouseFormat::Normal(false)
+ }
+ }
+}
+
+pub enum MouseButton {
+ LeftButton = 0,
+ MiddleButton = 1,
+ RightButton = 2,
+ LeftMove = 32,
+ MiddleMove = 33,
+ RightMove = 34,
+ NoneMove = 35,
+ ScrollUp = 64,
+ ScrollDown = 65,
+ Other = 99,
+}
+
+impl MouseButton {
+ pub fn from_move(e: &MouseMovedEvent) -> Self {
+ match e.pressed_button {
+ Some(b) => match b {
+ gpui::MouseButton::Left => MouseButton::LeftMove,
+ gpui::MouseButton::Middle => MouseButton::MiddleMove,
+ gpui::MouseButton::Right => MouseButton::RightMove,
+ gpui::MouseButton::Navigate(_) => MouseButton::Other,
+ },
+ None => MouseButton::NoneMove,
+ }
+ }
+
+ pub fn from_button(e: &MouseButtonEvent) -> Self {
+ match e.button {
+ gpui::MouseButton::Left => MouseButton::LeftButton,
+ gpui::MouseButton::Right => MouseButton::MiddleButton,
+ gpui::MouseButton::Middle => MouseButton::RightButton,
+ gpui::MouseButton::Navigate(_) => MouseButton::Other,
+ }
+ }
+
+ pub fn from_scroll(e: &ScrollWheelEvent) -> Self {
+ if e.delta.y() > 0. {
+ MouseButton::ScrollUp
+ } else {
+ MouseButton::ScrollDown
+ }
+ }
+
+ pub fn is_other(&self) -> bool {
+ match self {
+ MouseButton::Other => true,
+ _ => false,
+ }
+ }
+}
+
+pub fn scroll_report(
+ point: Point,
+ scroll_lines: i32,
+ e: &ScrollWheelEvent,
+ mode: TermMode,
+) -> Option<Vec<Vec<u8>>> {
+ if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 {
+ if let Some(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);
+ }
+ }
+
+ None
+}
+
+pub fn mouse_button_report(
+ point: Point,
+ e: &MouseButtonEvent,
+ pressed: bool,
+ mode: TermMode,
+) -> Option<Vec<u8>> {
+ let button = MouseButton::from_button(e);
+ if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) {
+ mouse_report(
+ point,
+ button,
+ pressed,
+ Modifiers::from_button(e),
+ MouseFormat::from_mode(mode),
+ )
+ } else {
+ None
+ }
+}
+
+pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option<Vec<u8>> {
+ let button = MouseButton::from_move(e);
+ if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
+ mouse_report(
+ point,
+ button,
+ true,
+ Modifiers::from_moved(e),
+ MouseFormat::from_mode(mode),
+ )
+ } else {
+ None
+ }
+}
+
+///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
+fn mouse_report(
+ point: Point,
+ button: MouseButton,
+ pressed: bool,
+ modifiers: Modifiers,
+ format: MouseFormat,
+) -> Option<Vec<u8>> {
+ if point.line < 0 {
+ return None;
+ }
+
+ let mut mods = 0;
+ if modifiers.shift {
+ mods += 4;
+ }
+ if modifiers.alt {
+ mods += 8;
+ }
+ if modifiers.ctrl {
+ mods += 16;
+ }
+
+ match format {
+ MouseFormat::SGR => {
+ Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes())
+ }
+ MouseFormat::Normal(utf8) => {
+ if pressed {
+ normal_mouse_report(point, button as u8 + mods, utf8)
+ } else {
+ normal_mouse_report(point, 3 + mods, utf8)
+ }
+ }
+ }
+}
+
+fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option<Vec<u8>> {
+ let Point { line, column } = point;
+ let max_point = if utf8 { 2015 } else { 223 };
+
+ if line >= max_point || column >= max_point {
+ return None;
+ }
+
+ 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 >= 95 {
+ msg.append(&mut mouse_pos_encode(column.0));
+ } else {
+ msg.push(32 + 1 + column.0 as u8);
+ }
+
+ if utf8 && line >= 95 {
+ msg.append(&mut mouse_pos_encode(line.0 as usize));
+ } else {
+ msg.push(32 + 1 + line.0 as u8);
+ }
+
+ Some(msg)
+}
+
+fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
+ let c = if pressed { 'M' } else { 'm' };
+
+ let msg = format!(
+ "\x1b[<{};{};{}{}",
+ button,
+ point.column + 1,
+ point.line + 1,
+ c
+ );
+
+ msg
+}
@@ -24,6 +24,7 @@ use futures::{
FutureExt,
};
+use mappings::mouse::mouse_moved_report;
use modal::deploy_modal;
use settings::{Settings, Shell, TerminalBlink};
use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration};
@@ -32,7 +33,7 @@ use thiserror::Error;
use gpui::{
geometry::vector::{vec2f, Vector2F},
keymap::Keystroke,
- ClipboardItem, Entity, ModelContext, MutableAppContext,
+ ClipboardItem, Entity, ModelContext, MouseMovedEvent, MutableAppContext,
};
use crate::mappings::{
@@ -49,11 +50,9 @@ pub fn init(cx: &mut MutableAppContext) {
}
const DEBUG_TERMINAL_WIDTH: f32 = 500.;
-const DEBUG_TERMINAL_HEIGHT: f32 = 30.; //This needs to be wide enough that the CI & a local dev's prompt can fill the whole space.
+const DEBUG_TERMINAL_HEIGHT: f32 = 30.;
const DEBUG_CELL_WIDTH: f32 = 5.;
const DEBUG_LINE_HEIGHT: f32 = 5.;
-// const MAX_FRAME_RATE: f32 = 60.;
-// const BACK_BUFFER_SIZE: usize = 5000;
///Upward flowing events, for changing the title and such
#[derive(Clone, Copy, Debug)]
@@ -348,7 +347,7 @@ impl TerminalBuilder {
default_title: shell_txt,
last_mode: TermMode::NONE,
cur_size: initial_size,
- // utilization: 0.,
+ last_mouse: None,
};
Ok(TerminalBuilder {
@@ -406,27 +405,6 @@ impl TerminalBuilder {
})
.detach();
- // //Render loop
- // cx.spawn_weak(|this, mut cx| async move {
- // loop {
- // let utilization = match this.upgrade(&cx) {
- // Some(this) => this.update(&mut cx, |this, cx| {
- // cx.notify();
- // this.utilization()
- // }),
- // None => break,
- // };
-
- // let utilization = (1. - utilization).clamp(0.1, 1.);
- // let delay = cx.background().timer(Duration::from_secs_f32(
- // 1.0 / (Terminal::default_fps() * utilization),
- // ));
-
- // delay.await;
- // }
- // })
- // .detach();
-
self.terminal
}
}
@@ -439,19 +417,10 @@ pub struct Terminal {
title: String,
cur_size: TerminalSize,
last_mode: TermMode,
- //Percentage, between 0 and 1
- // utilization: f32,
+ last_mouse: Option<(Point, Direction)>,
}
impl Terminal {
- // fn default_fps() -> f32 {
- // MAX_FRAME_RATE
- // }
-
- // fn utilization(&self) -> f32 {
- // self.utilization
- // }
-
fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext<Self>) {
match event {
AlacTermEvent::Title(title) => {
@@ -494,12 +463,6 @@ impl Terminal {
}
}
- // fn process_events(&mut self, events: Vec<AlacTermEvent>, cx: &mut ModelContext<Self>) {
- // for event in events.into_iter() {
- // self.process_event(&event, cx);
- // }
- // }
-
///Takes events from Alacritty and translates them to behavior on this view
fn process_terminal_event(
&mut self,
@@ -507,7 +470,6 @@ impl Terminal {
term: &mut Term<ZedListener>,
cx: &mut ModelContext<Self>,
) {
- // TODO: Handle is_self_focused in subscription on terminal view
match event {
InternalEvent::TermEvent(term_event) => {
if let AlacTermEvent::ColorRequest(index, format) = term_event {
@@ -619,13 +581,46 @@ 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_changed(&mut self, point: Point, side: Direction) -> bool {
+ match self.last_mouse {
+ Some((old_point, old_side)) => {
+ if old_point == point && old_side == side {
+ false
+ } else {
+ self.last_mouse = Some((point, side));
+ true
+ }
+ }
+ None => {
+ self.last_mouse = Some((point, side));
+ true
+ }
}
+ }
- self.events.push(InternalEvent::Scroll(scroll));
+ /// Handle a mouse move, this is mutually exclusive with drag.
+ pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) {
+ if self.mouse_changed(point, side) {
+ if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) {
+ self.pty_tx.notify(bytes);
+ }
+ } else if matches!(e.pressed_button, Some(gpui::MouseButton::Left)) {
+ 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
+ } else {
+ self.events
+ .push(InternalEvent::SetSelection(Some(Selection::new(
+ SelectionType::Simple,
+ point,
+ side,
+ ))));
+ }
}
pub fn click(&mut self, point: Point, side: Direction, clicks: usize) {
@@ -647,32 +642,13 @@ impl Terminal {
}
}
- pub fn mouse_move(&mut self, point: Point, side: Direction, clicks: usize) {
- if self.last_mode.intersects(TermMode::MOUSE_MODE) {
- //TODE: MOUSE MODE
- }
- }
-
- pub fn drag(&mut self, point: Point, side: Direction) {
+ ///Scroll the terminal
+ pub fn scroll(&mut self, scroll: Scroll) {
if self.last_mode.intersects(TermMode::MOUSE_MODE) {
//TODE: MOUSE MODE
- } else {
- self.events
- .push(InternalEvent::UpdateSelection((point, side)));
}
- }
- pub fn mouse_down(&mut self, point: Point, side: Direction) {
- if self.last_mode.intersects(TermMode::MOUSE_MODE) {
- //TODE: MOUSE MODE
- } else {
- self.events
- .push(InternalEvent::SetSelection(Some(Selection::new(
- SelectionType::Simple,
- point,
- side,
- ))));
- }
+ self.events.push(InternalEvent::Scroll(scroll));
}
}
@@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
- "name": "styles",
"version": "1.0.0",
"license": "ISC",
"dependencies": {