fix(cef): address touchpad navigation review feedback
- commit
- 0d3b8ed9c553b94413c028f8b11b18d09f4f5288
- author
- brice · · 2026-06-18 09:05 UTC
- committer
- brice · · 2026-06-18 09:51 UTC
- parent
- 34fd6d3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +354 -63
M internal/application/port/webview.go +20 -4
| @@ -87,24 +87,40 @@ type Texture interface { | |||
| 87 | 87 | GoPointer() uintptr | |
| 88 | 88 | } | |
| 89 | 89 | ||
| 90 | - | // WebViewCallbacks defines callback handlers for WebView events. | |
| 91 | - | // Implementations should invoke these on the main thread/goroutine. | |
| 90 | + | // TouchpadNavigationAction indicates the direction of a touchpad navigation gesture. | |
| 92 | 91 | type TouchpadNavigationAction int | |
| 93 | 92 | ||
| 94 | 93 | const ( | |
| 94 | + | // TouchpadNavigationBack indicates a backward navigation gesture. | |
| 95 | 95 | TouchpadNavigationBack TouchpadNavigationAction = iota | |
| 96 | + | // TouchpadNavigationForward indicates a forward navigation gesture. | |
| 96 | 97 | TouchpadNavigationForward | |
| 97 | 98 | ) | |
| 98 | 99 | ||
| 100 | + | // String returns a human-readable representation of the navigation action. | |
| 101 | + | func (a TouchpadNavigationAction) String() string { | |
| 102 | + | switch a { | |
| 103 | + | case TouchpadNavigationBack: | |
| 104 | + | return "back" | |
| 105 | + | case TouchpadNavigationForward: | |
| 106 | + | return "forward" | |
| 107 | + | default: | |
| 108 | + | return "unknown" | |
| 109 | + | } | |
| 110 | + | } | |
| 111 | + | ||
| 99 | 112 | // TouchpadNavigationGesture describes visual progress for a deliberate | |
| 100 | 113 | // two-finger history navigation gesture. | |
| 101 | 114 | type TouchpadNavigationGesture struct { | |
| 102 | - | Action TouchpadNavigationAction | |
| 115 | + | Action TouchpadNavigationAction | |
| 116 | + | // Progress is normalized gesture progress from 0.0 to 1.0. | |
| 103 | 117 | Progress float64 | |
| 104 | 118 | ThresholdReached bool | |
| 105 | 119 | Active bool | |
| 106 | 120 | } | |
| 107 | 121 | ||
| 122 | + | // WebViewCallbacks defines callback handlers for WebView events. | |
| 123 | + | // Implementations should invoke these on the main thread/goroutine. | |
| 108 | 124 | type WebViewCallbacks struct { | |
| 109 | 125 | // OnLoadChanged is called when load state changes. | |
| 110 | 126 | OnLoadChanged func(event LoadEvent) | |
| @@ -153,7 +169,7 @@ type WebViewCallbacks struct { | |||
| 153 | 169 | ||
| 154 | 170 | // OnAudioStateChanged is called when audio playback starts or stops. | |
| 155 | 171 | OnAudioStateChanged func(playing bool) | |
| 156 | - | // OnTouchpadNavigationGesture is called while a CEF two-finger history | |
| 172 | + | // OnTouchpadNavigationGesture is called while a two-finger touchpad history | |
| 157 | 173 | // navigation gesture is progressing or finishing. | |
| 158 | 174 | OnTouchpadNavigationGesture func(gesture TouchpadNavigationGesture) | |
| 159 | 175 | } | |
| @@ -87,24 +87,40 @@ type Texture interface { | |||||
| 87 | GoPointer() uintptr | 87 | GoPointer() uintptr | ||
| 88 | } | 88 | } | ||
| 89 | 89 | ||||
| 90 | - | // WebViewCallbacks defines callback handlers for WebView events. | 90 | + | // TouchpadNavigationAction indicates the direction of a touchpad navigation gesture. |
| 91 | - | // Implementations should invoke these on the main thread/goroutine. | |||
| 92 | type TouchpadNavigationAction int | 91 | type TouchpadNavigationAction int | ||
| 93 | 92 | ||||
| 94 | const ( | 93 | const ( | ||
| 94 | + | // TouchpadNavigationBack indicates a backward navigation gesture. | |||
| 95 | TouchpadNavigationBack TouchpadNavigationAction = iota | 95 | TouchpadNavigationBack TouchpadNavigationAction = iota | ||
| 96 | + | // TouchpadNavigationForward indicates a forward navigation gesture. | |||
| 96 | TouchpadNavigationForward | 97 | TouchpadNavigationForward | ||
| 97 | ) | 98 | ) | ||
| 98 | 99 | ||||
| 100 | + | // String returns a human-readable representation of the navigation action. | |||
| 101 | + | func (a TouchpadNavigationAction) String() string { | |||
| 102 | + | switch a { | |||
| 103 | + | case TouchpadNavigationBack: | |||
| 104 | + | return "back" | |||
| 105 | + | case TouchpadNavigationForward: | |||
| 106 | + | return "forward" | |||
| 107 | + | default: | |||
| 108 | + | return "unknown" | |||
| 109 | + | } | |||
| 110 | + | } | |||
| 111 | + | ||||
| 99 | // TouchpadNavigationGesture describes visual progress for a deliberate | 112 | // TouchpadNavigationGesture describes visual progress for a deliberate | ||
| 100 | // two-finger history navigation gesture. | 113 | // two-finger history navigation gesture. | ||
| 101 | type TouchpadNavigationGesture struct { | 114 | type TouchpadNavigationGesture struct { | ||
| 102 | - | Action TouchpadNavigationAction | 115 | + | Action TouchpadNavigationAction |
| 116 | + | // Progress is normalized gesture progress from 0.0 to 1.0. | |||
| 103 | Progress float64 | 117 | Progress float64 | ||
| 104 | ThresholdReached bool | 118 | ThresholdReached bool | ||
| 105 | Active bool | 119 | Active bool | ||
| 106 | } | 120 | } | ||
| 107 | 121 | ||||
| 122 | + | // WebViewCallbacks defines callback handlers for WebView events. | |||
| 123 | + | // Implementations should invoke these on the main thread/goroutine. | |||
| 108 | type WebViewCallbacks struct { | 124 | type WebViewCallbacks struct { | ||
| 109 | // OnLoadChanged is called when load state changes. | 125 | // OnLoadChanged is called when load state changes. | ||
| 110 | OnLoadChanged func(event LoadEvent) | 126 | OnLoadChanged func(event LoadEvent) | ||
| @@ -153,7 +169,7 @@ type WebViewCallbacks struct { | |||||
| 153 | 169 | ||||
| 154 | // OnAudioStateChanged is called when audio playback starts or stops. | 170 | // OnAudioStateChanged is called when audio playback starts or stops. | ||
| 155 | OnAudioStateChanged func(playing bool) | 171 | OnAudioStateChanged func(playing bool) | ||
| 156 | - | // OnTouchpadNavigationGesture is called while a CEF two-finger history | 172 | + | // OnTouchpadNavigationGesture is called while a two-finger touchpad history |
| 157 | // navigation gesture is progressing or finishing. | 173 | // navigation gesture is progressing or finishing. | ||
| 158 | OnTouchpadNavigationGesture func(gesture TouchpadNavigationGesture) | 174 | OnTouchpadNavigationGesture func(gesture TouchpadNavigationGesture) | ||
| 159 | } | 175 | } | ||
M internal/infrastructure/cef/touchpad_navigation.go +24 -17
| @@ -93,8 +93,9 @@ func (r *touchpadNavigationRecognizer) handleUpdate(input touchpadNavigationInpu | |||
| 93 | 93 | } | |
| 94 | 94 | ||
| 95 | 95 | threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta) | |
| 96 | - | progress := clampFloat(math.Abs(r.cumulativeDX)/threshold, 0, 1) | |
| 97 | - | r.thresholdReached = progress >= 1 | |
| 96 | + | absDX := math.Abs(r.cumulativeDX) | |
| 97 | + | progress := clampFloat(absDX/threshold, 0, 1) | |
| 98 | + | r.thresholdReached = absDX > threshold | |
| 98 | 99 | if !r.indicatorShown && progress < touchpadNavigationIndicatorActivationFraction { | |
| 99 | 100 | return touchpadNavigationResult{} | |
| 100 | 101 | } | |
| @@ -147,23 +148,18 @@ func (r *touchpadNavigationRecognizer) finishIndicator(input touchpadNavigationI | |||
| 147 | 148 | } | |
| 148 | 149 | ||
| 149 | 150 | func (r *touchpadNavigationRecognizer) indicatorAction(input touchpadNavigationInput) (port.TouchpadNavigationAction, bool) { | |
| 150 | - | if r.cumulativeDX > 0 && input.CanGoBack && r.startedAtNavigationEdge(port.TouchpadNavigationBack) { | |
| 151 | - | return port.TouchpadNavigationBack, true | |
| 152 | - | } | |
| 153 | - | if r.cumulativeDX < 0 && input.CanGoForward && r.startedAtNavigationEdge(port.TouchpadNavigationForward) { | |
| 154 | - | return port.TouchpadNavigationForward, true | |
| 155 | - | } | |
| 156 | - | return port.TouchpadNavigationBack, false | |
| 151 | + | return r.resolvedDirection(input) | |
| 157 | 152 | } | |
| 158 | 153 | ||
| 159 | 154 | func (r *touchpadNavigationRecognizer) cancelAsTooVertical() touchpadNavigationResult { | |
| 160 | - | action, ok := r.indicatorAction(touchpadNavigationInput{}) | |
| 161 | - | if !ok && r.indicatorShown { | |
| 162 | - | action, ok = r.lastIndicatorAction, true | |
| 155 | + | var action port.TouchpadNavigationAction | |
| 156 | + | hasAction := r.indicatorShown | |
| 157 | + | if hasAction { | |
| 158 | + | action = r.lastIndicatorAction | |
| 163 | 159 | } | |
| 164 | 160 | r.reset() | |
| 165 | 161 | r.verticalCanceled = true | |
| 166 | - | if !ok { | |
| 162 | + | if !hasAction { | |
| 167 | 163 | return touchpadNavigationResult{} | |
| 168 | 164 | } | |
| 169 | 165 | return touchpadNavigationResult{ | |
| @@ -176,13 +172,24 @@ func (r *touchpadNavigationRecognizer) cancelAsTooVertical() touchpadNavigationR | |||
| 176 | 172 | } | |
| 177 | 173 | ||
| 178 | 174 | func (r *touchpadNavigationRecognizer) navigationAction(input touchpadNavigationInput) (cef2gtk.NavigationSwipeAction, bool) { | |
| 175 | + | action, ok := r.resolvedDirection(input) | |
| 176 | + | if !ok { | |
| 177 | + | return cef2gtk.NavigationSwipeBack, false | |
| 178 | + | } | |
| 179 | + | if action == port.TouchpadNavigationForward { | |
| 180 | + | return cef2gtk.NavigationSwipeForward, true | |
| 181 | + | } | |
| 182 | + | return cef2gtk.NavigationSwipeBack, true | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | func (r *touchpadNavigationRecognizer) resolvedDirection(input touchpadNavigationInput) (port.TouchpadNavigationAction, bool) { | |
| 179 | 186 | if r.cumulativeDX > 0 && input.CanGoBack && r.startedAtNavigationEdge(port.TouchpadNavigationBack) { | |
| 180 | - | return cef2gtk.NavigationSwipeBack, true | |
| 187 | + | return port.TouchpadNavigationBack, true | |
| 181 | 188 | } | |
| 182 | 189 | if r.cumulativeDX < 0 && input.CanGoForward && r.startedAtNavigationEdge(port.TouchpadNavigationForward) { | |
| 183 | - | return cef2gtk.NavigationSwipeForward, true | |
| 190 | + | return port.TouchpadNavigationForward, true | |
| 184 | 191 | } | |
| 185 | - | return cef2gtk.NavigationSwipeBack, false | |
| 192 | + | return port.TouchpadNavigationBack, false | |
| 186 | 193 | } | |
| 187 | 194 | ||
| 188 | 195 | func (r *touchpadNavigationRecognizer) setGestureStart(input touchpadNavigationInput) { | |
| @@ -193,7 +200,7 @@ func (r *touchpadNavigationRecognizer) setGestureStart(input touchpadNavigationI | |||
| 193 | 200 | ||
| 194 | 201 | func (r *touchpadNavigationRecognizer) startedAtNavigationEdge(action port.TouchpadNavigationAction) bool { | |
| 195 | 202 | if r.gestureViewWidth <= 0 { | |
| 196 | - | return true | |
| 203 | + | return false | |
| 197 | 204 | } | |
| 198 | 205 | edgeDistance := math.Min(r.gestureViewWidth*touchpadNavigationEdgeFraction, touchpadNavigationMaxEdgeDistance) | |
| 199 | 206 | if action == port.TouchpadNavigationForward { | |
| @@ -93,8 +93,9 @@ func (r *touchpadNavigationRecognizer) handleUpdate(input touchpadNavigationInpu | |||||
| 93 | } | 93 | } | ||
| 94 | 94 | ||||
| 95 | threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta) | 95 | threshold := normalizedTouchpadNavigationMinDelta(input.Config.TouchpadNavigationMinDelta) | ||
| 96 | - | progress := clampFloat(math.Abs(r.cumulativeDX)/threshold, 0, 1) | 96 | + | absDX := math.Abs(r.cumulativeDX) |
| 97 | - | r.thresholdReached = progress >= 1 | 97 | + | progress := clampFloat(absDX/threshold, 0, 1) |
| 98 | + | r.thresholdReached = absDX > threshold | |||
| 98 | if !r.indicatorShown && progress < touchpadNavigationIndicatorActivationFraction { | 99 | if !r.indicatorShown && progress < touchpadNavigationIndicatorActivationFraction { | ||
| 99 | return touchpadNavigationResult{} | 100 | return touchpadNavigationResult{} | ||
| 100 | } | 101 | } | ||
| @@ -147,23 +148,18 @@ func (r *touchpadNavigationRecognizer) finishIndicator(input touchpadNavigationI | |||||
| 147 | } | 148 | } | ||
| 148 | 149 | ||||
| 149 | func (r *touchpadNavigationRecognizer) indicatorAction(input touchpadNavigationInput) (port.TouchpadNavigationAction, bool) { | 150 | func (r *touchpadNavigationRecognizer) indicatorAction(input touchpadNavigationInput) (port.TouchpadNavigationAction, bool) { | ||
| 150 | - | if r.cumulativeDX > 0 && input.CanGoBack && r.startedAtNavigationEdge(port.TouchpadNavigationBack) { | 151 | + | return r.resolvedDirection(input) |
| 151 | - | return port.TouchpadNavigationBack, true | |||
| 152 | - | } | |||
| 153 | - | if r.cumulativeDX < 0 && input.CanGoForward && r.startedAtNavigationEdge(port.TouchpadNavigationForward) { | |||
| 154 | - | return port.TouchpadNavigationForward, true | |||
| 155 | - | } | |||
| 156 | - | return port.TouchpadNavigationBack, false | |||
| 157 | } | 152 | } | ||
| 158 | 153 | ||||
| 159 | func (r *touchpadNavigationRecognizer) cancelAsTooVertical() touchpadNavigationResult { | 154 | func (r *touchpadNavigationRecognizer) cancelAsTooVertical() touchpadNavigationResult { | ||
| 160 | - | action, ok := r.indicatorAction(touchpadNavigationInput{}) | 155 | + | var action port.TouchpadNavigationAction |
| 161 | - | if !ok && r.indicatorShown { | 156 | + | hasAction := r.indicatorShown |
| 162 | - | action, ok = r.lastIndicatorAction, true | 157 | + | if hasAction { |
| 158 | + | action = r.lastIndicatorAction | |||
| 163 | } | 159 | } | ||
| 164 | r.reset() | 160 | r.reset() | ||
| 165 | r.verticalCanceled = true | 161 | r.verticalCanceled = true | ||
| 166 | - | if !ok { | 162 | + | if !hasAction { |
| 167 | return touchpadNavigationResult{} | 163 | return touchpadNavigationResult{} | ||
| 168 | } | 164 | } | ||
| 169 | return touchpadNavigationResult{ | 165 | return touchpadNavigationResult{ | ||
| @@ -176,13 +172,24 @@ func (r *touchpadNavigationRecognizer) cancelAsTooVertical() touchpadNavigationR | |||||
| 176 | } | 172 | } | ||
| 177 | 173 | ||||
| 178 | func (r *touchpadNavigationRecognizer) navigationAction(input touchpadNavigationInput) (cef2gtk.NavigationSwipeAction, bool) { | 174 | func (r *touchpadNavigationRecognizer) navigationAction(input touchpadNavigationInput) (cef2gtk.NavigationSwipeAction, bool) { | ||
| 175 | + | action, ok := r.resolvedDirection(input) | |||
| 176 | + | if !ok { | |||
| 177 | + | return cef2gtk.NavigationSwipeBack, false | |||
| 178 | + | } | |||
| 179 | + | if action == port.TouchpadNavigationForward { | |||
| 180 | + | return cef2gtk.NavigationSwipeForward, true | |||
| 181 | + | } | |||
| 182 | + | return cef2gtk.NavigationSwipeBack, true | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | func (r *touchpadNavigationRecognizer) resolvedDirection(input touchpadNavigationInput) (port.TouchpadNavigationAction, bool) { | |||
| 179 | if r.cumulativeDX > 0 && input.CanGoBack && r.startedAtNavigationEdge(port.TouchpadNavigationBack) { | 186 | if r.cumulativeDX > 0 && input.CanGoBack && r.startedAtNavigationEdge(port.TouchpadNavigationBack) { | ||
| 180 | - | return cef2gtk.NavigationSwipeBack, true | 187 | + | return port.TouchpadNavigationBack, true |
| 181 | } | 188 | } | ||
| 182 | if r.cumulativeDX < 0 && input.CanGoForward && r.startedAtNavigationEdge(port.TouchpadNavigationForward) { | 189 | if r.cumulativeDX < 0 && input.CanGoForward && r.startedAtNavigationEdge(port.TouchpadNavigationForward) { | ||
| 183 | - | return cef2gtk.NavigationSwipeForward, true | 190 | + | return port.TouchpadNavigationForward, true |
| 184 | } | 191 | } | ||
| 185 | - | return cef2gtk.NavigationSwipeBack, false | 192 | + | return port.TouchpadNavigationBack, false |
| 186 | } | 193 | } | ||
| 187 | 194 | ||||
| 188 | func (r *touchpadNavigationRecognizer) setGestureStart(input touchpadNavigationInput) { | 195 | func (r *touchpadNavigationRecognizer) setGestureStart(input touchpadNavigationInput) { | ||
| @@ -193,7 +200,7 @@ func (r *touchpadNavigationRecognizer) setGestureStart(input touchpadNavigationI | |||||
| 193 | 200 | ||||
| 194 | func (r *touchpadNavigationRecognizer) startedAtNavigationEdge(action port.TouchpadNavigationAction) bool { | 201 | func (r *touchpadNavigationRecognizer) startedAtNavigationEdge(action port.TouchpadNavigationAction) bool { | ||
| 195 | if r.gestureViewWidth <= 0 { | 202 | if r.gestureViewWidth <= 0 { | ||
| 196 | - | return true | 203 | + | return false |
| 197 | } | 204 | } | ||
| 198 | edgeDistance := math.Min(r.gestureViewWidth*touchpadNavigationEdgeFraction, touchpadNavigationMaxEdgeDistance) | 205 | edgeDistance := math.Min(r.gestureViewWidth*touchpadNavigationEdgeFraction, touchpadNavigationMaxEdgeDistance) | ||
| 199 | if action == port.TouchpadNavigationForward { | 206 | if action == port.TouchpadNavigationForward { | ||
M internal/infrastructure/cef/touchpad_navigation_test.go +280 -2
| @@ -22,6 +22,7 @@ func TestTouchpadNavigationRecognizer_ReportsProgressAndNavigatesOnReleaseAfterT | |||
| 22 | 22 | Config: cfg, | |
| 23 | 23 | CanGoBack: true, | |
| 24 | 24 | CanGoForward: true, | |
| 25 | + | ViewWidth: 1000, | |
| 25 | 26 | }) | |
| 26 | 27 | require.False(t, begin.HasIndicator) | |
| 27 | 28 | require.False(t, begin.HasAction) | |
| @@ -73,6 +74,83 @@ func TestTouchpadNavigationRecognizer_ReportsProgressAndNavigatesOnReleaseAfterT | |||
| 73 | 74 | require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action) | |
| 74 | 75 | } | |
| 75 | 76 | ||
| 77 | + | func TestTouchpadNavigationRecognizer_ReportsForwardProgressAndNavigatesOnReleaseAfterThreshold(t *testing.T) { | |
| 78 | + | recognizer := newTouchpadNavigationRecognizer() | |
| 79 | + | cfg := RuntimeInputConfig{ | |
| 80 | + | TouchpadNavigationEnabled: true, | |
| 81 | + | TouchpadNavigationMinDelta: 200, | |
| 82 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | begin := recognizer.Handle(touchpadNavigationInput{ | |
| 86 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseBegin, 976, 0, 0), | |
| 87 | + | Config: cfg, | |
| 88 | + | CanGoBack: true, | |
| 89 | + | CanGoForward: true, | |
| 90 | + | ViewWidth: 1000, | |
| 91 | + | }) | |
| 92 | + | require.False(t, begin.HasIndicator) | |
| 93 | + | require.False(t, begin.HasAction) | |
| 94 | + | ||
| 95 | + | armed := recognizer.Handle(touchpadNavigationInput{ | |
| 96 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 976, 240, 0), | |
| 97 | + | Config: cfg, | |
| 98 | + | CanGoBack: true, | |
| 99 | + | CanGoForward: true, | |
| 100 | + | }) | |
| 101 | + | require.True(t, armed.HasIndicator) | |
| 102 | + | require.Equal(t, port.TouchpadNavigationForward, armed.Indicator.Action) | |
| 103 | + | require.True(t, armed.Indicator.Active) | |
| 104 | + | require.InDelta(t, 1.0, armed.Indicator.Progress, 0.001) | |
| 105 | + | require.True(t, armed.Indicator.ThresholdReached) | |
| 106 | + | require.False(t, armed.HasAction) | |
| 107 | + | ||
| 108 | + | end := recognizer.Handle(touchpadNavigationInput{ | |
| 109 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 976, 0, 0), | |
| 110 | + | Config: cfg, | |
| 111 | + | CanGoBack: true, | |
| 112 | + | CanGoForward: true, | |
| 113 | + | }) | |
| 114 | + | require.True(t, end.HasIndicator) | |
| 115 | + | require.False(t, end.Indicator.Active) | |
| 116 | + | require.True(t, end.HasAction) | |
| 117 | + | require.Equal(t, cef2gtk.NavigationSwipeForward, end.Action) | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | func TestTouchpadNavigationRecognizer_DoesNotNavigateAtExactThreshold(t *testing.T) { | |
| 121 | + | recognizer := newTouchpadNavigationRecognizer() | |
| 122 | + | cfg := RuntimeInputConfig{ | |
| 123 | + | TouchpadNavigationEnabled: true, | |
| 124 | + | TouchpadNavigationMinDelta: 200, | |
| 125 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |
| 129 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |
| 130 | + | Config: cfg, | |
| 131 | + | CanGoBack: true, | |
| 132 | + | ViewWidth: 1000, | |
| 133 | + | }) | |
| 134 | + | exact := recognizer.Handle(touchpadNavigationInput{ | |
| 135 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -200, 0), | |
| 136 | + | Config: cfg, | |
| 137 | + | CanGoBack: true, | |
| 138 | + | }) | |
| 139 | + | require.True(t, exact.HasIndicator) | |
| 140 | + | require.InDelta(t, 1.0, exact.Indicator.Progress, 0.001) | |
| 141 | + | require.False(t, exact.Indicator.ThresholdReached) | |
| 142 | + | ||
| 143 | + | end := recognizer.Handle(touchpadNavigationInput{ | |
| 144 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0), | |
| 145 | + | Config: cfg, | |
| 146 | + | CanGoBack: true, | |
| 147 | + | }) | |
| 148 | + | require.True(t, end.HasIndicator) | |
| 149 | + | require.False(t, end.Indicator.Active) | |
| 150 | + | require.False(t, end.Indicator.ThresholdReached) | |
| 151 | + | require.False(t, end.HasAction) | |
| 152 | + | } | |
| 153 | + | ||
| 76 | 154 | func TestTouchpadNavigationRecognizer_DoesNotShowIndicatorBelowActivationDistance(t *testing.T) { | |
| 77 | 155 | recognizer := newTouchpadNavigationRecognizer() | |
| 78 | 156 | cfg := RuntimeInputConfig{ | |
| @@ -85,6 +163,7 @@ func TestTouchpadNavigationRecognizer_DoesNotShowIndicatorBelowActivationDistanc | |||
| 85 | 163 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |
| 86 | 164 | Config: cfg, | |
| 87 | 165 | CanGoBack: true, | |
| 166 | + | ViewWidth: 1000, | |
| 88 | 167 | }) | |
| 89 | 168 | ordinaryScroll := recognizer.Handle(touchpadNavigationInput{ | |
| 90 | 169 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -80, 0), | |
| @@ -121,7 +200,6 @@ func TestTouchpadNavigationRecognizer_AllowsIntentionalEdgeSwipeWithViewWidth(t | |||
| 121 | 200 | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 24, -240, 0), | |
| 122 | 201 | Config: cfg, | |
| 123 | 202 | CanGoBack: true, | |
| 124 | - | ViewWidth: 1000, | |
| 125 | 203 | }) | |
| 126 | 204 | require.True(t, armed.HasIndicator) | |
| 127 | 205 | require.True(t, armed.Indicator.ThresholdReached) | |
| @@ -130,7 +208,6 @@ func TestTouchpadNavigationRecognizer_AllowsIntentionalEdgeSwipeWithViewWidth(t | |||
| 130 | 208 | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 24, 0, 0), | |
| 131 | 209 | Config: cfg, | |
| 132 | 210 | CanGoBack: true, | |
| 133 | - | ViewWidth: 1000, | |
| 134 | 211 | }) | |
| 135 | 212 | require.True(t, end.HasAction) | |
| 136 | 213 | require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action) | |
| @@ -169,6 +246,153 @@ func TestTouchpadNavigationRecognizer_DoesNotTreatInContentHorizontalScrollAsNav | |||
| 169 | 246 | require.False(t, end.HasAction) | |
| 170 | 247 | } | |
| 171 | 248 | ||
| 249 | + | func TestTouchpadNavigationRecognizer_IgnoresNonPreciseScrollEvents(t *testing.T) { | |
| 250 | + | tests := []struct { | |
| 251 | + | name string | |
| 252 | + | event cef2gtk.ScrollEvent | |
| 253 | + | }{ | |
| 254 | + | { | |
| 255 | + | name: "unknown unit", | |
| 256 | + | event: cef2gtk.ScrollEvent{ | |
| 257 | + | Phase: cef2gtk.ScrollPhaseUpdate, | |
| 258 | + | DX: -240, | |
| 259 | + | Unit: gdk.ScrollUnitSurfaceValue, | |
| 260 | + | UnitKnown: false, | |
| 261 | + | }, | |
| 262 | + | }, | |
| 263 | + | { | |
| 264 | + | name: "wheel unit", | |
| 265 | + | event: cef2gtk.ScrollEvent{ | |
| 266 | + | Phase: cef2gtk.ScrollPhaseUpdate, | |
| 267 | + | DX: -240, | |
| 268 | + | Unit: gdk.ScrollUnitWheelValue, | |
| 269 | + | UnitKnown: true, | |
| 270 | + | }, | |
| 271 | + | }, | |
| 272 | + | } | |
| 273 | + | ||
| 274 | + | for _, tt := range tests { | |
| 275 | + | t.Run(tt.name, func(t *testing.T) { | |
| 276 | + | recognizer := newTouchpadNavigationRecognizer() | |
| 277 | + | cfg := RuntimeInputConfig{ | |
| 278 | + | TouchpadNavigationEnabled: true, | |
| 279 | + | TouchpadNavigationMinDelta: 200, | |
| 280 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |
| 281 | + | } | |
| 282 | + | ||
| 283 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |
| 284 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |
| 285 | + | Config: cfg, | |
| 286 | + | CanGoBack: true, | |
| 287 | + | ViewWidth: 1000, | |
| 288 | + | }) | |
| 289 | + | result := recognizer.Handle(touchpadNavigationInput{ | |
| 290 | + | Event: tt.event, | |
| 291 | + | Config: cfg, | |
| 292 | + | CanGoBack: true, | |
| 293 | + | }) | |
| 294 | + | require.False(t, result.HasIndicator) | |
| 295 | + | require.False(t, result.HasAction) | |
| 296 | + | }) | |
| 297 | + | } | |
| 298 | + | } | |
| 299 | + | ||
| 300 | + | func TestTouchpadNavigationRecognizer_DisabledConfigSuppressesGestureRecognition(t *testing.T) { | |
| 301 | + | recognizer := newTouchpadNavigationRecognizer() | |
| 302 | + | cfg := RuntimeInputConfig{ | |
| 303 | + | TouchpadNavigationEnabled: false, | |
| 304 | + | TouchpadNavigationMinDelta: 200, | |
| 305 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |
| 306 | + | } | |
| 307 | + | ||
| 308 | + | begin := recognizer.Handle(touchpadNavigationInput{ | |
| 309 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |
| 310 | + | Config: cfg, | |
| 311 | + | CanGoBack: true, | |
| 312 | + | ViewWidth: 1000, | |
| 313 | + | }) | |
| 314 | + | require.False(t, begin.HasIndicator) | |
| 315 | + | require.False(t, begin.HasAction) | |
| 316 | + | ||
| 317 | + | update := recognizer.Handle(touchpadNavigationInput{ | |
| 318 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -240, 0), | |
| 319 | + | Config: cfg, | |
| 320 | + | CanGoBack: true, | |
| 321 | + | }) | |
| 322 | + | require.False(t, update.HasIndicator) | |
| 323 | + | require.False(t, update.HasAction) | |
| 324 | + | ||
| 325 | + | end := recognizer.Handle(touchpadNavigationInput{ | |
| 326 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0), | |
| 327 | + | Config: cfg, | |
| 328 | + | CanGoBack: true, | |
| 329 | + | }) | |
| 330 | + | require.False(t, end.HasIndicator) | |
| 331 | + | require.False(t, end.HasAction) | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | func TestTouchpadNavigationRecognizer_BackCapabilitySuppressesBackGesture(t *testing.T) { | |
| 335 | + | recognizer := newTouchpadNavigationRecognizer() | |
| 336 | + | cfg := RuntimeInputConfig{ | |
| 337 | + | TouchpadNavigationEnabled: true, | |
| 338 | + | TouchpadNavigationMinDelta: 200, | |
| 339 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |
| 340 | + | } | |
| 341 | + | ||
| 342 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |
| 343 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |
| 344 | + | Config: cfg, | |
| 345 | + | CanGoBack: false, | |
| 346 | + | ViewWidth: 1000, | |
| 347 | + | }) | |
| 348 | + | update := recognizer.Handle(touchpadNavigationInput{ | |
| 349 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -240, 0), | |
| 350 | + | Config: cfg, | |
| 351 | + | CanGoBack: false, | |
| 352 | + | }) | |
| 353 | + | require.False(t, update.HasIndicator) | |
| 354 | + | require.False(t, update.HasAction) | |
| 355 | + | ||
| 356 | + | end := recognizer.Handle(touchpadNavigationInput{ | |
| 357 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0), | |
| 358 | + | Config: cfg, | |
| 359 | + | CanGoBack: false, | |
| 360 | + | }) | |
| 361 | + | require.False(t, end.HasIndicator) | |
| 362 | + | require.False(t, end.HasAction) | |
| 363 | + | } | |
| 364 | + | ||
| 365 | + | func TestTouchpadNavigationRecognizer_ForwardCapabilitySuppressesForwardGesture(t *testing.T) { | |
| 366 | + | recognizer := newTouchpadNavigationRecognizer() | |
| 367 | + | cfg := RuntimeInputConfig{ | |
| 368 | + | TouchpadNavigationEnabled: true, | |
| 369 | + | TouchpadNavigationMinDelta: 200, | |
| 370 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |
| 371 | + | } | |
| 372 | + | ||
| 373 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |
| 374 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseBegin, 976, 0, 0), | |
| 375 | + | Config: cfg, | |
| 376 | + | CanGoForward: false, | |
| 377 | + | ViewWidth: 1000, | |
| 378 | + | }) | |
| 379 | + | update := recognizer.Handle(touchpadNavigationInput{ | |
| 380 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 976, 240, 0), | |
| 381 | + | Config: cfg, | |
| 382 | + | CanGoForward: false, | |
| 383 | + | }) | |
| 384 | + | require.False(t, update.HasIndicator) | |
| 385 | + | require.False(t, update.HasAction) | |
| 386 | + | ||
| 387 | + | end := recognizer.Handle(touchpadNavigationInput{ | |
| 388 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 976, 0, 0), | |
| 389 | + | Config: cfg, | |
| 390 | + | CanGoForward: false, | |
| 391 | + | }) | |
| 392 | + | require.False(t, end.HasIndicator) | |
| 393 | + | require.False(t, end.HasAction) | |
| 394 | + | } | |
| 395 | + | ||
| 172 | 396 | func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenReleasedAfterActivationBelowThreshold(t *testing.T) { | |
| 173 | 397 | recognizer := newTouchpadNavigationRecognizer() | |
| 174 | 398 | cfg := RuntimeInputConfig{ | |
| @@ -181,6 +405,7 @@ func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenRelease | |||
| 181 | 405 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |
| 182 | 406 | Config: cfg, | |
| 183 | 407 | CanGoBack: true, | |
| 408 | + | ViewWidth: 1000, | |
| 184 | 409 | }) | |
| 185 | 410 | progress := recognizer.Handle(touchpadNavigationInput{ | |
| 186 | 411 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -120, 0), | |
| @@ -201,6 +426,56 @@ func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenRelease | |||
| 201 | 426 | require.False(t, end.Indicator.Active) | |
| 202 | 427 | } | |
| 203 | 428 | ||
| 429 | + | func TestTouchpadNavigationRecognizer_ResetsBetweenConsecutiveGestures(t *testing.T) { | |
| 430 | + | recognizer := newTouchpadNavigationRecognizer() | |
| 431 | + | cfg := RuntimeInputConfig{ | |
| 432 | + | TouchpadNavigationEnabled: true, | |
| 433 | + | TouchpadNavigationMinDelta: 200, | |
| 434 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |
| 435 | + | } | |
| 436 | + | ||
| 437 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |
| 438 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |
| 439 | + | Config: cfg, | |
| 440 | + | CanGoBack: true, | |
| 441 | + | ViewWidth: 1000, | |
| 442 | + | }) | |
| 443 | + | firstUpdate := recognizer.Handle(touchpadNavigationInput{ | |
| 444 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -240, 0), | |
| 445 | + | Config: cfg, | |
| 446 | + | CanGoBack: true, | |
| 447 | + | }) | |
| 448 | + | require.True(t, firstUpdate.HasIndicator) | |
| 449 | + | firstEnd := recognizer.Handle(touchpadNavigationInput{ | |
| 450 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0), | |
| 451 | + | Config: cfg, | |
| 452 | + | CanGoBack: true, | |
| 453 | + | }) | |
| 454 | + | require.True(t, firstEnd.HasAction) | |
| 455 | + | require.Equal(t, cef2gtk.NavigationSwipeBack, firstEnd.Action) | |
| 456 | + | ||
| 457 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |
| 458 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseBegin, 976, 0, 0), | |
| 459 | + | Config: cfg, | |
| 460 | + | CanGoForward: true, | |
| 461 | + | ViewWidth: 1000, | |
| 462 | + | }) | |
| 463 | + | secondUpdate := recognizer.Handle(touchpadNavigationInput{ | |
| 464 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 976, 240, 0), | |
| 465 | + | Config: cfg, | |
| 466 | + | CanGoForward: true, | |
| 467 | + | }) | |
| 468 | + | require.True(t, secondUpdate.HasIndicator) | |
| 469 | + | require.Equal(t, port.TouchpadNavigationForward, secondUpdate.Indicator.Action) | |
| 470 | + | secondEnd := recognizer.Handle(touchpadNavigationInput{ | |
| 471 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 976, 0, 0), | |
| 472 | + | Config: cfg, | |
| 473 | + | CanGoForward: true, | |
| 474 | + | }) | |
| 475 | + | require.True(t, secondEnd.HasAction) | |
| 476 | + | require.Equal(t, cef2gtk.NavigationSwipeForward, secondEnd.Action) | |
| 477 | + | } | |
| 478 | + | ||
| 204 | 479 | func TestTouchpadNavigationRecognizer_CancelsVerticalGestures(t *testing.T) { | |
| 205 | 480 | recognizer := newTouchpadNavigationRecognizer() | |
| 206 | 481 | cfg := RuntimeInputConfig{ | |
| @@ -213,6 +488,7 @@ func TestTouchpadNavigationRecognizer_CancelsVerticalGestures(t *testing.T) { | |||
| 213 | 488 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |
| 214 | 489 | Config: cfg, | |
| 215 | 490 | CanGoBack: true, | |
| 491 | + | ViewWidth: 1000, | |
| 216 | 492 | }) | |
| 217 | 493 | vertical := recognizer.Handle(touchpadNavigationInput{ | |
| 218 | 494 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -60, 80), | |
| @@ -242,6 +518,8 @@ func touchpadNavigationScrollEvent(phase cef2gtk.ScrollPhase, dx, dy float64) ce | |||
| 242 | 518 | return touchpadNavigationScrollEventAt(phase, 0, dx, dy) | |
| 243 | 519 | } | |
| 244 | 520 | ||
| 521 | + | // ViewWidth is provided separately on touchpadNavigationInput; the recognizer | |
| 522 | + | // caches it from the initial Begin event for subsequent Update and End phases. | |
| 245 | 523 | func touchpadNavigationScrollEventAt(phase cef2gtk.ScrollPhase, x, dx, dy float64) cef2gtk.ScrollEvent { | |
| 246 | 524 | return cef2gtk.ScrollEvent{ | |
| 247 | 525 | Phase: phase, | |
| @@ -22,6 +22,7 @@ func TestTouchpadNavigationRecognizer_ReportsProgressAndNavigatesOnReleaseAfterT | |||||
| 22 | Config: cfg, | 22 | Config: cfg, | ||
| 23 | CanGoBack: true, | 23 | CanGoBack: true, | ||
| 24 | CanGoForward: true, | 24 | CanGoForward: true, | ||
| 25 | + | ViewWidth: 1000, | |||
| 25 | }) | 26 | }) | ||
| 26 | require.False(t, begin.HasIndicator) | 27 | require.False(t, begin.HasIndicator) | ||
| 27 | require.False(t, begin.HasAction) | 28 | require.False(t, begin.HasAction) | ||
| @@ -73,6 +74,83 @@ func TestTouchpadNavigationRecognizer_ReportsProgressAndNavigatesOnReleaseAfterT | |||||
| 73 | require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action) | 74 | require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action) | ||
| 74 | } | 75 | } | ||
| 75 | 76 | ||||
| 77 | + | func TestTouchpadNavigationRecognizer_ReportsForwardProgressAndNavigatesOnReleaseAfterThreshold(t *testing.T) { | |||
| 78 | + | recognizer := newTouchpadNavigationRecognizer() | |||
| 79 | + | cfg := RuntimeInputConfig{ | |||
| 80 | + | TouchpadNavigationEnabled: true, | |||
| 81 | + | TouchpadNavigationMinDelta: 200, | |||
| 82 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | begin := recognizer.Handle(touchpadNavigationInput{ | |||
| 86 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseBegin, 976, 0, 0), | |||
| 87 | + | Config: cfg, | |||
| 88 | + | CanGoBack: true, | |||
| 89 | + | CanGoForward: true, | |||
| 90 | + | ViewWidth: 1000, | |||
| 91 | + | }) | |||
| 92 | + | require.False(t, begin.HasIndicator) | |||
| 93 | + | require.False(t, begin.HasAction) | |||
| 94 | + | ||||
| 95 | + | armed := recognizer.Handle(touchpadNavigationInput{ | |||
| 96 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 976, 240, 0), | |||
| 97 | + | Config: cfg, | |||
| 98 | + | CanGoBack: true, | |||
| 99 | + | CanGoForward: true, | |||
| 100 | + | }) | |||
| 101 | + | require.True(t, armed.HasIndicator) | |||
| 102 | + | require.Equal(t, port.TouchpadNavigationForward, armed.Indicator.Action) | |||
| 103 | + | require.True(t, armed.Indicator.Active) | |||
| 104 | + | require.InDelta(t, 1.0, armed.Indicator.Progress, 0.001) | |||
| 105 | + | require.True(t, armed.Indicator.ThresholdReached) | |||
| 106 | + | require.False(t, armed.HasAction) | |||
| 107 | + | ||||
| 108 | + | end := recognizer.Handle(touchpadNavigationInput{ | |||
| 109 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 976, 0, 0), | |||
| 110 | + | Config: cfg, | |||
| 111 | + | CanGoBack: true, | |||
| 112 | + | CanGoForward: true, | |||
| 113 | + | }) | |||
| 114 | + | require.True(t, end.HasIndicator) | |||
| 115 | + | require.False(t, end.Indicator.Active) | |||
| 116 | + | require.True(t, end.HasAction) | |||
| 117 | + | require.Equal(t, cef2gtk.NavigationSwipeForward, end.Action) | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | func TestTouchpadNavigationRecognizer_DoesNotNavigateAtExactThreshold(t *testing.T) { | |||
| 121 | + | recognizer := newTouchpadNavigationRecognizer() | |||
| 122 | + | cfg := RuntimeInputConfig{ | |||
| 123 | + | TouchpadNavigationEnabled: true, | |||
| 124 | + | TouchpadNavigationMinDelta: 200, | |||
| 125 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |||
| 126 | + | } | |||
| 127 | + | ||||
| 128 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |||
| 129 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |||
| 130 | + | Config: cfg, | |||
| 131 | + | CanGoBack: true, | |||
| 132 | + | ViewWidth: 1000, | |||
| 133 | + | }) | |||
| 134 | + | exact := recognizer.Handle(touchpadNavigationInput{ | |||
| 135 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -200, 0), | |||
| 136 | + | Config: cfg, | |||
| 137 | + | CanGoBack: true, | |||
| 138 | + | }) | |||
| 139 | + | require.True(t, exact.HasIndicator) | |||
| 140 | + | require.InDelta(t, 1.0, exact.Indicator.Progress, 0.001) | |||
| 141 | + | require.False(t, exact.Indicator.ThresholdReached) | |||
| 142 | + | ||||
| 143 | + | end := recognizer.Handle(touchpadNavigationInput{ | |||
| 144 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0), | |||
| 145 | + | Config: cfg, | |||
| 146 | + | CanGoBack: true, | |||
| 147 | + | }) | |||
| 148 | + | require.True(t, end.HasIndicator) | |||
| 149 | + | require.False(t, end.Indicator.Active) | |||
| 150 | + | require.False(t, end.Indicator.ThresholdReached) | |||
| 151 | + | require.False(t, end.HasAction) | |||
| 152 | + | } | |||
| 153 | + | ||||
| 76 | func TestTouchpadNavigationRecognizer_DoesNotShowIndicatorBelowActivationDistance(t *testing.T) { | 154 | func TestTouchpadNavigationRecognizer_DoesNotShowIndicatorBelowActivationDistance(t *testing.T) { | ||
| 77 | recognizer := newTouchpadNavigationRecognizer() | 155 | recognizer := newTouchpadNavigationRecognizer() | ||
| 78 | cfg := RuntimeInputConfig{ | 156 | cfg := RuntimeInputConfig{ | ||
| @@ -85,6 +163,7 @@ func TestTouchpadNavigationRecognizer_DoesNotShowIndicatorBelowActivationDistanc | |||||
| 85 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | 163 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | ||
| 86 | Config: cfg, | 164 | Config: cfg, | ||
| 87 | CanGoBack: true, | 165 | CanGoBack: true, | ||
| 166 | + | ViewWidth: 1000, | |||
| 88 | }) | 167 | }) | ||
| 89 | ordinaryScroll := recognizer.Handle(touchpadNavigationInput{ | 168 | ordinaryScroll := recognizer.Handle(touchpadNavigationInput{ | ||
| 90 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -80, 0), | 169 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -80, 0), | ||
| @@ -121,7 +200,6 @@ func TestTouchpadNavigationRecognizer_AllowsIntentionalEdgeSwipeWithViewWidth(t | |||||
| 121 | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 24, -240, 0), | 200 | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 24, -240, 0), | ||
| 122 | Config: cfg, | 201 | Config: cfg, | ||
| 123 | CanGoBack: true, | 202 | CanGoBack: true, | ||
| 124 | - | ViewWidth: 1000, | |||
| 125 | }) | 203 | }) | ||
| 126 | require.True(t, armed.HasIndicator) | 204 | require.True(t, armed.HasIndicator) | ||
| 127 | require.True(t, armed.Indicator.ThresholdReached) | 205 | require.True(t, armed.Indicator.ThresholdReached) | ||
| @@ -130,7 +208,6 @@ func TestTouchpadNavigationRecognizer_AllowsIntentionalEdgeSwipeWithViewWidth(t | |||||
| 130 | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 24, 0, 0), | 208 | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 24, 0, 0), | ||
| 131 | Config: cfg, | 209 | Config: cfg, | ||
| 132 | CanGoBack: true, | 210 | CanGoBack: true, | ||
| 133 | - | ViewWidth: 1000, | |||
| 134 | }) | 211 | }) | ||
| 135 | require.True(t, end.HasAction) | 212 | require.True(t, end.HasAction) | ||
| 136 | require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action) | 213 | require.Equal(t, cef2gtk.NavigationSwipeBack, end.Action) | ||
| @@ -169,6 +246,153 @@ func TestTouchpadNavigationRecognizer_DoesNotTreatInContentHorizontalScrollAsNav | |||||
| 169 | require.False(t, end.HasAction) | 246 | require.False(t, end.HasAction) | ||
| 170 | } | 247 | } | ||
| 171 | 248 | ||||
| 249 | + | func TestTouchpadNavigationRecognizer_IgnoresNonPreciseScrollEvents(t *testing.T) { | |||
| 250 | + | tests := []struct { | |||
| 251 | + | name string | |||
| 252 | + | event cef2gtk.ScrollEvent | |||
| 253 | + | }{ | |||
| 254 | + | { | |||
| 255 | + | name: "unknown unit", | |||
| 256 | + | event: cef2gtk.ScrollEvent{ | |||
| 257 | + | Phase: cef2gtk.ScrollPhaseUpdate, | |||
| 258 | + | DX: -240, | |||
| 259 | + | Unit: gdk.ScrollUnitSurfaceValue, | |||
| 260 | + | UnitKnown: false, | |||
| 261 | + | }, | |||
| 262 | + | }, | |||
| 263 | + | { | |||
| 264 | + | name: "wheel unit", | |||
| 265 | + | event: cef2gtk.ScrollEvent{ | |||
| 266 | + | Phase: cef2gtk.ScrollPhaseUpdate, | |||
| 267 | + | DX: -240, | |||
| 268 | + | Unit: gdk.ScrollUnitWheelValue, | |||
| 269 | + | UnitKnown: true, | |||
| 270 | + | }, | |||
| 271 | + | }, | |||
| 272 | + | } | |||
| 273 | + | ||||
| 274 | + | for _, tt := range tests { | |||
| 275 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 276 | + | recognizer := newTouchpadNavigationRecognizer() | |||
| 277 | + | cfg := RuntimeInputConfig{ | |||
| 278 | + | TouchpadNavigationEnabled: true, | |||
| 279 | + | TouchpadNavigationMinDelta: 200, | |||
| 280 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |||
| 281 | + | } | |||
| 282 | + | ||||
| 283 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |||
| 284 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |||
| 285 | + | Config: cfg, | |||
| 286 | + | CanGoBack: true, | |||
| 287 | + | ViewWidth: 1000, | |||
| 288 | + | }) | |||
| 289 | + | result := recognizer.Handle(touchpadNavigationInput{ | |||
| 290 | + | Event: tt.event, | |||
| 291 | + | Config: cfg, | |||
| 292 | + | CanGoBack: true, | |||
| 293 | + | }) | |||
| 294 | + | require.False(t, result.HasIndicator) | |||
| 295 | + | require.False(t, result.HasAction) | |||
| 296 | + | }) | |||
| 297 | + | } | |||
| 298 | + | } | |||
| 299 | + | ||||
| 300 | + | func TestTouchpadNavigationRecognizer_DisabledConfigSuppressesGestureRecognition(t *testing.T) { | |||
| 301 | + | recognizer := newTouchpadNavigationRecognizer() | |||
| 302 | + | cfg := RuntimeInputConfig{ | |||
| 303 | + | TouchpadNavigationEnabled: false, | |||
| 304 | + | TouchpadNavigationMinDelta: 200, | |||
| 305 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |||
| 306 | + | } | |||
| 307 | + | ||||
| 308 | + | begin := recognizer.Handle(touchpadNavigationInput{ | |||
| 309 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |||
| 310 | + | Config: cfg, | |||
| 311 | + | CanGoBack: true, | |||
| 312 | + | ViewWidth: 1000, | |||
| 313 | + | }) | |||
| 314 | + | require.False(t, begin.HasIndicator) | |||
| 315 | + | require.False(t, begin.HasAction) | |||
| 316 | + | ||||
| 317 | + | update := recognizer.Handle(touchpadNavigationInput{ | |||
| 318 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -240, 0), | |||
| 319 | + | Config: cfg, | |||
| 320 | + | CanGoBack: true, | |||
| 321 | + | }) | |||
| 322 | + | require.False(t, update.HasIndicator) | |||
| 323 | + | require.False(t, update.HasAction) | |||
| 324 | + | ||||
| 325 | + | end := recognizer.Handle(touchpadNavigationInput{ | |||
| 326 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0), | |||
| 327 | + | Config: cfg, | |||
| 328 | + | CanGoBack: true, | |||
| 329 | + | }) | |||
| 330 | + | require.False(t, end.HasIndicator) | |||
| 331 | + | require.False(t, end.HasAction) | |||
| 332 | + | } | |||
| 333 | + | ||||
| 334 | + | func TestTouchpadNavigationRecognizer_BackCapabilitySuppressesBackGesture(t *testing.T) { | |||
| 335 | + | recognizer := newTouchpadNavigationRecognizer() | |||
| 336 | + | cfg := RuntimeInputConfig{ | |||
| 337 | + | TouchpadNavigationEnabled: true, | |||
| 338 | + | TouchpadNavigationMinDelta: 200, | |||
| 339 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |||
| 340 | + | } | |||
| 341 | + | ||||
| 342 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |||
| 343 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |||
| 344 | + | Config: cfg, | |||
| 345 | + | CanGoBack: false, | |||
| 346 | + | ViewWidth: 1000, | |||
| 347 | + | }) | |||
| 348 | + | update := recognizer.Handle(touchpadNavigationInput{ | |||
| 349 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -240, 0), | |||
| 350 | + | Config: cfg, | |||
| 351 | + | CanGoBack: false, | |||
| 352 | + | }) | |||
| 353 | + | require.False(t, update.HasIndicator) | |||
| 354 | + | require.False(t, update.HasAction) | |||
| 355 | + | ||||
| 356 | + | end := recognizer.Handle(touchpadNavigationInput{ | |||
| 357 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0), | |||
| 358 | + | Config: cfg, | |||
| 359 | + | CanGoBack: false, | |||
| 360 | + | }) | |||
| 361 | + | require.False(t, end.HasIndicator) | |||
| 362 | + | require.False(t, end.HasAction) | |||
| 363 | + | } | |||
| 364 | + | ||||
| 365 | + | func TestTouchpadNavigationRecognizer_ForwardCapabilitySuppressesForwardGesture(t *testing.T) { | |||
| 366 | + | recognizer := newTouchpadNavigationRecognizer() | |||
| 367 | + | cfg := RuntimeInputConfig{ | |||
| 368 | + | TouchpadNavigationEnabled: true, | |||
| 369 | + | TouchpadNavigationMinDelta: 200, | |||
| 370 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |||
| 371 | + | } | |||
| 372 | + | ||||
| 373 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |||
| 374 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseBegin, 976, 0, 0), | |||
| 375 | + | Config: cfg, | |||
| 376 | + | CanGoForward: false, | |||
| 377 | + | ViewWidth: 1000, | |||
| 378 | + | }) | |||
| 379 | + | update := recognizer.Handle(touchpadNavigationInput{ | |||
| 380 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 976, 240, 0), | |||
| 381 | + | Config: cfg, | |||
| 382 | + | CanGoForward: false, | |||
| 383 | + | }) | |||
| 384 | + | require.False(t, update.HasIndicator) | |||
| 385 | + | require.False(t, update.HasAction) | |||
| 386 | + | ||||
| 387 | + | end := recognizer.Handle(touchpadNavigationInput{ | |||
| 388 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 976, 0, 0), | |||
| 389 | + | Config: cfg, | |||
| 390 | + | CanGoForward: false, | |||
| 391 | + | }) | |||
| 392 | + | require.False(t, end.HasIndicator) | |||
| 393 | + | require.False(t, end.HasAction) | |||
| 394 | + | } | |||
| 395 | + | ||||
| 172 | func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenReleasedAfterActivationBelowThreshold(t *testing.T) { | 396 | func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenReleasedAfterActivationBelowThreshold(t *testing.T) { | ||
| 173 | recognizer := newTouchpadNavigationRecognizer() | 397 | recognizer := newTouchpadNavigationRecognizer() | ||
| 174 | cfg := RuntimeInputConfig{ | 398 | cfg := RuntimeInputConfig{ | ||
| @@ -181,6 +405,7 @@ func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenRelease | |||||
| 181 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | 405 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | ||
| 182 | Config: cfg, | 406 | Config: cfg, | ||
| 183 | CanGoBack: true, | 407 | CanGoBack: true, | ||
| 408 | + | ViewWidth: 1000, | |||
| 184 | }) | 409 | }) | ||
| 185 | progress := recognizer.Handle(touchpadNavigationInput{ | 410 | progress := recognizer.Handle(touchpadNavigationInput{ | ||
| 186 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -120, 0), | 411 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -120, 0), | ||
| @@ -201,6 +426,56 @@ func TestTouchpadNavigationRecognizer_HidesIndicatorWithoutNavigatingWhenRelease | |||||
| 201 | require.False(t, end.Indicator.Active) | 426 | require.False(t, end.Indicator.Active) | ||
| 202 | } | 427 | } | ||
| 203 | 428 | ||||
| 429 | + | func TestTouchpadNavigationRecognizer_ResetsBetweenConsecutiveGestures(t *testing.T) { | |||
| 430 | + | recognizer := newTouchpadNavigationRecognizer() | |||
| 431 | + | cfg := RuntimeInputConfig{ | |||
| 432 | + | TouchpadNavigationEnabled: true, | |||
| 433 | + | TouchpadNavigationMinDelta: 200, | |||
| 434 | + | TouchpadNavigationMaxVerticalRatio: 0.5, | |||
| 435 | + | } | |||
| 436 | + | ||||
| 437 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |||
| 438 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | |||
| 439 | + | Config: cfg, | |||
| 440 | + | CanGoBack: true, | |||
| 441 | + | ViewWidth: 1000, | |||
| 442 | + | }) | |||
| 443 | + | firstUpdate := recognizer.Handle(touchpadNavigationInput{ | |||
| 444 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -240, 0), | |||
| 445 | + | Config: cfg, | |||
| 446 | + | CanGoBack: true, | |||
| 447 | + | }) | |||
| 448 | + | require.True(t, firstUpdate.HasIndicator) | |||
| 449 | + | firstEnd := recognizer.Handle(touchpadNavigationInput{ | |||
| 450 | + | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseEnd, 0, 0), | |||
| 451 | + | Config: cfg, | |||
| 452 | + | CanGoBack: true, | |||
| 453 | + | }) | |||
| 454 | + | require.True(t, firstEnd.HasAction) | |||
| 455 | + | require.Equal(t, cef2gtk.NavigationSwipeBack, firstEnd.Action) | |||
| 456 | + | ||||
| 457 | + | _ = recognizer.Handle(touchpadNavigationInput{ | |||
| 458 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseBegin, 976, 0, 0), | |||
| 459 | + | Config: cfg, | |||
| 460 | + | CanGoForward: true, | |||
| 461 | + | ViewWidth: 1000, | |||
| 462 | + | }) | |||
| 463 | + | secondUpdate := recognizer.Handle(touchpadNavigationInput{ | |||
| 464 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseUpdate, 976, 240, 0), | |||
| 465 | + | Config: cfg, | |||
| 466 | + | CanGoForward: true, | |||
| 467 | + | }) | |||
| 468 | + | require.True(t, secondUpdate.HasIndicator) | |||
| 469 | + | require.Equal(t, port.TouchpadNavigationForward, secondUpdate.Indicator.Action) | |||
| 470 | + | secondEnd := recognizer.Handle(touchpadNavigationInput{ | |||
| 471 | + | Event: touchpadNavigationScrollEventAt(cef2gtk.ScrollPhaseEnd, 976, 0, 0), | |||
| 472 | + | Config: cfg, | |||
| 473 | + | CanGoForward: true, | |||
| 474 | + | }) | |||
| 475 | + | require.True(t, secondEnd.HasAction) | |||
| 476 | + | require.Equal(t, cef2gtk.NavigationSwipeForward, secondEnd.Action) | |||
| 477 | + | } | |||
| 478 | + | ||||
| 204 | func TestTouchpadNavigationRecognizer_CancelsVerticalGestures(t *testing.T) { | 479 | func TestTouchpadNavigationRecognizer_CancelsVerticalGestures(t *testing.T) { | ||
| 205 | recognizer := newTouchpadNavigationRecognizer() | 480 | recognizer := newTouchpadNavigationRecognizer() | ||
| 206 | cfg := RuntimeInputConfig{ | 481 | cfg := RuntimeInputConfig{ | ||
| @@ -213,6 +488,7 @@ func TestTouchpadNavigationRecognizer_CancelsVerticalGestures(t *testing.T) { | |||||
| 213 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | 488 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseBegin, 0, 0), | ||
| 214 | Config: cfg, | 489 | Config: cfg, | ||
| 215 | CanGoBack: true, | 490 | CanGoBack: true, | ||
| 491 | + | ViewWidth: 1000, | |||
| 216 | }) | 492 | }) | ||
| 217 | vertical := recognizer.Handle(touchpadNavigationInput{ | 493 | vertical := recognizer.Handle(touchpadNavigationInput{ | ||
| 218 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -60, 80), | 494 | Event: touchpadNavigationScrollEvent(cef2gtk.ScrollPhaseUpdate, -60, 80), | ||
| @@ -242,6 +518,8 @@ func touchpadNavigationScrollEvent(phase cef2gtk.ScrollPhase, dx, dy float64) ce | |||||
| 242 | return touchpadNavigationScrollEventAt(phase, 0, dx, dy) | 518 | return touchpadNavigationScrollEventAt(phase, 0, dx, dy) | ||
| 243 | } | 519 | } | ||
| 244 | 520 | ||||
| 521 | + | // ViewWidth is provided separately on touchpadNavigationInput; the recognizer | |||
| 522 | + | // caches it from the initial Begin event for subsequent Update and End phases. | |||
| 245 | func touchpadNavigationScrollEventAt(phase cef2gtk.ScrollPhase, x, dx, dy float64) cef2gtk.ScrollEvent { | 523 | func touchpadNavigationScrollEventAt(phase cef2gtk.ScrollPhase, x, dx, dy float64) cef2gtk.ScrollEvent { | ||
| 246 | return cef2gtk.ScrollEvent{ | 524 | return cef2gtk.ScrollEvent{ | ||
| 247 | Phase: phase, | 525 | Phase: phase, | ||
M internal/infrastructure/cef/webview.go +9 -6
| @@ -1137,6 +1137,7 @@ func (wv *WebView) Destroy() { | |||
| 1137 | 1137 | wv.openerMessageCallbacks = nil | |
| 1138 | 1138 | wv.openerNavigationCallbacks = nil | |
| 1139 | 1139 | wv.popupReadyToShow = nil | |
| 1140 | + | wv.touchpadNavigation = nil | |
| 1140 | 1141 | wv.inputAttached = false | |
| 1141 | 1142 | wv.pendingNativePopups = nil | |
| 1142 | 1143 | wv.nativePopupParent = nil | |
| @@ -1486,10 +1487,7 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions { | |||
| 1486 | 1487 | // waits for a deliberate release past the visual threshold. | |
| 1487 | 1488 | Enabled: false, | |
| 1488 | 1489 | }, | |
| 1489 | - | CanNavigateBack: wv.CanGoBack, | |
| 1490 | - | CanNavigateForward: wv.CanGoForward, | |
| 1491 | - | OnNavigateSwipe: wv.handleNavigationSwipeAction, | |
| 1492 | - | SelectionText: wv.selectedTextSnapshot, | |
| 1490 | + | SelectionText: wv.selectedTextSnapshot, | |
| 1493 | 1491 | OnClipboardShortcut: func(action, text string) { | |
| 1494 | 1492 | if wv.engine != nil { | |
| 1495 | 1493 | wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text) | |
| @@ -1498,13 +1496,16 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions { | |||
| 1498 | 1496 | } | |
| 1499 | 1497 | } | |
| 1500 | 1498 | ||
| 1499 | + | // handleScrollInput is invoked by cef2gtk from GTK scroll event callbacks on | |
| 1500 | + | // the GTK main thread, which keeps lazy touchpad recognizer ownership | |
| 1501 | + | // single-threaded while every scroll event is still forwarded to CEF. | |
| 1501 | 1502 | func (wv *WebView) handleScrollInput(event cef2gtk.ScrollEvent) cef2gtk.ScrollDecision { | |
| 1502 | 1503 | wv.handleTouchpadNavigationScroll(event) | |
| 1503 | 1504 | return wv.handleScrollInputDiagnostic(event) | |
| 1504 | 1505 | } | |
| 1505 | 1506 | ||
| 1506 | 1507 | func (wv *WebView) handleTouchpadNavigationScroll(event cef2gtk.ScrollEvent) { | |
| 1507 | - | if wv == nil { | |
| 1508 | + | if wv == nil || wv.destroyed.Load() { | |
| 1508 | 1509 | return | |
| 1509 | 1510 | } | |
| 1510 | 1511 | if wv.touchpadNavigation == nil { | |
| @@ -1543,7 +1544,9 @@ func (wv *WebView) emitTouchpadNavigationGesture(gesture port.TouchpadNavigation | |||
| 1543 | 1544 | cb := wv.callbacks | |
| 1544 | 1545 | wv.mu.RUnlock() | |
| 1545 | 1546 | if cb != nil && cb.OnTouchpadNavigationGesture != nil { | |
| 1546 | - | cb.OnTouchpadNavigationGesture(gesture) | |
| 1547 | + | wv.runOnGTK(func() { | |
| 1548 | + | cb.OnTouchpadNavigationGesture(gesture) | |
| 1549 | + | }) | |
| 1547 | 1550 | } | |
| 1548 | 1551 | } | |
| 1549 | 1552 | ||
| @@ -1137,6 +1137,7 @@ func (wv *WebView) Destroy() { | |||||
| 1137 | wv.openerMessageCallbacks = nil | 1137 | wv.openerMessageCallbacks = nil | ||
| 1138 | wv.openerNavigationCallbacks = nil | 1138 | wv.openerNavigationCallbacks = nil | ||
| 1139 | wv.popupReadyToShow = nil | 1139 | wv.popupReadyToShow = nil | ||
| 1140 | + | wv.touchpadNavigation = nil | |||
| 1140 | wv.inputAttached = false | 1141 | wv.inputAttached = false | ||
| 1141 | wv.pendingNativePopups = nil | 1142 | wv.pendingNativePopups = nil | ||
| 1142 | wv.nativePopupParent = nil | 1143 | wv.nativePopupParent = nil | ||
| @@ -1486,10 +1487,7 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions { | |||||
| 1486 | // waits for a deliberate release past the visual threshold. | 1487 | // waits for a deliberate release past the visual threshold. | ||
| 1487 | Enabled: false, | 1488 | Enabled: false, | ||
| 1488 | }, | 1489 | }, | ||
| 1489 | - | CanNavigateBack: wv.CanGoBack, | 1490 | + | SelectionText: wv.selectedTextSnapshot, |
| 1490 | - | CanNavigateForward: wv.CanGoForward, | |||
| 1491 | - | OnNavigateSwipe: wv.handleNavigationSwipeAction, | |||
| 1492 | - | SelectionText: wv.selectedTextSnapshot, | |||
| 1493 | OnClipboardShortcut: func(action, text string) { | 1491 | OnClipboardShortcut: func(action, text string) { | ||
| 1494 | if wv.engine != nil { | 1492 | if wv.engine != nil { | ||
| 1495 | wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text) | 1493 | wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text) | ||
| @@ -1498,13 +1496,16 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions { | |||||
| 1498 | } | 1496 | } | ||
| 1499 | } | 1497 | } | ||
| 1500 | 1498 | ||||
| 1499 | + | // handleScrollInput is invoked by cef2gtk from GTK scroll event callbacks on | |||
| 1500 | + | // the GTK main thread, which keeps lazy touchpad recognizer ownership | |||
| 1501 | + | // single-threaded while every scroll event is still forwarded to CEF. | |||
| 1501 | func (wv *WebView) handleScrollInput(event cef2gtk.ScrollEvent) cef2gtk.ScrollDecision { | 1502 | func (wv *WebView) handleScrollInput(event cef2gtk.ScrollEvent) cef2gtk.ScrollDecision { | ||
| 1502 | wv.handleTouchpadNavigationScroll(event) | 1503 | wv.handleTouchpadNavigationScroll(event) | ||
| 1503 | return wv.handleScrollInputDiagnostic(event) | 1504 | return wv.handleScrollInputDiagnostic(event) | ||
| 1504 | } | 1505 | } | ||
| 1505 | 1506 | ||||
| 1506 | func (wv *WebView) handleTouchpadNavigationScroll(event cef2gtk.ScrollEvent) { | 1507 | func (wv *WebView) handleTouchpadNavigationScroll(event cef2gtk.ScrollEvent) { | ||
| 1507 | - | if wv == nil { | 1508 | + | if wv == nil || wv.destroyed.Load() { |
| 1508 | return | 1509 | return | ||
| 1509 | } | 1510 | } | ||
| 1510 | if wv.touchpadNavigation == nil { | 1511 | if wv.touchpadNavigation == nil { | ||
| @@ -1543,7 +1544,9 @@ func (wv *WebView) emitTouchpadNavigationGesture(gesture port.TouchpadNavigation | |||||
| 1543 | cb := wv.callbacks | 1544 | cb := wv.callbacks | ||
| 1544 | wv.mu.RUnlock() | 1545 | wv.mu.RUnlock() | ||
| 1545 | if cb != nil && cb.OnTouchpadNavigationGesture != nil { | 1546 | if cb != nil && cb.OnTouchpadNavigationGesture != nil { | ||
| 1546 | - | cb.OnTouchpadNavigationGesture(gesture) | 1547 | + | wv.runOnGTK(func() { |
| 1548 | + | cb.OnTouchpadNavigationGesture(gesture) | |||
| 1549 | + | }) | |||
| 1547 | } | 1550 | } | ||
| 1548 | } | 1551 | } | ||
| 1549 | 1552 | ||||
M internal/infrastructure/cef/webview_input_test.go +6 -3
| @@ -39,9 +39,9 @@ func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T) | |||
| 39 | 39 | require.False(t, opts.NavigationSwipe.Enabled) | |
| 40 | 40 | require.Zero(t, opts.NavigationSwipe.MinDelta) | |
| 41 | 41 | require.Zero(t, opts.NavigationSwipe.MaxVerticalRatio) | |
| 42 | - | require.NotNil(t, opts.CanNavigateBack) | |
| 43 | - | require.NotNil(t, opts.CanNavigateForward) | |
| 44 | - | require.NotNil(t, opts.OnNavigateSwipe) | |
| 42 | + | require.Nil(t, opts.CanNavigateBack) | |
| 43 | + | require.Nil(t, opts.CanNavigateForward) | |
| 44 | + | require.Nil(t, opts.OnNavigateSwipe) | |
| 45 | 45 | require.NotNil(t, opts.SelectionText) | |
| 46 | 46 | require.NotNil(t, opts.OnClipboardShortcut) | |
| 47 | 47 | require.Same(t, nativeWidget, wv.nativeWidget) | |
| @@ -58,6 +58,9 @@ func TestWebViewHandleScrollInput_ForwardsOrdinaryHorizontalScroll(t *testing.T) | |||
| 58 | 58 | canGoBack: true, | |
| 59 | 59 | } | |
| 60 | 60 | ||
| 61 | + | // This verifies the bridge-level forwarding contract for ordinary scroll | |
| 62 | + | // updates. The nil native widget leaves view width unknown, so local history | |
| 63 | + | // gesture recognition fails closed and the event continues to CEF. | |
| 61 | 64 | decision := wv.handleScrollInput(cef2gtk.ScrollEvent{ | |
| 62 | 65 | Phase: cef2gtk.ScrollPhaseUpdate, | |
| 63 | 66 | X: 500, | |
| @@ -39,9 +39,9 @@ func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T) | |||||
| 39 | require.False(t, opts.NavigationSwipe.Enabled) | 39 | require.False(t, opts.NavigationSwipe.Enabled) | ||
| 40 | require.Zero(t, opts.NavigationSwipe.MinDelta) | 40 | require.Zero(t, opts.NavigationSwipe.MinDelta) | ||
| 41 | require.Zero(t, opts.NavigationSwipe.MaxVerticalRatio) | 41 | require.Zero(t, opts.NavigationSwipe.MaxVerticalRatio) | ||
| 42 | - | require.NotNil(t, opts.CanNavigateBack) | 42 | + | require.Nil(t, opts.CanNavigateBack) |
| 43 | - | require.NotNil(t, opts.CanNavigateForward) | 43 | + | require.Nil(t, opts.CanNavigateForward) |
| 44 | - | require.NotNil(t, opts.OnNavigateSwipe) | 44 | + | require.Nil(t, opts.OnNavigateSwipe) |
| 45 | require.NotNil(t, opts.SelectionText) | 45 | require.NotNil(t, opts.SelectionText) | ||
| 46 | require.NotNil(t, opts.OnClipboardShortcut) | 46 | require.NotNil(t, opts.OnClipboardShortcut) | ||
| 47 | require.Same(t, nativeWidget, wv.nativeWidget) | 47 | require.Same(t, nativeWidget, wv.nativeWidget) | ||
| @@ -58,6 +58,9 @@ func TestWebViewHandleScrollInput_ForwardsOrdinaryHorizontalScroll(t *testing.T) | |||||
| 58 | canGoBack: true, | 58 | canGoBack: true, | ||
| 59 | } | 59 | } | ||
| 60 | 60 | ||||
| 61 | + | // This verifies the bridge-level forwarding contract for ordinary scroll | |||
| 62 | + | // updates. The nil native widget leaves view width unknown, so local history | |||
| 63 | + | // gesture recognition fails closed and the event continues to CEF. | |||
| 61 | decision := wv.handleScrollInput(cef2gtk.ScrollEvent{ | 64 | decision := wv.handleScrollInput(cef2gtk.ScrollEvent{ | ||
| 62 | Phase: cef2gtk.ScrollPhaseUpdate, | 65 | Phase: cef2gtk.ScrollPhaseUpdate, | ||
| 63 | X: 500, | 66 | X: 500, | ||
M internal/infrastructure/config/defaults.go +2 -0
| @@ -88,6 +88,8 @@ const ( | |||
| 88 | 88 | defaultCEFScrollPreciseMultiplier = 2.5 | |
| 89 | 89 | defaultCEFScrollMaxDelta = 0 | |
| 90 | 90 | defaultCEFTouchpadNavigation = true | |
| 91 | + | // Require a deliberate swipe before history navigation so ordinary | |
| 92 | + | // horizontal scrolling is unlikely to cross the commit threshold. | |
| 91 | 93 | defaultCEFTouchpadNavigationDelta = 320.0 | |
| 92 | 94 | defaultCEFTouchpadNavigationRatio = 0.5 | |
| 93 | 95 | ||
| @@ -88,6 +88,8 @@ const ( | |||||
| 88 | defaultCEFScrollPreciseMultiplier = 2.5 | 88 | defaultCEFScrollPreciseMultiplier = 2.5 | ||
| 89 | defaultCEFScrollMaxDelta = 0 | 89 | defaultCEFScrollMaxDelta = 0 | ||
| 90 | defaultCEFTouchpadNavigation = true | 90 | defaultCEFTouchpadNavigation = true | ||
| 91 | + | // Require a deliberate swipe before history navigation so ordinary | |||
| 92 | + | // horizontal scrolling is unlikely to cross the commit threshold. | |||
| 91 | defaultCEFTouchpadNavigationDelta = 320.0 | 93 | defaultCEFTouchpadNavigationDelta = 320.0 | ||
| 92 | defaultCEFTouchpadNavigationRatio = 0.5 | 94 | defaultCEFTouchpadNavigationRatio = 0.5 | ||
| 93 | 95 | ||||
M internal/infrastructure/config/defaults_test.go +0 -1
| @@ -32,7 +32,6 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) { | |||
| 32 | 32 | assert.Equal(t, int32(defaultCEFScrollMaxDelta), cfg.Engine.CEF.Input.ScrollMaxDelta) | |
| 33 | 33 | assert.Equal(t, defaultCEFTouchpadNavigation, cfg.Engine.CEF.Input.TouchpadNavigationEnabled) | |
| 34 | 34 | assert.InDelta(t, defaultCEFTouchpadNavigationDelta, cfg.Engine.CEF.Input.TouchpadNavigationMinDelta, 0.001) | |
| 35 | - | assert.InDelta(t, 320.0, cfg.Engine.CEF.Input.TouchpadNavigationMinDelta, 0.001, "touchpad navigation should require a deliberate swipe by default") | |
| 36 | 35 | assert.InDelta(t, defaultCEFTouchpadNavigationRatio, cfg.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio, 0.001) | |
| 37 | 36 | assert.True(t, cfg.Engine.WebKit.ITPEnabled) | |
| 38 | 37 | ||
| @@ -32,7 +32,6 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) { | |||||
| 32 | assert.Equal(t, int32(defaultCEFScrollMaxDelta), cfg.Engine.CEF.Input.ScrollMaxDelta) | 32 | assert.Equal(t, int32(defaultCEFScrollMaxDelta), cfg.Engine.CEF.Input.ScrollMaxDelta) | ||
| 33 | assert.Equal(t, defaultCEFTouchpadNavigation, cfg.Engine.CEF.Input.TouchpadNavigationEnabled) | 33 | assert.Equal(t, defaultCEFTouchpadNavigation, cfg.Engine.CEF.Input.TouchpadNavigationEnabled) | ||
| 34 | assert.InDelta(t, defaultCEFTouchpadNavigationDelta, cfg.Engine.CEF.Input.TouchpadNavigationMinDelta, 0.001) | 34 | assert.InDelta(t, defaultCEFTouchpadNavigationDelta, cfg.Engine.CEF.Input.TouchpadNavigationMinDelta, 0.001) | ||
| 35 | - | assert.InDelta(t, 320.0, cfg.Engine.CEF.Input.TouchpadNavigationMinDelta, 0.001, "touchpad navigation should require a deliberate swipe by default") | |||
| 36 | assert.InDelta(t, defaultCEFTouchpadNavigationRatio, cfg.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio, 0.001) | 35 | assert.InDelta(t, defaultCEFTouchpadNavigationRatio, cfg.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio, 0.001) | ||
| 37 | assert.True(t, cfg.Engine.WebKit.ITPEnabled) | 36 | assert.True(t, cfg.Engine.WebKit.ITPEnabled) | ||
| 38 | 37 | ||||
M internal/ui/component/touchpad_navigation_indicator.go +13 -30
| @@ -1,8 +1,6 @@ | |||
| 1 | 1 | package component | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "sync" | |
| 5 | - | ||
| 6 | 4 | "github.com/bnema/dumber/internal/application/port" | |
| 7 | 5 | "github.com/bnema/puregotk/v4/glib" | |
| 8 | 6 | "github.com/bnema/puregotk/v4/gtk" | |
| @@ -29,7 +27,6 @@ type TouchpadNavigationIndicator struct { | |||
| 29 | 27 | bar *gtk.ProgressBar | |
| 30 | 28 | ||
| 31 | 29 | hideTimer uint | |
| 32 | - | mu sync.Mutex | |
| 33 | 30 | } | |
| 34 | 31 | ||
| 35 | 32 | func NewTouchpadNavigationIndicator() *TouchpadNavigationIndicator { | |
| @@ -121,9 +118,7 @@ func (i *TouchpadNavigationIndicator) ShowGesture(gesture port.TouchpadNavigatio | |||
| 121 | 118 | if i == nil || i.container == nil || i.icon == nil || i.label == nil || i.bar == nil { | |
| 122 | 119 | return | |
| 123 | 120 | } | |
| 124 | - | i.mu.Lock() | |
| 125 | - | defer i.mu.Unlock() | |
| 126 | - | i.cancelHideTimerLocked() | |
| 121 | + | i.cancelHideTimer() | |
| 127 | 122 | ||
| 128 | 123 | i.container.RemoveCssClass("back") | |
| 129 | 124 | i.container.RemoveCssClass("forward") | |
| @@ -149,29 +144,19 @@ func (i *TouchpadNavigationIndicator) ShowGesture(gesture port.TouchpadNavigatio | |||
| 149 | 144 | i.container.SetVisible(true) | |
| 150 | 145 | ||
| 151 | 146 | if !gesture.Active { | |
| 152 | - | i.scheduleHideLocked(gesture.ThresholdReached) | |
| 153 | - | } | |
| 154 | - | } | |
| 155 | - | ||
| 156 | - | func (i *TouchpadNavigationIndicator) Hide() { | |
| 157 | - | if i == nil || i.container == nil { | |
| 158 | - | return | |
| 147 | + | i.scheduleHide(gesture.ThresholdReached) | |
| 159 | 148 | } | |
| 160 | - | i.mu.Lock() | |
| 161 | - | defer i.mu.Unlock() | |
| 162 | - | i.hideLocked() | |
| 163 | 149 | } | |
| 164 | 150 | ||
| 165 | 151 | func (i *TouchpadNavigationIndicator) Destroy() { | |
| 166 | 152 | if i == nil { | |
| 167 | 153 | return | |
| 168 | 154 | } | |
| 169 | - | i.mu.Lock() | |
| 170 | - | defer i.mu.Unlock() | |
| 171 | - | i.cancelHideTimerLocked() | |
| 155 | + | i.cancelHideTimer() | |
| 172 | 156 | if i.container != nil { | |
| 173 | 157 | i.container.SetVisible(false) | |
| 174 | 158 | i.container.Unparent() | |
| 159 | + | i.container.Unref() | |
| 175 | 160 | } | |
| 176 | 161 | i.container = nil | |
| 177 | 162 | i.icon = nil | |
| @@ -179,27 +164,25 @@ func (i *TouchpadNavigationIndicator) Destroy() { | |||
| 179 | 164 | i.bar = nil | |
| 180 | 165 | } | |
| 181 | 166 | ||
| 182 | - | func (i *TouchpadNavigationIndicator) scheduleHideLocked(triggered bool) { | |
| 167 | + | func (i *TouchpadNavigationIndicator) scheduleHide(triggered bool) { | |
| 183 | 168 | delay := uint(0) | |
| 184 | 169 | if triggered { | |
| 185 | 170 | delay = touchpadNavigationIndicatorHideMs | |
| 186 | 171 | } | |
| 172 | + | if delay == 0 { | |
| 173 | + | i.hide() | |
| 174 | + | return | |
| 175 | + | } | |
| 187 | 176 | cb := glib.SourceFunc(func(_ uintptr) bool { | |
| 188 | - | i.mu.Lock() | |
| 189 | - | defer i.mu.Unlock() | |
| 190 | 177 | i.hideTimer = 0 | |
| 191 | - | i.hideLocked() | |
| 178 | + | i.hide() | |
| 192 | 179 | return false | |
| 193 | 180 | }) | |
| 194 | - | if delay == 0 { | |
| 195 | - | i.hideLocked() | |
| 196 | - | return | |
| 197 | - | } | |
| 198 | 181 | i.hideTimer = glib.TimeoutAdd(delay, &cb, 0) | |
| 199 | 182 | } | |
| 200 | 183 | ||
| 201 | - | func (i *TouchpadNavigationIndicator) hideLocked() { | |
| 202 | - | i.cancelHideTimerLocked() | |
| 184 | + | func (i *TouchpadNavigationIndicator) hide() { | |
| 185 | + | i.cancelHideTimer() | |
| 203 | 186 | if i.container != nil { | |
| 204 | 187 | i.container.RemoveCssClass("threshold-reached") | |
| 205 | 188 | i.container.RemoveCssClass("back") | |
| @@ -211,7 +194,7 @@ func (i *TouchpadNavigationIndicator) hideLocked() { | |||
| 211 | 194 | } | |
| 212 | 195 | } | |
| 213 | 196 | ||
| 214 | - | func (i *TouchpadNavigationIndicator) cancelHideTimerLocked() { | |
| 197 | + | func (i *TouchpadNavigationIndicator) cancelHideTimer() { | |
| 215 | 198 | if i.hideTimer != 0 { | |
| 216 | 199 | glib.SourceRemove(i.hideTimer) | |
| 217 | 200 | i.hideTimer = 0 | |
| @@ -1,8 +1,6 @@ | |||||
| 1 | package component | 1 | package component | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "sync" | |||
| 5 | - | ||||
| 6 | "github.com/bnema/dumber/internal/application/port" | 4 | "github.com/bnema/dumber/internal/application/port" | ||
| 7 | "github.com/bnema/puregotk/v4/glib" | 5 | "github.com/bnema/puregotk/v4/glib" | ||
| 8 | "github.com/bnema/puregotk/v4/gtk" | 6 | "github.com/bnema/puregotk/v4/gtk" | ||
| @@ -29,7 +27,6 @@ type TouchpadNavigationIndicator struct { | |||||
| 29 | bar *gtk.ProgressBar | 27 | bar *gtk.ProgressBar | ||
| 30 | 28 | ||||
| 31 | hideTimer uint | 29 | hideTimer uint | ||
| 32 | - | mu sync.Mutex | |||
| 33 | } | 30 | } | ||
| 34 | 31 | ||||
| 35 | func NewTouchpadNavigationIndicator() *TouchpadNavigationIndicator { | 32 | func NewTouchpadNavigationIndicator() *TouchpadNavigationIndicator { | ||
| @@ -121,9 +118,7 @@ func (i *TouchpadNavigationIndicator) ShowGesture(gesture port.TouchpadNavigatio | |||||
| 121 | if i == nil || i.container == nil || i.icon == nil || i.label == nil || i.bar == nil { | 118 | if i == nil || i.container == nil || i.icon == nil || i.label == nil || i.bar == nil { | ||
| 122 | return | 119 | return | ||
| 123 | } | 120 | } | ||
| 124 | - | i.mu.Lock() | 121 | + | i.cancelHideTimer() |
| 125 | - | defer i.mu.Unlock() | |||
| 126 | - | i.cancelHideTimerLocked() | |||
| 127 | 122 | ||||
| 128 | i.container.RemoveCssClass("back") | 123 | i.container.RemoveCssClass("back") | ||
| 129 | i.container.RemoveCssClass("forward") | 124 | i.container.RemoveCssClass("forward") | ||
| @@ -149,29 +144,19 @@ func (i *TouchpadNavigationIndicator) ShowGesture(gesture port.TouchpadNavigatio | |||||
| 149 | i.container.SetVisible(true) | 144 | i.container.SetVisible(true) | ||
| 150 | 145 | ||||
| 151 | if !gesture.Active { | 146 | if !gesture.Active { | ||
| 152 | - | i.scheduleHideLocked(gesture.ThresholdReached) | 147 | + | i.scheduleHide(gesture.ThresholdReached) |
| 153 | - | } | |||
| 154 | - | } | |||
| 155 | - | ||||
| 156 | - | func (i *TouchpadNavigationIndicator) Hide() { | |||
| 157 | - | if i == nil || i.container == nil { | |||
| 158 | - | return | |||
| 159 | } | 148 | } | ||
| 160 | - | i.mu.Lock() | |||
| 161 | - | defer i.mu.Unlock() | |||
| 162 | - | i.hideLocked() | |||
| 163 | } | 149 | } | ||
| 164 | 150 | ||||
| 165 | func (i *TouchpadNavigationIndicator) Destroy() { | 151 | func (i *TouchpadNavigationIndicator) Destroy() { | ||
| 166 | if i == nil { | 152 | if i == nil { | ||
| 167 | return | 153 | return | ||
| 168 | } | 154 | } | ||
| 169 | - | i.mu.Lock() | 155 | + | i.cancelHideTimer() |
| 170 | - | defer i.mu.Unlock() | |||
| 171 | - | i.cancelHideTimerLocked() | |||
| 172 | if i.container != nil { | 156 | if i.container != nil { | ||
| 173 | i.container.SetVisible(false) | 157 | i.container.SetVisible(false) | ||
| 174 | i.container.Unparent() | 158 | i.container.Unparent() | ||
| 159 | + | i.container.Unref() | |||
| 175 | } | 160 | } | ||
| 176 | i.container = nil | 161 | i.container = nil | ||
| 177 | i.icon = nil | 162 | i.icon = nil | ||
| @@ -179,27 +164,25 @@ func (i *TouchpadNavigationIndicator) Destroy() { | |||||
| 179 | i.bar = nil | 164 | i.bar = nil | ||
| 180 | } | 165 | } | ||
| 181 | 166 | ||||
| 182 | - | func (i *TouchpadNavigationIndicator) scheduleHideLocked(triggered bool) { | 167 | + | func (i *TouchpadNavigationIndicator) scheduleHide(triggered bool) { |
| 183 | delay := uint(0) | 168 | delay := uint(0) | ||
| 184 | if triggered { | 169 | if triggered { | ||
| 185 | delay = touchpadNavigationIndicatorHideMs | 170 | delay = touchpadNavigationIndicatorHideMs | ||
| 186 | } | 171 | } | ||
| 172 | + | if delay == 0 { | |||
| 173 | + | i.hide() | |||
| 174 | + | return | |||
| 175 | + | } | |||
| 187 | cb := glib.SourceFunc(func(_ uintptr) bool { | 176 | cb := glib.SourceFunc(func(_ uintptr) bool { | ||
| 188 | - | i.mu.Lock() | |||
| 189 | - | defer i.mu.Unlock() | |||
| 190 | i.hideTimer = 0 | 177 | i.hideTimer = 0 | ||
| 191 | - | i.hideLocked() | 178 | + | i.hide() |
| 192 | return false | 179 | return false | ||
| 193 | }) | 180 | }) | ||
| 194 | - | if delay == 0 { | |||
| 195 | - | i.hideLocked() | |||
| 196 | - | return | |||
| 197 | - | } | |||
| 198 | i.hideTimer = glib.TimeoutAdd(delay, &cb, 0) | 181 | i.hideTimer = glib.TimeoutAdd(delay, &cb, 0) | ||
| 199 | } | 182 | } | ||
| 200 | 183 | ||||
| 201 | - | func (i *TouchpadNavigationIndicator) hideLocked() { | 184 | + | func (i *TouchpadNavigationIndicator) hide() { |
| 202 | - | i.cancelHideTimerLocked() | 185 | + | i.cancelHideTimer() |
| 203 | if i.container != nil { | 186 | if i.container != nil { | ||
| 204 | i.container.RemoveCssClass("threshold-reached") | 187 | i.container.RemoveCssClass("threshold-reached") | ||
| 205 | i.container.RemoveCssClass("back") | 188 | i.container.RemoveCssClass("back") | ||
| @@ -211,7 +194,7 @@ func (i *TouchpadNavigationIndicator) hideLocked() { | |||||
| 211 | } | 194 | } | ||
| 212 | } | 195 | } | ||
| 213 | 196 | ||||
| 214 | - | func (i *TouchpadNavigationIndicator) cancelHideTimerLocked() { | 197 | + | func (i *TouchpadNavigationIndicator) cancelHideTimer() { |
| 215 | if i.hideTimer != 0 { | 198 | if i.hideTimer != 0 { | ||
| 216 | glib.SourceRemove(i.hideTimer) | 199 | glib.SourceRemove(i.hideTimer) | ||
| 217 | i.hideTimer = 0 | 200 | i.hideTimer = 0 | ||