Fix unscaled scrolling when using an imprecise mouse wheel

Mikayla Maki created

Change summary

crates/editor/src/element.rs             | 10 +++++-
crates/gpui/src/elements/flex.rs         | 12 ++++---
crates/gpui/src/elements/list.rs         |  4 +-
crates/gpui/src/elements/uniform_list.rs | 14 +++++---
crates/gpui/src/platform/event.rs        | 40 ++++++++++++++++++++++++-
crates/gpui/src/platform/mac/event.rs    | 19 ++++++++---
crates/terminal/src/mappings/mouse.rs    |  2 
crates/terminal/src/terminal.rs          | 10 +++---
8 files changed, 83 insertions(+), 28 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -192,8 +192,14 @@ impl EditorElement {
                 .on_scroll({
                     let position_map = position_map.clone();
                     move |e, cx| {
-                        if !Self::scroll(e.position, e.delta, e.precise, &position_map, bounds, cx)
-                        {
+                        if !Self::scroll(
+                            e.position,
+                            *e.delta.raw(),
+                            e.delta.precise(),
+                            &position_map,
+                            bounds,
+                            cx,
+                        ) {
                             cx.propagate_event()
                         }
                     }

crates/gpui/src/elements/flex.rs 🔗

@@ -257,17 +257,19 @@ impl Element for Flex {
                         let axis = self.axis;
                         move |e, cx| {
                             if remaining_space < 0. {
+                                let scroll_delta = e.delta.raw();
+
                                 let mut delta = match axis {
                                     Axis::Horizontal => {
-                                        if e.delta.x().abs() >= e.delta.y().abs() {
-                                            e.delta.x()
+                                        if scroll_delta.x().abs() >= scroll_delta.y().abs() {
+                                            scroll_delta.x()
                                         } else {
-                                            e.delta.y()
+                                            scroll_delta.y()
                                         }
                                     }
-                                    Axis::Vertical => e.delta.y(),
+                                    Axis::Vertical => scroll_delta.y(),
                                 };
-                                if !e.precise {
+                                if !e.delta.precise() {
                                     delta *= 20.;
                                 }
 

crates/gpui/src/elements/list.rs 🔗

@@ -258,8 +258,8 @@ impl Element for List {
                     state.0.borrow_mut().scroll(
                         &scroll_top,
                         height,
-                        e.platform_event.delta,
-                        e.platform_event.precise,
+                        *e.platform_event.delta.raw(),
+                        e.platform_event.delta.precise(),
                         cx,
                     )
                 }

crates/gpui/src/elements/uniform_list.rs 🔗

@@ -295,15 +295,19 @@ impl Element for UniformList {
                 move |MouseScrollWheel {
                           platform_event:
                               ScrollWheelEvent {
-                                  position,
-                                  delta,
-                                  precise,
-                                  ..
+                                  position, delta, ..
                               },
                           ..
                       },
                       cx| {
-                    if !Self::scroll(state.clone(), position, delta, precise, scroll_max, cx) {
+                    if !Self::scroll(
+                        state.clone(),
+                        position,
+                        *delta.raw(),
+                        delta.precise(),
+                        scroll_max,
+                        cx,
+                    ) {
                         cx.propagate_event();
                     }
                 }

crates/gpui/src/platform/event.rs 🔗

@@ -1,5 +1,7 @@
 use std::ops::Deref;
 
+use pathfinder_geometry::vector::vec2f;
+
 use crate::{geometry::vector::Vector2F, keymap::Keystroke};
 
 #[derive(Clone, Debug)]
@@ -44,11 +46,45 @@ pub enum TouchPhase {
     Ended,
 }
 
+#[derive(Clone, Copy, Debug)]
+pub enum ScrollDelta {
+    Pixels(Vector2F),
+    Lines(Vector2F),
+}
+
+impl Default for ScrollDelta {
+    fn default() -> Self {
+        Self::Lines(Default::default())
+    }
+}
+
+impl ScrollDelta {
+    pub fn raw(&self) -> &Vector2F {
+        match self {
+            ScrollDelta::Pixels(v) => v,
+            ScrollDelta::Lines(v) => v,
+        }
+    }
+
+    pub fn precise(&self) -> bool {
+        match self {
+            ScrollDelta::Pixels(_) => true,
+            ScrollDelta::Lines(_) => false,
+        }
+    }
+
+    pub fn pixel_delta(&self, line_height: f32) -> Vector2F {
+        match self {
+            ScrollDelta::Pixels(delta) => *delta,
+            ScrollDelta::Lines(delta) => vec2f(delta.x() * line_height, delta.y() * line_height),
+        }
+    }
+}
+
 #[derive(Clone, Copy, Debug, Default)]
 pub struct ScrollWheelEvent {
     pub position: Vector2F,
-    pub delta: Vector2F,
-    pub precise: bool,
+    pub delta: ScrollDelta,
     pub modifiers: Modifiers,
     /// If the platform supports returning the phase of a scroll wheel event, it will be stored here
     pub phase: Option<TouchPhase>,

crates/gpui/src/platform/mac/event.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     keymap::Keystroke,
     platform::{Event, NavigationDirection},
     KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
-    MouseMovedEvent, ScrollWheelEvent, TouchPhase,
+    MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase,
 };
 use cocoa::{
     appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
@@ -164,17 +164,24 @@ impl Event {
                     _ => Some(TouchPhase::Moved),
                 };
 
+                let raw_data = vec2f(
+                    native_event.scrollingDeltaX() as f32,
+                    native_event.scrollingDeltaY() as f32,
+                );
+
+                let delta = if native_event.hasPreciseScrollingDeltas() == YES {
+                    ScrollDelta::Pixels(raw_data)
+                } else {
+                    ScrollDelta::Lines(raw_data)
+                };
+
                 Self::ScrollWheel(ScrollWheelEvent {
                     position: vec2f(
                         native_event.locationInWindow().x as f32,
                         window_height - native_event.locationInWindow().y as f32,
                     ),
-                    delta: vec2f(
-                        native_event.scrollingDeltaX() as f32,
-                        native_event.scrollingDeltaY() as f32,
-                    ),
+                    delta,
                     phase,
-                    precise: native_event.hasPreciseScrollingDeltas() == YES,
                     modifiers: read_modifiers(native_event),
                 })
             }),

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

@@ -97,7 +97,7 @@ impl MouseButton {
     }
 
     fn from_scroll(e: &ScrollWheelEvent) -> Self {
-        if e.delta.y() > 0. {
+        if e.delta.raw().y() > 0. {
             MouseButton::ScrollUp
         } else {
             MouseButton::ScrollDown

crates/terminal/src/terminal.rs 🔗

@@ -1144,7 +1144,7 @@ impl Terminal {
 
     fn determine_scroll_lines(&mut self, e: &MouseScrollWheel, mouse_mode: bool) -> Option<i32> {
         let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER };
-
+        let line_height = self.last_content.size.line_height;
         match e.phase {
             /* Reset scroll state on started */
             Some(gpui::TouchPhase::Started) => {
@@ -1153,11 +1153,11 @@ impl Terminal {
             }
             /* Calculate the appropriate scroll lines */
             Some(gpui::TouchPhase::Moved) => {
-                let old_offset = (self.scroll_px / self.last_content.size.line_height) as i32;
+                let old_offset = (self.scroll_px / line_height) as i32;
 
-                self.scroll_px += e.delta.y() * scroll_multiplier;
+                self.scroll_px += e.delta.pixel_delta(line_height).y() * scroll_multiplier;
 
-                let new_offset = (self.scroll_px / self.last_content.size.line_height) as i32;
+                let new_offset = (self.scroll_px / line_height) as i32;
 
                 // Whenever we hit the edges, reset our stored scroll to 0
                 // so we can respond to changes in direction quickly
@@ -1167,7 +1167,7 @@ impl Terminal {
             }
             /* Fall back to delta / line_height */
             None => Some(
-                ((e.delta.y() * scroll_multiplier) / self.last_content.size.line_height) as i32,
+                ((e.delta.pixel_delta(line_height).y() * scroll_multiplier) / line_height) as i32,
             ),
             _ => None,
         }