Skip to content

Commit 1b7298c

Browse files
authored
Separate scrolling from clicking (#603)
Closes #410 Closes #588
1 parent a41267a commit 1b7298c

File tree

1 file changed

+109
-107
lines changed

1 file changed

+109
-107
lines changed

src/tui.rs

Lines changed: 109 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ impl Tui {
569569
&& next_state != InputMouseState::None;
570570
let mouse_up = self.mouse_state != InputMouseState::None
571571
&& next_state == InputMouseState::None;
572+
let is_scroll = next_scroll != Point::default();
572573
let is_drag = self.mouse_state == InputMouseState::Left
573574
&& next_state == InputMouseState::Left
574575
&& next_position != self.mouse_position;
@@ -607,7 +608,11 @@ impl Tui {
607608
}
608609
}
609610

610-
if mouse_down {
611+
if is_scroll {
612+
next_state = self.mouse_state;
613+
} else if is_drag {
614+
self.mouse_is_drag = true;
615+
} else if mouse_down {
611616
// Transition from no mouse input to some mouse input --> Record the mouse down position.
612617
Self::build_node_path(hovered_node, &mut self.mouse_down_node_path);
613618

@@ -662,8 +667,6 @@ impl Tui {
662667
}
663668

664669
self.mouse_up_timestamp = now;
665-
} else if is_drag {
666-
self.mouse_is_drag = true;
667670
}
668671

669672
input_mouse_modifiers = mouse.modifiers;
@@ -2186,120 +2189,120 @@ impl<'a> Context<'a, '_> {
21862189
let mut make_cursor_visible = false;
21872190
let mut change_preferred_column = false;
21882191

2189-
if self.tui.mouse_state != InputMouseState::None
2190-
&& self.tui.was_mouse_down_on_node(node_prev.id)
2192+
// Scrolling works even if the node isn't focused.
2193+
if self.input_scroll_delta != Point::default()
2194+
&& node_prev.inner_clipped.contains(self.tui.mouse_position)
21912195
{
2192-
// Scrolling works even if the node isn't focused.
2193-
if self.tui.mouse_state == InputMouseState::Scroll {
2194-
tc.scroll_offset.x += self.input_scroll_delta.x;
2195-
tc.scroll_offset.y += self.input_scroll_delta.y;
2196-
self.set_input_consumed();
2197-
} else if self.tui.is_node_focused(node_prev.id) {
2198-
let mouse = self.tui.mouse_position;
2199-
let inner = node_prev.inner;
2200-
let text_rect = Rect {
2201-
left: inner.left + tb.margin_width(),
2202-
top: inner.top,
2203-
right: inner.right - !single_line as CoordType,
2204-
bottom: inner.bottom,
2205-
};
2206-
let track_rect = Rect {
2207-
left: text_rect.right,
2208-
top: inner.top,
2209-
right: inner.right,
2210-
bottom: inner.bottom,
2211-
};
2212-
let pos = Point {
2213-
x: mouse.x - inner.left - tb.margin_width() + tc.scroll_offset.x,
2214-
y: mouse.y - inner.top + tc.scroll_offset.y,
2215-
};
2196+
tc.scroll_offset.x += self.input_scroll_delta.x;
2197+
tc.scroll_offset.y += self.input_scroll_delta.y;
2198+
self.set_input_consumed();
2199+
return make_cursor_visible;
2200+
} else if self.tui.mouse_state != InputMouseState::None
2201+
&& self.tui.is_node_focused(node_prev.id)
2202+
{
2203+
let mouse = self.tui.mouse_position;
2204+
let inner = node_prev.inner;
2205+
let text_rect = Rect {
2206+
left: inner.left + tb.margin_width(),
2207+
top: inner.top,
2208+
right: inner.right - !single_line as CoordType,
2209+
bottom: inner.bottom,
2210+
};
2211+
let track_rect = Rect {
2212+
left: text_rect.right,
2213+
top: inner.top,
2214+
right: inner.right,
2215+
bottom: inner.bottom,
2216+
};
2217+
let pos = Point {
2218+
x: mouse.x - inner.left - tb.margin_width() + tc.scroll_offset.x,
2219+
y: mouse.y - inner.top + tc.scroll_offset.y,
2220+
};
22162221

2217-
if text_rect.contains(self.tui.mouse_down_position) {
2218-
if self.tui.mouse_is_drag {
2219-
tb.selection_update_visual(pos);
2220-
tc.preferred_column = tb.cursor_visual_pos().x;
2222+
if text_rect.contains(self.tui.mouse_down_position) {
2223+
if self.tui.mouse_is_drag {
2224+
tb.selection_update_visual(pos);
2225+
tc.preferred_column = tb.cursor_visual_pos().x;
22212226

2222-
let height = inner.height();
2227+
let height = inner.height();
22232228

2224-
// If the editor is only 1 line tall we can't possibly scroll up or down.
2225-
if height >= 2 {
2226-
fn calc(min: CoordType, max: CoordType, mouse: CoordType) -> CoordType {
2227-
// Otherwise, the scroll zone is up to 3 lines at the top/bottom.
2228-
let zone_height = ((max - min) / 2).min(3);
2229+
// If the editor is only 1 line tall we can't possibly scroll up or down.
2230+
if height >= 2 {
2231+
fn calc(min: CoordType, max: CoordType, mouse: CoordType) -> CoordType {
2232+
// Otherwise, the scroll zone is up to 3 lines at the top/bottom.
2233+
let zone_height = ((max - min) / 2).min(3);
22292234

2230-
// The .y positions where the scroll zones begin:
2231-
// Mouse coordinates above top and below bottom respectively.
2232-
let scroll_min = min + zone_height;
2233-
let scroll_max = max - zone_height - 1;
2235+
// The .y positions where the scroll zones begin:
2236+
// Mouse coordinates above top and below bottom respectively.
2237+
let scroll_min = min + zone_height;
2238+
let scroll_max = max - zone_height - 1;
22342239

2235-
// Calculate the delta for scrolling up or down.
2236-
let delta_min = (mouse - scroll_min).clamp(-zone_height, 0);
2237-
let delta_max = (mouse - scroll_max).clamp(0, zone_height);
2240+
// Calculate the delta for scrolling up or down.
2241+
let delta_min = (mouse - scroll_min).clamp(-zone_height, 0);
2242+
let delta_max = (mouse - scroll_max).clamp(0, zone_height);
22382243

2239-
// If I didn't mess up my logic here, only one of the two values can possibly be !=0.
2240-
let idx = 3 + delta_min + delta_max;
2244+
// If I didn't mess up my logic here, only one of the two values can possibly be !=0.
2245+
let idx = 3 + delta_min + delta_max;
22412246

2242-
const SPEEDS: [CoordType; 7] = [-9, -3, -1, 0, 1, 3, 9];
2243-
let idx = idx.clamp(0, SPEEDS.len() as CoordType) as usize;
2244-
SPEEDS[idx]
2245-
}
2247+
const SPEEDS: [CoordType; 7] = [-9, -3, -1, 0, 1, 3, 9];
2248+
let idx = idx.clamp(0, SPEEDS.len() as CoordType) as usize;
2249+
SPEEDS[idx]
2250+
}
22462251

2247-
let delta_x = calc(text_rect.left, text_rect.right, mouse.x);
2248-
let delta_y = calc(text_rect.top, text_rect.bottom, mouse.y);
2252+
let delta_x = calc(text_rect.left, text_rect.right, mouse.x);
2253+
let delta_y = calc(text_rect.top, text_rect.bottom, mouse.y);
22492254

2250-
tc.scroll_offset.x += delta_x;
2251-
tc.scroll_offset.y += delta_y;
2255+
tc.scroll_offset.x += delta_x;
2256+
tc.scroll_offset.y += delta_y;
22522257

2253-
if delta_x != 0 || delta_y != 0 {
2254-
self.tui.read_timeout = time::Duration::from_millis(25);
2255-
}
2258+
if delta_x != 0 || delta_y != 0 {
2259+
self.tui.read_timeout = time::Duration::from_millis(25);
22562260
}
2257-
} else {
2258-
match self.input_mouse_click {
2259-
5.. => {}
2260-
4 => tb.select_all(),
2261-
3 => tb.select_line(),
2262-
2 => tb.select_word(),
2263-
_ => match self.tui.mouse_state {
2264-
InputMouseState::Left => {
2265-
if self.input_mouse_modifiers.contains(kbmod::SHIFT) {
2266-
// TODO: Untested because Windows Terminal surprisingly doesn't support Shift+Click.
2267-
tb.selection_update_visual(pos);
2268-
} else {
2269-
tb.cursor_move_to_visual(pos);
2270-
}
2271-
tc.preferred_column = tb.cursor_visual_pos().x;
2272-
make_cursor_visible = true;
2261+
}
2262+
} else {
2263+
match self.input_mouse_click {
2264+
5.. => {}
2265+
4 => tb.select_all(),
2266+
3 => tb.select_line(),
2267+
2 => tb.select_word(),
2268+
_ => match self.tui.mouse_state {
2269+
InputMouseState::Left => {
2270+
if self.input_mouse_modifiers.contains(kbmod::SHIFT) {
2271+
// TODO: Untested because Windows Terminal surprisingly doesn't support Shift+Click.
2272+
tb.selection_update_visual(pos);
2273+
} else {
2274+
tb.cursor_move_to_visual(pos);
22732275
}
2274-
_ => return false,
2275-
},
2276-
}
2276+
tc.preferred_column = tb.cursor_visual_pos().x;
2277+
make_cursor_visible = true;
2278+
}
2279+
_ => return false,
2280+
},
2281+
}
2282+
}
2283+
} else if track_rect.contains(self.tui.mouse_down_position) {
2284+
if self.tui.mouse_state == InputMouseState::Release {
2285+
tc.scroll_offset_y_drag_start = CoordType::MIN;
2286+
} else if self.tui.mouse_is_drag {
2287+
if tc.scroll_offset_y_drag_start == CoordType::MIN {
2288+
tc.scroll_offset_y_drag_start = tc.scroll_offset.y;
22772289
}
2278-
} else if track_rect.contains(self.tui.mouse_down_position) {
2279-
if self.tui.mouse_state == InputMouseState::Release {
2280-
tc.scroll_offset_y_drag_start = CoordType::MIN;
2281-
} else if self.tui.mouse_is_drag {
2282-
if tc.scroll_offset_y_drag_start == CoordType::MIN {
2283-
tc.scroll_offset_y_drag_start = tc.scroll_offset.y;
2284-
}
22852290

2286-
// The textarea supports 1 height worth of "scrolling beyond the end".
2287-
// `track_height` is the same as the viewport height.
2288-
let scrollable_height = tb.visual_line_count() - 1;
2291+
// The textarea supports 1 height worth of "scrolling beyond the end".
2292+
// `track_height` is the same as the viewport height.
2293+
let scrollable_height = tb.visual_line_count() - 1;
22892294

2290-
if scrollable_height > 0 {
2291-
let trackable = track_rect.height() - tc.thumb_height;
2292-
let delta_y = mouse.y - self.tui.mouse_down_position.y;
2293-
tc.scroll_offset.y = tc.scroll_offset_y_drag_start
2294-
+ (delta_y as i64 * scrollable_height as i64 / trackable as i64)
2295-
as CoordType;
2296-
}
2295+
if scrollable_height > 0 {
2296+
let trackable = track_rect.height() - tc.thumb_height;
2297+
let delta_y = mouse.y - self.tui.mouse_down_position.y;
2298+
tc.scroll_offset.y = tc.scroll_offset_y_drag_start
2299+
+ (delta_y as i64 * scrollable_height as i64 / trackable as i64)
2300+
as CoordType;
22972301
}
22982302
}
2299-
2300-
self.set_input_consumed();
23012303
}
23022304

2305+
self.set_input_consumed();
23032306
return make_cursor_visible;
23042307
}
23052308

@@ -2806,9 +2809,15 @@ impl<'a> Context<'a, '_> {
28062809
}
28072810

28082811
if !self.input_consumed {
2809-
if self.tui.mouse_state != InputMouseState::None {
2810-
let container_rect = prev_container.inner;
2812+
let container_rect = prev_container.inner;
28112813

2814+
if self.input_scroll_delta != Point::default()
2815+
&& container_rect.contains(self.tui.mouse_position)
2816+
{
2817+
sc.scroll_offset.x += self.input_scroll_delta.x;
2818+
sc.scroll_offset.y += self.input_scroll_delta.y;
2819+
self.set_input_consumed();
2820+
} else if self.tui.mouse_state != InputMouseState::None {
28122821
match self.tui.mouse_state {
28132822
InputMouseState::Left => {
28142823
if self.tui.mouse_is_drag {
@@ -2848,13 +2857,6 @@ impl<'a> Context<'a, '_> {
28482857
InputMouseState::Release => {
28492858
sc.scroll_offset_y_drag_start = CoordType::MIN;
28502859
}
2851-
InputMouseState::Scroll => {
2852-
if container_rect.contains(self.tui.mouse_position) {
2853-
sc.scroll_offset.x += self.input_scroll_delta.x;
2854-
sc.scroll_offset.y += self.input_scroll_delta.y;
2855-
self.set_input_consumed();
2856-
}
2857-
}
28582860
_ => {}
28592861
}
28602862
} else if self.tui.is_subtree_focused_alt(container_id, container_depth)
@@ -3586,7 +3588,7 @@ impl<'a> NodeMap<'a> {
35863588
}
35873589

35883590
/// Gets a node by its ID.
3589-
fn get(&mut self, id: u64) -> Option<&'a NodeCell<'a>> {
3591+
fn get(&self, id: u64) -> Option<&'a NodeCell<'a>> {
35903592
let shift = self.shift;
35913593
let mask = self.mask;
35923594
let mut slot = id >> shift;

0 commit comments

Comments
 (0)