Fix multi-line selection with single cell end

When the user selected multiple lines, dragging the selection downwards,
and then leaves the cursor to the left side of the first cell, the first
cell was still incorrectly selected. This has been fixed.

The selection also did not update if the mouse was outside of the
window, now all movement events are accpeted even when the mouse is
outside of the window. This allows updating the selection when the user
is dragging the cursor too far.

Mouse movement and click events outside of the window are not
propagated, these are only used for updating the selection.
This commit is contained in:
Christian Duerr 2018-03-13 19:00:14 +01:00 committed by Joe Wilm
parent d9bd21d33f
commit 58c69cafad
4 changed files with 80 additions and 62 deletions

View File

@ -154,8 +154,8 @@ pub enum ClickState {
/// State of the mouse /// State of the mouse
pub struct Mouse { pub struct Mouse {
pub x: u32, pub x: usize,
pub y: u32, pub y: usize,
pub left_button_state: ElementState, pub left_button_state: ElementState,
pub middle_button_state: ElementState, pub middle_button_state: ElementState,
pub right_button_state: ElementState, pub right_button_state: ElementState,
@ -315,13 +315,11 @@ impl<N: Notify> Processor<N> {
processor.ctx.terminal.dirty = true; processor.ctx.terminal.dirty = true;
}, },
CursorMoved { position: (x, y), modifiers, .. } => { CursorMoved { position: (x, y), modifiers, .. } => {
let x = x as i32; let x = limit(x as i32, 0, processor.ctx.size_info.width as i32);
let y = y as i32; let y = limit(y as i32, 0, processor.ctx.size_info.height as i32);
let x = limit(x, 0, processor.ctx.size_info.width as i32);
let y = limit(y, 0, processor.ctx.size_info.height as i32);
*hide_cursor = false; *hide_cursor = false;
processor.mouse_moved(x as u32, y as u32, modifiers); processor.mouse_moved(x as usize, y as usize, modifiers);
}, },
MouseWheel { delta, phase, modifiers, .. } => { MouseWheel { delta, phase, modifiers, .. } => {
*hide_cursor = false; *hide_cursor = false;

View File

@ -266,55 +266,52 @@ impl From<&'static str> for Action {
impl<'a, A: ActionContext + 'a> Processor<'a, A> { impl<'a, A: ActionContext + 'a> Processor<'a, A> {
#[inline] #[inline]
pub fn mouse_moved(&mut self, x: u32, y: u32, modifiers: ModifiersState) { pub fn mouse_moved(&mut self, x: usize, y: usize, modifiers: ModifiersState) {
self.ctx.mouse_mut().x = x; self.ctx.mouse_mut().x = x;
self.ctx.mouse_mut().y = y; self.ctx.mouse_mut().y = y;
let size_info = self.ctx.size_info(); let size_info = self.ctx.size_info();
if let Some(point) = size_info.pixels_to_coords(x as usize, y as usize) { let point = size_info.pixels_to_coords(x, y);
let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line);
let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col);
let cell_x = (x as usize - size_info.padding_x as usize) % size_info.cell_width as usize; let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line);
let half_cell_width = (size_info.cell_width / 2.0) as usize; let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col);
let cell_side = if cell_x > half_cell_width let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize;
// Edge case when mouse leaves the window let half_cell_width = (size_info.cell_width / 2.0) as usize;
|| x as f32 >= size_info.width - size_info.padding_x
{
Side::Right
} else {
Side::Left
};
self.ctx.mouse_mut().cell_side = cell_side;
let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG; let cell_side = if cell_x > half_cell_width
if self.ctx.mouse_mut().left_button_state == ElementState::Pressed // Edge case when mouse leaves the window
&& ( || x as f32 >= size_info.width - size_info.padding_x
modifiers.shift {
|| !self.ctx.terminal_mode().intersects(TermMode::MOUSE_REPORT_CLICK | motion_mode) Side::Right
) } else {
{ Side::Left
self.ctx.update_selection(Point { };
line: point.line, self.ctx.mouse_mut().cell_side = cell_side;
col: point.col
}, cell_side); let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG;
} else if self.ctx.terminal_mode().intersects(motion_mode) let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode;
// Only report motion when changing cells
&& ( if self.ctx.mouse_mut().left_button_state == ElementState::Pressed &&
prev_line != self.ctx.mouse_mut().line ( modifiers.shift || !self.ctx.terminal_mode().intersects(report_mode))
|| prev_col != self.ctx.mouse_mut().column {
) self.ctx.update_selection(Point {
{ line: point.line,
if self.ctx.mouse_mut().left_button_state == ElementState::Pressed { col: point.col
self.mouse_report(32, ElementState::Pressed, modifiers); }, cell_side);
} else if self.ctx.mouse_mut().middle_button_state == ElementState::Pressed { } else if self.ctx.terminal_mode().intersects(motion_mode)
self.mouse_report(33, ElementState::Pressed, modifiers); // Only report motion when changing cells
} else if self.ctx.mouse_mut().right_button_state == ElementState::Pressed { && (prev_line != self.ctx.mouse_mut().line || prev_col != self.ctx.mouse_mut().column)
self.mouse_report(34, ElementState::Pressed, modifiers); && size_info.contains_point(x, y)
} else if self.ctx.terminal_mode().contains(TermMode::MOUSE_MOTION) { {
self.mouse_report(35, ElementState::Pressed, modifiers); if self.ctx.mouse_mut().left_button_state == ElementState::Pressed {
} self.mouse_report(32, ElementState::Pressed, modifiers);
} else if self.ctx.mouse_mut().middle_button_state == ElementState::Pressed {
self.mouse_report(33, ElementState::Pressed, modifiers);
} else if self.ctx.mouse_mut().right_button_state == ElementState::Pressed {
self.mouse_report(34, ElementState::Pressed, modifiers);
} else if self.ctx.terminal_mode().contains(TermMode::MOUSE_MOTION) {
self.mouse_report(35, ElementState::Pressed, modifiers);
} }
} }
} }

View File

@ -253,8 +253,31 @@ impl Selection {
// Handle some edge cases // Handle some edge cases
if start.line > end.line { if start.line > end.line {
start.col += 1; if end.col > Column(0) {
end.col -= 1; start.col += 1;
end.col -= 1;
}
// Special case for when a multi-line selection to the
// bottom ends on a new line with just one cell selected
// and the first cell should not be selected
else {
if start_side == Side::Right {
start.col += 1;
}
// Remove the single selected cell if mouse left window
if end_side == Side::Left {
end.line += 1;
end.col = cols - 1;
}
return Some(Span {
cols,
front: end,
tail: start,
ty: SpanType::Inclusive,
});
}
} else if start.line < end.line { } else if start.line < end.line {
start.col -= 1; start.col -= 1;
end.col += 1; end.col += 1;

View File

@ -791,25 +791,21 @@ impl SizeInfo {
Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize)
} }
fn contains_point(&self, x: usize, y:usize) -> bool { pub fn contains_point(&self, x: usize, y:usize) -> bool {
x <= (self.width - self.padding_x) as usize && x <= (self.width - self.padding_x) as usize &&
x >= self.padding_x as usize && x >= self.padding_x as usize &&
y <= (self.height - self.padding_y) as usize && y <= (self.height - self.padding_y) as usize &&
y >= self.padding_y as usize y >= self.padding_y as usize
} }
pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<Point> { pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point {
if !self.contains_point(x, y) { let col = Column(x.saturating_sub(self.padding_x as usize) / (self.cell_width as usize));
return None; let line = Line(y.saturating_sub(self.padding_y as usize) / (self.cell_height as usize));
}
let col = Column((x - self.padding_x as usize) / (self.cell_width as usize)); Point {
let line = Line((y - self.padding_y as usize) / (self.cell_height as usize));
Some(Point {
line: min(line, self.lines() - 1), line: min(line, self.lines() - 1),
col: min(col, self.cols() - 1) col: min(col, self.cols() - 1)
}) }
} }
} }
@ -1036,7 +1032,11 @@ impl Term {
/// ///
/// Returns None if the coordinates are outside the screen /// Returns None if the coordinates are outside the screen
pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<Point> { pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<Point> {
self.size_info().pixels_to_coords(x, y) if self.size_info.contains_point(x, y) {
Some(self.size_info.pixels_to_coords(x, y))
} else {
None
}
} }
/// Access to the raw grid data structure /// Access to the raw grid data structure