on main help sign in
fix(scrolling): stale scrolling acceleration (#3197)
7206981Charles Dong
internal/ui/model/filter.go+10
@@ -38,6 +38,12 @@ func (f *Filter) Filter(_ tea.Model, msg tea.Msg) tea.Msg {
38 case tea.MouseWheelMsg:
38 case tea.MouseWheelMsg:
39 mouse := typed.Mouse()
39 mouse := typed.Mouse()
40 dx, dy := wheelDeltas(mouse)
40 dx, dy := wheelDeltas(mouse)
41+ if oppositeSign(f.wheelDX, dx) {
42+ f.wheelDX = 0
43+ }
44+ if oppositeSign(f.wheelDY, dy) {
45+ f.wheelDY = 0
46+ }
41 f.wheelDX += dx
47 f.wheelDX += dx
42 f.wheelDY += dy
48 f.wheelDY += dy
43 if !f.allow(&f.lastWheel) {
49 if !f.allow(&f.lastWheel) {
@@ -96,3 +102,7 @@ func wheelDeltas(mouse tea.Mouse) (float64, float64) {
96 return 0, 0
102 return 0, 0
97 }
103 }
98}
104}
105+
106+func oppositeSign(a, b float64) bool {
107+ return (a < 0 && b > 0) || (a > 0 && b < 0)
108+}
internal/ui/model/filter_test.go+26−1
@@ -61,7 +61,7 @@ func TestFilter_CoalescesSameDirection(t *testing.T) {
61 require.Equal(t, float64(3), msg.DeltaY, "should accumulate 3 down events")
61 require.Equal(t, float64(3), msg.DeltaY, "should accumulate 3 down events")
62}
62}
63
63
64−func TestFilter_DirectionChangeResetsAccumulator(t *testing.T) {
64+func TestFilter_DirectionChangeWithinWindowAccumulatesNewDirection(t *testing.T) {
65 t.Parallel()
65 t.Parallel()
66 f, now := newTestFilter(t)
66 f, now := newTestFilter(t)
67
67
@@ -81,6 +81,31 @@ func TestFilter_DirectionChangeResetsAccumulator(t *testing.T) {
81 require.Equal(t, float64(-2), msg.DeltaY, "should have 2 accumulated up events")
81 require.Equal(t, float64(-2), msg.DeltaY, "should have 2 accumulated up events")
82}
82}
83
83
84+func TestFilter_DirectionChangeDropsStaleAccumulator(t *testing.T) {
85+ t.Parallel()
86+ f, now := newTestFilter(t)
87+
88+ // Scroll up
89+ f.Filter(nil, wheelUp())
90+
91+ // Scroll up again
92+ *now = now.Add(5 * time.Millisecond)
93+ result := f.Filter(nil, wheelUp())
94+ require.Nil(t, result)
95+
96+ // Scroll down (accumulation should reset)
97+ *now = now.Add(5 * time.Millisecond)
98+ result = f.Filter(nil, wheelDown())
99+ require.Nil(t, result)
100+
101+ // And scroll down again
102+ *now = now.Add(20 * time.Millisecond)
103+ result = f.Filter(nil, wheelDown())
104+ msg, ok := result.(common.CoalescedWheelMsg)
105+ require.True(t, ok)
106+ require.Equal(t, float64(2), msg.DeltaY, "should drop stale opposite-direction events")
107+}
108+
84func TestFilter_WindowExpiryEmitsAccumulated(t *testing.T) {
109func TestFilter_WindowExpiryEmitsAccumulated(t *testing.T) {
85 t.Parallel()
110 t.Parallel()
86 f, now := newTestFilter(t)
111 f, now := newTestFilter(t)