detached a9f60b2

fix(cef): delay touchpad navigation indicator

commit
a9f60b26a32458f0653e3b7e3007746c77a98a0a
author
brice · · 2026-06-18 07:30 UTC
parent
4cf5b9e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +53 -10
M internal/infrastructure/cef/touchpad_navigation.go +10 -6
@@ -9,8 +9,9 @@ import (
99 )
1010
1111 const (
12- defaultTouchpadNavigationCommitDistance = 320.0
13- defaultTouchpadNavigationVerticalRatio = 0.5
12+ defaultTouchpadNavigationCommitDistance = 320.0
13+ defaultTouchpadNavigationVerticalRatio = 0.5
14+ touchpadNavigationIndicatorActivationFraction = 0.5
1415 )
1516
1617 type touchpadNavigationRecognizer struct {
@@ -84,6 +85,9 @@ func (r *touchpadNavigationRecognizer) handleUpdate(input touchpadNavigationInpu
8485 threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta)
8586 progress := clampFloat(math.Abs(r.cumulativeDX)/threshold, 0, 1)
8687 r.thresholdReached = progress >= 1
88+ if !r.indicatorShown && progress < touchpadNavigationIndicatorActivationFraction {
89+ return touchpadNavigationResult{}
90+ }
8791 r.indicatorShown = true
8892 r.lastIndicatorAction = action
8993 return touchpadNavigationResult{
@@ -113,12 +117,12 @@ func (r *touchpadNavigationRecognizer) handleEnd(input touchpadNavigationInput)
113117 }
114118
115119 func (r *touchpadNavigationRecognizer) finishIndicator(input touchpadNavigationInput, triggered bool) touchpadNavigationResult {
116- action, ok := r.indicatorAction(input)
117- if !ok && r.indicatorShown {
118- action, ok = r.lastIndicatorAction, true
120+ if !r.indicatorShown {
121+ return touchpadNavigationResult{}
119122 }
123+ action, ok := r.indicatorAction(input)
120124 if !ok {
121- return touchpadNavigationResult{}
125+ action, ok = r.lastIndicatorAction, true
122126 }
123127 threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta)
124128 return touchpadNavigationResult{
@@ -9,8 +9,9 @@ import (
9 )9 )
10 10
11 const (11 const (
12- defaultTouchpadNavigationCommitDistance = 320.012+ defaultTouchpadNavigationCommitDistance = 320.0
13- defaultTouchpadNavigationVerticalRatio = 0.513+ defaultTouchpadNavigationVerticalRatio = 0.5
14+ touchpadNavigationIndicatorActivationFraction = 0.5
14 )15 )
15 16
16 type touchpadNavigationRecognizer struct {17 type touchpadNavigationRecognizer struct {
@@ -84,6 +85,9 @@ func (r *touchpadNavigationRecognizer) handleUpdate(input touchpadNavigationInpu
84 threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta)85 threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta)
85 progress := clampFloat(math.Abs(r.cumulativeDX)/threshold, 0, 1)86 progress := clampFloat(math.Abs(r.cumulativeDX)/threshold, 0, 1)
86 r.thresholdReached = progress >= 187 r.thresholdReached = progress >= 1
88+ if !r.indicatorShown && progress < touchpadNavigationIndicatorActivationFraction {
89+ return touchpadNavigationResult{}
90+ }
87 r.indicatorShown = true91 r.indicatorShown = true
88 r.lastIndicatorAction = action92 r.lastIndicatorAction = action
89 return touchpadNavigationResult{93 return touchpadNavigationResult{
@@ -113,12 +117,12 @@ func (r *touchpadNavigationRecognizer) handleEnd(input touchpadNavigationInput)
113 }117 }
114 118
115 func (r *touchpadNavigationRecognizer) finishIndicator(input touchpadNavigationInput, triggered bool) touchpadNavigationResult {119 func (r *touchpadNavigationRecognizer) finishIndicator(input touchpadNavigationInput, triggered bool) touchpadNavigationResult {
116- action, ok := r.indicatorAction(input)120+ if !r.indicatorShown {
117- if !ok && r.indicatorShown {121+ return touchpadNavigationResult{}
118- action, ok = r.lastIndicatorAction, true
119 }122 }
123+ action, ok := r.indicatorAction(input)
120 if !ok {124 if !ok {
121- return touchpadNavigationResult{}125+ action, ok = r.lastIndicatorAction, true
122 }126 }
123 threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta)127 threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta)
124 return touchpadNavigationResult{128 return touchpadNavigationResult{
M internal/infrastructure/cef/touchpad_navigation_test.go +43 -4
@@ -26,21 +26,30 @@ func TestTouchpadNavigationRecognizer_ReportsProgressAndNavigatesOnReleaseAfterT
2626 require.False(t, begin.HasIndicator)
2727 require.False(t, begin.HasAction)
2828
29- progress := recognizer.Handle(touchpadNavigationInput{
29+ ordinaryScroll := recognizer.Handle(touchpadNavigationInput{
3030 Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -80, 0),
3131 Config: cfg,
3232 CanGoBack: true,
3333 CanGoForward: true,
3434 })
35+ require.False(t, ordinaryScroll.HasIndicator, "ordinary horizontal scrolling below the activation distance should not show navigation UI")
36+ require.False(t, ordinaryScroll.HasAction)
37+
38+ progress := recognizer.Handle(touchpadNavigationInput{
39+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -40, 0),
40+ Config: cfg,
41+ CanGoBack: true,
42+ CanGoForward: true,
43+ })
3544 require.True(t, progress.HasIndicator)
3645 require.Equal(t, port.TouchpadNavigationBack, progress.Indicator.Action)
3746 require.True(t, progress.Indicator.Active)
38- require.InDelta(t, 0.4, progress.Indicator.Progress, 0.001)
47+ require.InDelta(t, 0.6, progress.Indicator.Progress, 0.001)
3948 require.False(t, progress.Indicator.ThresholdReached)
4049 require.False(t, progress.HasAction)
4150
4251 armed := recognizer.Handle(touchpadNavigationInput{
43- Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -140, 0),
52+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -100, 0),
4453 Config: cfg,
4554 CanGoBack: true,
4655 CanGoForward: true,
@@ -64,7 +73,37 @@ func TestTouchpadNavigationRecognizer_ReportsProgressAndNavigatesOnReleaseAfterT
6473 require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action)
6574 }
6675
67-func TestTouchpadNavigationRecognizer_DoesNotNavigateBelowThreshold(t *testing.T) {
76+func TestTouchpadNavigationRecognizer_DoesNotShowIndicatorBelowActivationDistance(t *testing.T) {
77+ recognizer := newTouchpadNavigationRecognizer()
78+ cfg := RuntimeInputConfig{
79+ TouchpadNavigationEnabled: true,
80+ TouchpadNavigationMinDelta: 200,
81+ TouchpadNavigationMaxVerticalRatio: 0.5,
82+ }
83+
84+ _ = recognizer.Handle(touchpadNavigationInput{
85+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0),
86+ Config: cfg,
87+ CanGoBack: true,
88+ })
89+ ordinaryScroll := recognizer.Handle(touchpadNavigationInput{
90+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -80, 0),
91+ Config: cfg,
92+ CanGoBack: true,
93+ })
94+ require.False(t, ordinaryScroll.HasIndicator)
95+ require.False(t, ordinaryScroll.HasAction)
96+
97+ end := recognizer.Handle(touchpadNavigationInput{
98+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0),
99+ Config: cfg,
100+ CanGoBack: true,
101+ })
102+ require.False(t, end.HasAction)
103+ require.False(t, end.HasIndicator)
104+}
105+
106+func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenReleasedAfterActivationBelowThreshold(t *testing.T) {
68107 recognizer := newTouchpadNavigationRecognizer()
69108 cfg := RuntimeInputConfig{
70109 TouchpadNavigationEnabled: true,
@@ -26,21 +26,30 @@ func TestTouchpadNavigationRecognizer_ReportsProgressAndNavigatesOnReleaseAfterT
26 require.False(t, begin.HasIndicator)26 require.False(t, begin.HasIndicator)
27 require.False(t, begin.HasAction)27 require.False(t, begin.HasAction)
28 28
29- progress := recognizer.Handle(touchpadNavigationInput{29+ ordinaryScroll := recognizer.Handle(touchpadNavigationInput{
30 Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -80, 0),30 Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -80, 0),
31 Config: cfg,31 Config: cfg,
32 CanGoBack: true,32 CanGoBack: true,
33 CanGoForward: true,33 CanGoForward: true,
34 })34 })
35+ require.False(t, ordinaryScroll.HasIndicator, "ordinary horizontal scrolling below the activation distance should not show navigation UI")
36+ require.False(t, ordinaryScroll.HasAction)
37+
38+ progress := recognizer.Handle(touchpadNavigationInput{
39+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -40, 0),
40+ Config: cfg,
41+ CanGoBack: true,
42+ CanGoForward: true,
43+ })
35 require.True(t, progress.HasIndicator)44 require.True(t, progress.HasIndicator)
36 require.Equal(t, port.TouchpadNavigationBack, progress.Indicator.Action)45 require.Equal(t, port.TouchpadNavigationBack, progress.Indicator.Action)
37 require.True(t, progress.Indicator.Active)46 require.True(t, progress.Indicator.Active)
38- require.InDelta(t, 0.4, progress.Indicator.Progress, 0.001)47+ require.InDelta(t, 0.6, progress.Indicator.Progress, 0.001)
39 require.False(t, progress.Indicator.ThresholdReached)48 require.False(t, progress.Indicator.ThresholdReached)
40 require.False(t, progress.HasAction)49 require.False(t, progress.HasAction)
41 50
42 armed := recognizer.Handle(touchpadNavigationInput{51 armed := recognizer.Handle(touchpadNavigationInput{
43- Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -140, 0),52+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -100, 0),
44 Config: cfg,53 Config: cfg,
45 CanGoBack: true,54 CanGoBack: true,
46 CanGoForward: true,55 CanGoForward: true,
@@ -64,7 +73,37 @@ func TestTouchpadNavigationRecognizer_ReportsProgressAndNavigatesOnReleaseAfterT
64 require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action)73 require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action)
65 }74 }
66 75
67-func TestTouchpadNavigationRecognizer_DoesNotNavigateBelowThreshold(t *testing.T) {76+func TestTouchpadNavigationRecognizer_DoesNotShowIndicatorBelowActivationDistance(t *testing.T) {
77+ recognizer := newTouchpadNavigationRecognizer()
78+ cfg := RuntimeInputConfig{
79+ TouchpadNavigationEnabled: true,
80+ TouchpadNavigationMinDelta: 200,
81+ TouchpadNavigationMaxVerticalRatio: 0.5,
82+ }
83+
84+ _ = recognizer.Handle(touchpadNavigationInput{
85+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0),
86+ Config: cfg,
87+ CanGoBack: true,
88+ })
89+ ordinaryScroll := recognizer.Handle(touchpadNavigationInput{
90+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -80, 0),
91+ Config: cfg,
92+ CanGoBack: true,
93+ })
94+ require.False(t, ordinaryScroll.HasIndicator)
95+ require.False(t, ordinaryScroll.HasAction)
96+
97+ end := recognizer.Handle(touchpadNavigationInput{
98+ Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0),
99+ Config: cfg,
100+ CanGoBack: true,
101+ })
102+ require.False(t, end.HasAction)
103+ require.False(t, end.HasIndicator)
104+}
105+
106+func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenReleasedAfterActivationBelowThreshold(t *testing.T) {
68 recognizer := newTouchpadNavigationRecognizer()107 recognizer := newTouchpadNavigationRecognizer()
69 cfg := RuntimeInputConfig{108 cfg := RuntimeInputConfig{
70 TouchpadNavigationEnabled: true,109 TouchpadNavigationEnabled: true,

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close