fix(core): use one touchpad navigation path in overview
- commit
- 575f049bed5d361c871c17b02a40ae88cdd9217c
- author
- brice · · 2026-10-06 04:34 UTC
- parent
- c00cfd6
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email - refs
- fix/overview-touchpad-routing
8 files · +117 -43
M docs/config.md +1 -1
| @@ -259,7 +259,7 @@ The focus binds (`focus-column-left/right`, `focus-window-up/down`, `cmd+arrows` | |||
| 259 | 259 | ||
| 260 | 260 | Each workspace's [stash](#stash), hidden or not, shows as a pile of cards on the left of its row, which stays centred unless it would overlap the pile: its selected window in front, up to three others behind it, dimmed. In the pile, `k` / `j` or up/down browse stash cards and stop at either end without changing workspace. Right (`l`) leaves the pile immediately; left (`h`) stays in the pile. On a stash-only workspace, right deselects the pile so up/down can change workspace. Browsing brings the selected card to the front without reordering the stash. `return` or a click on a card closes the overview with the stash shown on that window. | |
| 261 | 261 | ||
| 262 | - | A four-finger swipe up opens the overview, or hides the [stash](#stash) when it is shown, and a swipe down closes the overview on the selection (with the overview closed, a swipe down shows or hides the stash), whatever `touchpad.natural-scroll` says. Two-finger scrolling and the mouse wheel move the selection: left and right through the columns, up and down through stack cards before crossing workspaces, following `touchpad.natural-scroll` for two-finger scrolling and `mouse.natural-scroll` for the wheel. A two-finger scroll sideways moves it to the next column after a short distance, then one column per longer distance, and stays on the columns until the fingers lift; up or down it moves one step per scroll, so a card in a stack is easy to pick. The wheel moves one step per notch. A three-finger swipe moves it one step when the fingers lift: left/right enters or leaves the stash pile, and up/down browses its cards while it is selected. Two-finger scrolling, the wheel and keyboard navigation follow the same pile boundaries. | |
| 262 | + | A four-finger swipe up opens the overview, or hides the [stash](#stash) when it is shown, and a swipe down closes the overview on the selection (with the overview closed, a swipe down shows or hides the stash), whatever `touchpad.natural-scroll` says. Two-finger scrolling belongs to applications and never navigates the overview; previews do not accept application input. Three fingers move the selection during the gesture, following `touchpad.natural-scroll`: horizontally, a short distance selects the next app and further movement repeats at a longer distance; vertically, one step per gesture selects a stack card or changes workspace. Inside a selected stash pile, vertical movement browses its cards and horizontal movement enters or leaves the pile. Lifting resets the gesture without an extra step; cancellation keeps selections already made. The mouse wheel moves one step per notch, following `mouse.natural-scroll`. Three-finger navigation, the wheel and keyboard navigation share the same pile boundaries. | |
| 263 | 263 | ||
| 264 | 264 | Return or a click on a front tile or peeking card commits it; card changes remain provisional until then. `close-window` targets the selected preview. Move-to-workspace binds (`move-column-to-workspace*`, `move-window-to-workspace*`) move the selected preview at once and keep the overview open; with `focus.follow-move`, the selection follows it. Escape does not undo a move, and the row that received the window keeps it focused. A selected stash card, or a row with no preview, does not move. Other window mutation binds (moving within the row, resizing, maximizing, fullscreen and stash toggles) and moving a workspace to another monitor are disabled while the overview is open. Workspace and monitor navigation, launch and quit binds remain active. | |
| 265 | 265 | ||
| @@ -259,7 +259,7 @@ The focus binds (`focus-column-left/right`, `focus-window-up/down`, `cmd+arrows` | |||||
| 259 | 259 | ||||
| 260 | Each workspace's [stash](#stash), hidden or not, shows as a pile of cards on the left of its row, which stays centred unless it would overlap the pile: its selected window in front, up to three others behind it, dimmed. In the pile, `k` / `j` or up/down browse stash cards and stop at either end without changing workspace. Right (`l`) leaves the pile immediately; left (`h`) stays in the pile. On a stash-only workspace, right deselects the pile so up/down can change workspace. Browsing brings the selected card to the front without reordering the stash. `return` or a click on a card closes the overview with the stash shown on that window. | 260 | Each workspace's [stash](#stash), hidden or not, shows as a pile of cards on the left of its row, which stays centred unless it would overlap the pile: its selected window in front, up to three others behind it, dimmed. In the pile, `k` / `j` or up/down browse stash cards and stop at either end without changing workspace. Right (`l`) leaves the pile immediately; left (`h`) stays in the pile. On a stash-only workspace, right deselects the pile so up/down can change workspace. Browsing brings the selected card to the front without reordering the stash. `return` or a click on a card closes the overview with the stash shown on that window. | ||
| 261 | 261 | ||||
| 262 | - | A four-finger swipe up opens the overview, or hides the [stash](#stash) when it is shown, and a swipe down closes the overview on the selection (with the overview closed, a swipe down shows or hides the stash), whatever `touchpad.natural-scroll` says. Two-finger scrolling and the mouse wheel move the selection: left and right through the columns, up and down through stack cards before crossing workspaces, following `touchpad.natural-scroll` for two-finger scrolling and `mouse.natural-scroll` for the wheel. A two-finger scroll sideways moves it to the next column after a short distance, then one column per longer distance, and stays on the columns until the fingers lift; up or down it moves one step per scroll, so a card in a stack is easy to pick. The wheel moves one step per notch. A three-finger swipe moves it one step when the fingers lift: left/right enters or leaves the stash pile, and up/down browses its cards while it is selected. Two-finger scrolling, the wheel and keyboard navigation follow the same pile boundaries. | 262 | + | A four-finger swipe up opens the overview, or hides the [stash](#stash) when it is shown, and a swipe down closes the overview on the selection (with the overview closed, a swipe down shows or hides the stash), whatever `touchpad.natural-scroll` says. Two-finger scrolling belongs to applications and never navigates the overview; previews do not accept application input. Three fingers move the selection during the gesture, following `touchpad.natural-scroll`: horizontally, a short distance selects the next app and further movement repeats at a longer distance; vertically, one step per gesture selects a stack card or changes workspace. Inside a selected stash pile, vertical movement browses its cards and horizontal movement enters or leaves the pile. Lifting resets the gesture without an extra step; cancellation keeps selections already made. The mouse wheel moves one step per notch, following `mouse.natural-scroll`. Three-finger navigation, the wheel and keyboard navigation share the same pile boundaries. |
| 263 | 263 | ||||
| 264 | Return or a click on a front tile or peeking card commits it; card changes remain provisional until then. `close-window` targets the selected preview. Move-to-workspace binds (`move-column-to-workspace*`, `move-window-to-workspace*`) move the selected preview at once and keep the overview open; with `focus.follow-move`, the selection follows it. Escape does not undo a move, and the row that received the window keeps it focused. A selected stash card, or a row with no preview, does not move. Other window mutation binds (moving within the row, resizing, maximizing, fullscreen and stash toggles) and moving a workspace to another monitor are disabled while the overview is open. Workspace and monitor navigation, launch and quit binds remain active. | 264 | Return or a click on a front tile or peeking card commits it; card changes remain provisional until then. `close-window` targets the selected preview. Move-to-workspace binds (`move-column-to-workspace*`, `move-window-to-workspace*`) move the selected preview at once and keep the overview open; with `focus.follow-move`, the selection follows it. Escape does not undo a move, and the row that received the window keeps it focused. A selected stash card, or a row with no preview, does not move. Other window mutation binds (moving within the row, resizing, maximizing, fullscreen and stash toggles) and moving a workspace to another monitor are disabled while the overview is open. Workspace and monitor navigation, launch and quit binds remain active. | ||
| 265 | 265 | ||||
M internal/core/gesture.go +37 -10
| @@ -15,8 +15,8 @@ import ( | |||
| 15 | 15 | // on the closest, and a spring takes the view there (motion.go). Sideways | |
| 16 | 16 | // over the shown stash the swipe slides the stash the same way, one window | |
| 17 | 17 | // at most. Where the view cannot slide (fixed overflow, a float, a | |
| 18 | - | // fullscreen window, the overview), the swipe runs one focus action | |
| 19 | - | // instead. | |
| 18 | + | // fullscreen window), the swipe runs one focus action instead. | |
| 19 | + | // In the overview, three fingers use distance-based selection during movement. | |
| 20 | 20 | ||
| 21 | 21 | const ( | |
| 22 | 22 | // swipeDecide is the touchpad distance before the swipe picks its axis | |
| @@ -40,6 +40,8 @@ const ( | |||
| 40 | 40 | swipeColumns | |
| 41 | 41 | swipeWorkspaces | |
| 42 | 42 | swipeDiscrete | |
| 43 | + | // swipeNavigation navigates overview cards during a three-finger gesture. | |
| 44 | + | swipeNavigation | |
| 43 | 45 | // swipeOverview is a vertical four-finger swipe: up opens the | |
| 44 | 46 | // overview, down closes it on the selection (niri's gesture). With | |
| 45 | 47 | // the overview closed, down shows or hides the stash, and up hides | |
| @@ -126,6 +128,10 @@ func (c *Core) swipeBegin(b ports.SwipeBegin) bool { | |||
| 126 | 128 | changed = true | |
| 127 | 129 | } | |
| 128 | 130 | c.swipe = &swipeGesture{screen: c.cur(), fingers: b.Fingers} | |
| 131 | + | if b.Fingers == 3 && c.cur().mon.ov.open && !c.overviewKeyboardTaken() { | |
| 132 | + | c.scrollStop(ports.PointerAxis{Horizontal: ports.ScrollAxis{Stop: true}}) | |
| 133 | + | c.swipe.mode = swipeNavigation | |
| 134 | + | } | |
| 129 | 135 | return changed | |
| 130 | 136 | } | |
| 131 | 137 | ||
| @@ -141,6 +147,23 @@ func (c *Core) swipeUpdate(u ports.SwipeUpdate) bool { | |||
| 141 | 147 | c.swipe = nil | |
| 142 | 148 | return false | |
| 143 | 149 | } | |
| 150 | + | if g.mode == swipeNavigation { | |
| 151 | + | m := g.screen.mon | |
| 152 | + | if !m.ov.open || c.cur() != g.screen || c.overviewKeyboardTaken() { | |
| 153 | + | g.mode = swipeDropped | |
| 154 | + | return false | |
| 155 | + | } | |
| 156 | + | now := c.now() | |
| 157 | + | shots := c.snapshot(now) | |
| 158 | + | changed := m.overviewScroll(ports.PointerAxis{Source: ports.AxisFinger, | |
| 159 | + | Horizontal: ports.ScrollAxis{Set: true, Value: u.DX * c.swipeSign()}, | |
| 160 | + | Vertical: ports.ScrollAxis{Set: true, Value: u.DY * c.swipeSign()}}) | |
| 161 | + | if changed { | |
| 162 | + | c.keyboard.takeBack() | |
| 163 | + | c.transition(shots, now) | |
| 164 | + | } | |
| 165 | + | return changed | |
| 166 | + | } | |
| 144 | 167 | if g.mode == swipeUndecided { | |
| 145 | 168 | g.cx += u.DX | |
| 146 | 169 | g.cy += u.DY | |
| @@ -203,9 +226,11 @@ func (c *Core) decide(g *swipeGesture) { | |||
| 203 | 226 | case g.fingers == 4: | |
| 204 | 227 | // Four fingers sideways do nothing. | |
| 205 | 228 | g.mode = swipeDropped | |
| 206 | - | case m.ov.open || !c.animOn(): | |
| 207 | - | // The overview does not slide: the swipe moves its selection. | |
| 208 | - | // With animations off nothing slides either: the swipe runs a | |
| 229 | + | case m.ov.open: | |
| 230 | + | // Overview navigation is chosen at begin, never on release. | |
| 231 | + | g.mode = swipeDropped | |
| 232 | + | case !c.animOn(): | |
| 233 | + | // With animations off nothing slides: the swipe runs a | |
| 209 | 234 | // focus action when the fingers lift. | |
| 210 | 235 | g.mode, g.snap = swipeDiscrete, newStepSwipe() | |
| 211 | 236 | case g.horizontal && w.stashFocused() && !w.floatFocus && w.stashShown(): | |
| @@ -248,6 +273,9 @@ func (c *Core) swipeEnd(e ports.SwipeEnd) (shown bool) { | |||
| 248 | 273 | now := c.now() | |
| 249 | 274 | m := g.screen.mon | |
| 250 | 275 | switch g.mode { | |
| 276 | + | case swipeNavigation: | |
| 277 | + | c.scrollStop(ports.PointerAxis{Horizontal: ports.ScrollAxis{Stop: true}}) | |
| 278 | + | return false | |
| 251 | 279 | case swipeColumns: | |
| 252 | 280 | w := g.ws | |
| 253 | 281 | if g.columnsChanged(m) { | |
| @@ -366,14 +394,13 @@ func (c *Core) swipeEnd(e ports.SwipeEnd) (shown bool) { | |||
| 366 | 394 | if c.cur() != g.screen { | |
| 367 | 395 | return false | |
| 368 | 396 | } | |
| 397 | + | if c.cur().mon.ov.open { | |
| 398 | + | return false | |
| 399 | + | } | |
| 369 | 400 | before := c.cur().mon.Current() | |
| 370 | 401 | shots := c.snapshot(now) | |
| 371 | 402 | c.keyboard.takeBack() | |
| 372 | - | if mon := c.cur().mon; mon.ov.open { | |
| 373 | - | mon.overviewFocus(a) | |
| 374 | - | } else { | |
| 375 | - | c.applyAction(a) | |
| 376 | - | } | |
| 403 | + | c.applyAction(a) | |
| 377 | 404 | c.transition(shots, now) | |
| 378 | 405 | return c.cur().mon.Current() != before | |
| 379 | 406 | } | |
| @@ -15,8 +15,8 @@ import ( | |||||
| 15 | // on the closest, and a spring takes the view there (motion.go). Sideways | 15 | // on the closest, and a spring takes the view there (motion.go). Sideways | ||
| 16 | // over the shown stash the swipe slides the stash the same way, one window | 16 | // over the shown stash the swipe slides the stash the same way, one window | ||
| 17 | // at most. Where the view cannot slide (fixed overflow, a float, a | 17 | // at most. Where the view cannot slide (fixed overflow, a float, a | ||
| 18 | - | // fullscreen window, the overview), the swipe runs one focus action | 18 | + | // fullscreen window), the swipe runs one focus action instead. |
| 19 | - | // instead. | 19 | + | // In the overview, three fingers use distance-based selection during movement. |
| 20 | 20 | ||||
| 21 | const ( | 21 | const ( | ||
| 22 | // swipeDecide is the touchpad distance before the swipe picks its axis | 22 | // swipeDecide is the touchpad distance before the swipe picks its axis | ||
| @@ -40,6 +40,8 @@ const ( | |||||
| 40 | swipeColumns | 40 | swipeColumns | ||
| 41 | swipeWorkspaces | 41 | swipeWorkspaces | ||
| 42 | swipeDiscrete | 42 | swipeDiscrete | ||
| 43 | + | // swipeNavigation navigates overview cards during a three-finger gesture. | |||
| 44 | + | swipeNavigation | |||
| 43 | // swipeOverview is a vertical four-finger swipe: up opens the | 45 | // swipeOverview is a vertical four-finger swipe: up opens the | ||
| 44 | // overview, down closes it on the selection (niri's gesture). With | 46 | // overview, down closes it on the selection (niri's gesture). With | ||
| 45 | // the overview closed, down shows or hides the stash, and up hides | 47 | // the overview closed, down shows or hides the stash, and up hides | ||
| @@ -126,6 +128,10 @@ func (c *Core) swipeBegin(b ports.SwipeBegin) bool { | |||||
| 126 | changed = true | 128 | changed = true | ||
| 127 | } | 129 | } | ||
| 128 | c.swipe = &swipeGesture{screen: c.cur(), fingers: b.Fingers} | 130 | c.swipe = &swipeGesture{screen: c.cur(), fingers: b.Fingers} | ||
| 131 | + | if b.Fingers == 3 && c.cur().mon.ov.open && !c.overviewKeyboardTaken() { | |||
| 132 | + | c.scrollStop(ports.PointerAxis{Horizontal: ports.ScrollAxis{Stop: true}}) | |||
| 133 | + | c.swipe.mode = swipeNavigation | |||
| 134 | + | } | |||
| 129 | return changed | 135 | return changed | ||
| 130 | } | 136 | } | ||
| 131 | 137 | ||||
| @@ -141,6 +147,23 @@ func (c *Core) swipeUpdate(u ports.SwipeUpdate) bool { | |||||
| 141 | c.swipe = nil | 147 | c.swipe = nil | ||
| 142 | return false | 148 | return false | ||
| 143 | } | 149 | } | ||
| 150 | + | if g.mode == swipeNavigation { | |||
| 151 | + | m := g.screen.mon | |||
| 152 | + | if !m.ov.open || c.cur() != g.screen || c.overviewKeyboardTaken() { | |||
| 153 | + | g.mode = swipeDropped | |||
| 154 | + | return false | |||
| 155 | + | } | |||
| 156 | + | now := c.now() | |||
| 157 | + | shots := c.snapshot(now) | |||
| 158 | + | changed := m.overviewScroll(ports.PointerAxis{Source: ports.AxisFinger, | |||
| 159 | + | Horizontal: ports.ScrollAxis{Set: true, Value: u.DX * c.swipeSign()}, | |||
| 160 | + | Vertical: ports.ScrollAxis{Set: true, Value: u.DY * c.swipeSign()}}) | |||
| 161 | + | if changed { | |||
| 162 | + | c.keyboard.takeBack() | |||
| 163 | + | c.transition(shots, now) | |||
| 164 | + | } | |||
| 165 | + | return changed | |||
| 166 | + | } | |||
| 144 | if g.mode == swipeUndecided { | 167 | if g.mode == swipeUndecided { | ||
| 145 | g.cx += u.DX | 168 | g.cx += u.DX | ||
| 146 | g.cy += u.DY | 169 | g.cy += u.DY | ||
| @@ -203,9 +226,11 @@ func (c *Core) decide(g *swipeGesture) { | |||||
| 203 | case g.fingers == 4: | 226 | case g.fingers == 4: | ||
| 204 | // Four fingers sideways do nothing. | 227 | // Four fingers sideways do nothing. | ||
| 205 | g.mode = swipeDropped | 228 | g.mode = swipeDropped | ||
| 206 | - | case m.ov.open || !c.animOn(): | 229 | + | case m.ov.open: |
| 207 | - | // The overview does not slide: the swipe moves its selection. | 230 | + | // Overview navigation is chosen at begin, never on release. |
| 208 | - | // With animations off nothing slides either: the swipe runs a | 231 | + | g.mode = swipeDropped |
| 232 | + | case !c.animOn(): | |||
| 233 | + | // With animations off nothing slides: the swipe runs a | |||
| 209 | // focus action when the fingers lift. | 234 | // focus action when the fingers lift. | ||
| 210 | g.mode, g.snap = swipeDiscrete, newStepSwipe() | 235 | g.mode, g.snap = swipeDiscrete, newStepSwipe() | ||
| 211 | case g.horizontal && w.stashFocused() && !w.floatFocus && w.stashShown(): | 236 | case g.horizontal && w.stashFocused() && !w.floatFocus && w.stashShown(): | ||
| @@ -248,6 +273,9 @@ func (c *Core) swipeEnd(e ports.SwipeEnd) (shown bool) { | |||||
| 248 | now := c.now() | 273 | now := c.now() | ||
| 249 | m := g.screen.mon | 274 | m := g.screen.mon | ||
| 250 | switch g.mode { | 275 | switch g.mode { | ||
| 276 | + | case swipeNavigation: | |||
| 277 | + | c.scrollStop(ports.PointerAxis{Horizontal: ports.ScrollAxis{Stop: true}}) | |||
| 278 | + | return false | |||
| 251 | case swipeColumns: | 279 | case swipeColumns: | ||
| 252 | w := g.ws | 280 | w := g.ws | ||
| 253 | if g.columnsChanged(m) { | 281 | if g.columnsChanged(m) { | ||
| @@ -366,14 +394,13 @@ func (c *Core) swipeEnd(e ports.SwipeEnd) (shown bool) { | |||||
| 366 | if c.cur() != g.screen { | 394 | if c.cur() != g.screen { | ||
| 367 | return false | 395 | return false | ||
| 368 | } | 396 | } | ||
| 397 | + | if c.cur().mon.ov.open { | |||
| 398 | + | return false | |||
| 399 | + | } | |||
| 369 | before := c.cur().mon.Current() | 400 | before := c.cur().mon.Current() | ||
| 370 | shots := c.snapshot(now) | 401 | shots := c.snapshot(now) | ||
| 371 | c.keyboard.takeBack() | 402 | c.keyboard.takeBack() | ||
| 372 | - | if mon := c.cur().mon; mon.ov.open { | 403 | + | c.applyAction(a) |
| 373 | - | mon.overviewFocus(a) | |||
| 374 | - | } else { | |||
| 375 | - | c.applyAction(a) | |||
| 376 | - | } | |||
| 377 | c.transition(shots, now) | 404 | c.transition(shots, now) | ||
| 378 | return c.cur().mon.Current() != before | 405 | return c.cur().mon.Current() != before | ||
| 379 | } | 406 | } | ||
M internal/core/input.go +5 -3
| @@ -141,8 +141,9 @@ func (c *Core) handleInput(ctx context.Context, ev ports.InputEvent) error { | |||
| 141 | 141 | return nil | |
| 142 | 142 | case ports.PointerAxis: | |
| 143 | 143 | c.scrollStop(v) | |
| 144 | - | // In the overview, scrolling moves the selection. | |
| 145 | - | if c.cur().mon.ov.open && !c.overviewKeyboardTaken() { | |
| 144 | + | // Only wheel/continuous scrolling navigates the overview. | |
| 145 | + | // Two-finger scrolling belongs to clients, never to navigation. | |
| 146 | + | if v.Source != ports.AxisFinger && c.cur().mon.ov.open && !c.overviewKeyboardTaken() { | |
| 146 | 147 | // overviewScroll owns which scrolls step; the shot costs no | |
| 147 | 148 | // allocation once warm. | |
| 148 | 149 | now := c.now() | |
| @@ -172,8 +173,9 @@ func (c *Core) handleInput(ctx context.Context, ev ports.InputEvent) error { | |||
| 172 | 173 | } | |
| 173 | 174 | return nil | |
| 174 | 175 | case ports.SwipeUpdate: | |
| 176 | + | before := c.cur().mon.Current() | |
| 175 | 177 | if c.swipeUpdate(v) { | |
| 176 | - | return c.slid(ctx, false) | |
| 178 | + | return c.slid(ctx, c.cur().mon.Current() != before) | |
| 177 | 179 | } | |
| 178 | 180 | return nil | |
| 179 | 181 | case ports.SwipeEnd: | |
| @@ -141,8 +141,9 @@ func (c *Core) handleInput(ctx context.Context, ev ports.InputEvent) error { | |||||
| 141 | return nil | 141 | return nil | ||
| 142 | case ports.PointerAxis: | 142 | case ports.PointerAxis: | ||
| 143 | c.scrollStop(v) | 143 | c.scrollStop(v) | ||
| 144 | - | // In the overview, scrolling moves the selection. | 144 | + | // Only wheel/continuous scrolling navigates the overview. |
| 145 | - | if c.cur().mon.ov.open && !c.overviewKeyboardTaken() { | 145 | + | // Two-finger scrolling belongs to clients, never to navigation. |
| 146 | + | if v.Source != ports.AxisFinger && c.cur().mon.ov.open && !c.overviewKeyboardTaken() { | |||
| 146 | // overviewScroll owns which scrolls step; the shot costs no | 147 | // overviewScroll owns which scrolls step; the shot costs no | ||
| 147 | // allocation once warm. | 148 | // allocation once warm. | ||
| 148 | now := c.now() | 149 | now := c.now() | ||
| @@ -172,8 +173,9 @@ func (c *Core) handleInput(ctx context.Context, ev ports.InputEvent) error { | |||||
| 172 | } | 173 | } | ||
| 173 | return nil | 174 | return nil | ||
| 174 | case ports.SwipeUpdate: | 175 | case ports.SwipeUpdate: | ||
| 176 | + | before := c.cur().mon.Current() | |||
| 175 | if c.swipeUpdate(v) { | 177 | if c.swipeUpdate(v) { | ||
| 176 | - | return c.slid(ctx, false) | 178 | + | return c.slid(ctx, c.cur().mon.Current() != before) |
| 177 | } | 179 | } | ||
| 178 | return nil | 180 | return nil | ||
| 179 | case ports.SwipeEnd: | 181 | case ports.SwipeEnd: | ||
M internal/core/overview.go +4 -3
| @@ -24,7 +24,7 @@ type overviewState struct { | |||
| 24 | 24 | selected WindowID | |
| 25 | 25 | selectedAt int | |
| 26 | 26 | scrollX, scrollY float64 | |
| 27 | - | // scrolled is set once a two-finger scroll stepped up or down: it | |
| 27 | + | // scrolled is set once a three-finger gesture stepped up or down: it | |
| 28 | 28 | // waits for the fingers to lift before the next step. sideways is set | |
| 29 | 29 | // once it stepped to a column: it stays on columns and steps once per | |
| 30 | 30 | // overviewScrollRepeat. | |
| @@ -931,7 +931,7 @@ const ( | |||
| 931 | 931 | // overviewScrollStep is the scroll distance, in libinput's pointer | |
| 932 | 932 | // units, that moves the selection one column or workspace. | |
| 933 | 933 | overviewScrollStep = 60 | |
| 934 | - | // overviewScrollRepeat is the two-finger distance of each further | |
| 934 | + | // overviewScrollRepeat is the three-finger distance of each further | |
| 935 | 935 | // column step in one scroll: longer than the first, so a short scroll | |
| 936 | 936 | // reaches the next column exactly and a long one goes on. | |
| 937 | 937 | overviewScrollRepeat = 150 | |
| @@ -939,7 +939,8 @@ const ( | |||
| 939 | 939 | ||
| 940 | 940 | // overviewScroll moves the overview selection with a scroll frame: a row | |
| 941 | 941 | // step when the vertical scroll reaches overviewScrollStep and leads, else | |
| 942 | - | // a column step. Two fingers step to the next column after | |
| 942 | + | // a column step. Three-finger updates (encoded here as AxisFinger) step | |
| 943 | + | // to the next column after | |
| 943 | 944 | // overviewScrollStep, then once per overviewScrollRepeat, and stay on | |
| 944 | 945 | // columns until they lift; up or down they step once per scroll, so a card | |
| 945 | 946 | // in a stack is easy to pick. | |
| @@ -24,7 +24,7 @@ type overviewState struct { | |||||
| 24 | selected WindowID | 24 | selected WindowID | ||
| 25 | selectedAt int | 25 | selectedAt int | ||
| 26 | scrollX, scrollY float64 | 26 | scrollX, scrollY float64 | ||
| 27 | - | // scrolled is set once a two-finger scroll stepped up or down: it | 27 | + | // scrolled is set once a three-finger gesture stepped up or down: it |
| 28 | // waits for the fingers to lift before the next step. sideways is set | 28 | // waits for the fingers to lift before the next step. sideways is set | ||
| 29 | // once it stepped to a column: it stays on columns and steps once per | 29 | // once it stepped to a column: it stays on columns and steps once per | ||
| 30 | // overviewScrollRepeat. | 30 | // overviewScrollRepeat. | ||
| @@ -931,7 +931,7 @@ const ( | |||||
| 931 | // overviewScrollStep is the scroll distance, in libinput's pointer | 931 | // overviewScrollStep is the scroll distance, in libinput's pointer | ||
| 932 | // units, that moves the selection one column or workspace. | 932 | // units, that moves the selection one column or workspace. | ||
| 933 | overviewScrollStep = 60 | 933 | overviewScrollStep = 60 | ||
| 934 | - | // overviewScrollRepeat is the two-finger distance of each further | 934 | + | // overviewScrollRepeat is the three-finger distance of each further |
| 935 | // column step in one scroll: longer than the first, so a short scroll | 935 | // column step in one scroll: longer than the first, so a short scroll | ||
| 936 | // reaches the next column exactly and a long one goes on. | 936 | // reaches the next column exactly and a long one goes on. | ||
| 937 | overviewScrollRepeat = 150 | 937 | overviewScrollRepeat = 150 | ||
| @@ -939,7 +939,8 @@ const ( | |||||
| 939 | 939 | ||||
| 940 | // overviewScroll moves the overview selection with a scroll frame: a row | 940 | // overviewScroll moves the overview selection with a scroll frame: a row | ||
| 941 | // step when the vertical scroll reaches overviewScrollStep and leads, else | 941 | // step when the vertical scroll reaches overviewScrollStep and leads, else | ||
| 942 | - | // a column step. Two fingers step to the next column after | 942 | + | // a column step. Three-finger updates (encoded here as AxisFinger) step |
| 943 | + | // to the next column after | |||
| 943 | // overviewScrollStep, then once per overviewScrollRepeat, and stay on | 944 | // overviewScrollStep, then once per overviewScrollRepeat, and stay on | ||
| 944 | // columns until they lift; up or down they step once per scroll, so a card | 945 | // columns until they lift; up or down they step once per scroll, so a card | ||
| 945 | // in a stack is easy to pick. | 946 | // in a stack is easy to pick. | ||
M internal/core/overview_input_test.go +11 -10
| @@ -186,11 +186,8 @@ func TestFloatOnlyMonitorDirectionalExit(t *testing.T) { | |||
| 186 | 186 | } | |
| 187 | 187 | } | |
| 188 | 188 | ||
| 189 | - | // Two-finger scrolling moves the overview selection one column per step, | |
| 190 | - | // along the axis the fingers move most; a small scroll does nothing and | |
| 191 | - | // lifting the fingers starts over. Outside the overview it goes to the | |
| 192 | - | // window. | |
| 193 | - | func TestOverviewTwoFingerScroll(t *testing.T) { | |
| 189 | + | // Three-finger navigation changes selection during movement and resets on lift. | |
| 190 | + | func TestOverviewThreeFingerNavigation(t *testing.T) { | |
| 194 | 191 | cfg := config.Defaults() | |
| 195 | 192 | client := make(chan ports.ClientEvent, 8) | |
| 196 | 193 | input := make(chan ports.InputEvent, 8) | |
| @@ -223,25 +220,29 @@ func TestOverviewTwoFingerScroll(t *testing.T) { | |||
| 223 | 220 | input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper, Pressed: true} | |
| 224 | 221 | sceneMatch(t, scenes, focused(3)) | |
| 225 | 222 | input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper} | |
| 226 | - | finger := func(dx, dy float64) ports.PointerAxis { | |
| 227 | - | return ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Value: dx}, Vertical: ports.ScrollAxis{Set: true, Value: dy}} | |
| 223 | + | finger := func(dx, dy float64) ports.SwipeUpdate { | |
| 224 | + | return ports.SwipeUpdate{DX: dx, DY: dy} | |
| 228 | 225 | } | |
| 226 | + | input <- ports.SwipeBegin{Fingers: 3} | |
| 229 | 227 | // 50 left, lifted, then 50, 50 and 100 left: one step (to 2), the | |
| 230 | 228 | // next ones are longer. Without the reset it would be two (to 1), and | |
| 231 | 229 | // the l below would land on 2. | |
| 232 | 230 | input <- finger(-50, 5) | |
| 233 | - | input <- ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Stop: true}} | |
| 231 | + | input <- ports.SwipeEnd{} | |
| 232 | + | input <- ports.SwipeBegin{Fingers: 3} | |
| 234 | 233 | input <- finger(-50, 5) | |
| 235 | 234 | input <- finger(-50, 0) | |
| 236 | 235 | input <- finger(-100, 0) | |
| 237 | 236 | sceneMatch(t, scenes, focused(2)) | |
| 238 | - | input <- ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Stop: true}} | |
| 237 | + | input <- ports.SwipeEnd{} | |
| 238 | + | input <- ports.SwipeBegin{Fingers: 3} | |
| 239 | 239 | input <- ports.KeyEvent{Keysym: "l", Pressed: true} | |
| 240 | 240 | sceneMatch(t, scenes, focused(3)) | |
| 241 | 241 | input <- ports.KeyEvent{Keysym: "l"} | |
| 242 | 242 | input <- finger(-60, 0) | |
| 243 | 243 | sceneMatch(t, scenes, focused(2)) | |
| 244 | - | input <- ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Stop: true}} | |
| 244 | + | input <- ports.SwipeEnd{} | |
| 245 | + | input <- ports.SwipeBegin{Fingers: 3} | |
| 245 | 246 | // A wheel notch down has nowhere to go (no workspace below with | |
| 246 | 247 | // windows); a finger scroll left moves on. | |
| 247 | 248 | input <- ports.PointerAxis{Source: ports.AxisWheel, Vertical: ports.ScrollAxis{Set: true, Value: 15, V120: 120}} | |
| @@ -186,11 +186,8 @@ func TestFloatOnlyMonitorDirectionalExit(t *testing.T) { | |||||
| 186 | } | 186 | } | ||
| 187 | } | 187 | } | ||
| 188 | 188 | ||||
| 189 | - | // Two-finger scrolling moves the overview selection one column per step, | 189 | + | // Three-finger navigation changes selection during movement and resets on lift. |
| 190 | - | // along the axis the fingers move most; a small scroll does nothing and | 190 | + | func TestOverviewThreeFingerNavigation(t *testing.T) { |
| 191 | - | // lifting the fingers starts over. Outside the overview it goes to the | |||
| 192 | - | // window. | |||
| 193 | - | func TestOverviewTwoFingerScroll(t *testing.T) { | |||
| 194 | cfg := config.Defaults() | 191 | cfg := config.Defaults() | ||
| 195 | client := make(chan ports.ClientEvent, 8) | 192 | client := make(chan ports.ClientEvent, 8) | ||
| 196 | input := make(chan ports.InputEvent, 8) | 193 | input := make(chan ports.InputEvent, 8) | ||
| @@ -223,25 +220,29 @@ func TestOverviewTwoFingerScroll(t *testing.T) { | |||||
| 223 | input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper, Pressed: true} | 220 | input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper, Pressed: true} | ||
| 224 | sceneMatch(t, scenes, focused(3)) | 221 | sceneMatch(t, scenes, focused(3)) | ||
| 225 | input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper} | 222 | input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper} | ||
| 226 | - | finger := func(dx, dy float64) ports.PointerAxis { | 223 | + | finger := func(dx, dy float64) ports.SwipeUpdate { |
| 227 | - | return ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Value: dx}, Vertical: ports.ScrollAxis{Set: true, Value: dy}} | 224 | + | return ports.SwipeUpdate{DX: dx, DY: dy} |
| 228 | } | 225 | } | ||
| 226 | + | input <- ports.SwipeBegin{Fingers: 3} | |||
| 229 | // 50 left, lifted, then 50, 50 and 100 left: one step (to 2), the | 227 | // 50 left, lifted, then 50, 50 and 100 left: one step (to 2), the | ||
| 230 | // next ones are longer. Without the reset it would be two (to 1), and | 228 | // next ones are longer. Without the reset it would be two (to 1), and | ||
| 231 | // the l below would land on 2. | 229 | // the l below would land on 2. | ||
| 232 | input <- finger(-50, 5) | 230 | input <- finger(-50, 5) | ||
| 233 | - | input <- ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Stop: true}} | 231 | + | input <- ports.SwipeEnd{} |
| 232 | + | input <- ports.SwipeBegin{Fingers: 3} | |||
| 234 | input <- finger(-50, 5) | 233 | input <- finger(-50, 5) | ||
| 235 | input <- finger(-50, 0) | 234 | input <- finger(-50, 0) | ||
| 236 | input <- finger(-100, 0) | 235 | input <- finger(-100, 0) | ||
| 237 | sceneMatch(t, scenes, focused(2)) | 236 | sceneMatch(t, scenes, focused(2)) | ||
| 238 | - | input <- ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Stop: true}} | 237 | + | input <- ports.SwipeEnd{} |
| 238 | + | input <- ports.SwipeBegin{Fingers: 3} | |||
| 239 | input <- ports.KeyEvent{Keysym: "l", Pressed: true} | 239 | input <- ports.KeyEvent{Keysym: "l", Pressed: true} | ||
| 240 | sceneMatch(t, scenes, focused(3)) | 240 | sceneMatch(t, scenes, focused(3)) | ||
| 241 | input <- ports.KeyEvent{Keysym: "l"} | 241 | input <- ports.KeyEvent{Keysym: "l"} | ||
| 242 | input <- finger(-60, 0) | 242 | input <- finger(-60, 0) | ||
| 243 | sceneMatch(t, scenes, focused(2)) | 243 | sceneMatch(t, scenes, focused(2)) | ||
| 244 | - | input <- ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Stop: true}} | 244 | + | input <- ports.SwipeEnd{} |
| 245 | + | input <- ports.SwipeBegin{Fingers: 3} | |||
| 245 | // A wheel notch down has nowhere to go (no workspace below with | 246 | // A wheel notch down has nowhere to go (no workspace below with | ||
| 246 | // windows); a finger scroll left moves on. | 247 | // windows); a finger scroll left moves on. | ||
| 247 | input <- ports.PointerAxis{Source: ports.AxisWheel, Vertical: ports.ScrollAxis{Set: true, Value: 15, V120: 120}} | 248 | input <- ports.PointerAxis{Source: ports.AxisWheel, Vertical: ports.ScrollAxis{Set: true, Value: 15, V120: 120}} | ||
M internal/core/overview_stash_input_test.go +3 -13
| @@ -9,7 +9,7 @@ import ( | |||
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | 11 | func TestOverviewStashInputNavigation(t *testing.T) { | |
| 12 | - | for _, input := range []string{"swipe", "finger", "wheel", "bind"} { | |
| 12 | + | for _, input := range []string{"swipe", "wheel", "bind"} { | |
| 13 | 13 | t.Run(input, func(t *testing.T) { | |
| 14 | 14 | c, _, sc, _ := stashRig(t, true) | |
| 15 | 15 | m := sc.mon | |
| @@ -28,12 +28,8 @@ func TestOverviewStashInputNavigation(t *testing.T) { | |||
| 28 | 28 | c.swipeUpdate(ports.SwipeUpdate{DX: float64(dx) * 40, DY: float64(dy) * 40, Time: at}) | |
| 29 | 29 | } | |
| 30 | 30 | c.swipeEnd(ports.SwipeEnd{Cancelled: cancelled, Time: at}) | |
| 31 | - | case "finger", "wheel": | |
| 32 | - | source := ports.AxisFinger | |
| 33 | - | if input == "wheel" { | |
| 34 | - | source = ports.AxisWheel | |
| 35 | - | } | |
| 36 | - | m.overviewScroll(ports.PointerAxis{Source: source, | |
| 31 | + | case "wheel": | |
| 32 | + | m.overviewScroll(ports.PointerAxis{Source: ports.AxisWheel, | |
| 37 | 33 | Horizontal: ports.ScrollAxis{Set: dx != 0, Value: float64(dx) * 70, V120: int32(dx * 120)}, | |
| 38 | 34 | Vertical: ports.ScrollAxis{Set: dy != 0, Value: float64(dy) * 70, V120: int32(dy * 120)}}) | |
| 39 | 35 | c.scrollStop(ports.PointerAxis{Vertical: ports.ScrollAxis{Stop: true}}) | |
| @@ -47,12 +43,6 @@ func TestOverviewStashInputNavigation(t *testing.T) { | |||
| 47 | 43 | m.overviewFocus(a) | |
| 48 | 44 | } | |
| 49 | 45 | } | |
| 50 | - | if input == "swipe" { | |
| 51 | - | move(0, -1, true) | |
| 52 | - | if m.cardAt(w) != 2 { | |
| 53 | - | t.Fatal("cancelled swipe changed selection") | |
| 54 | - | } | |
| 55 | - | } | |
| 56 | 46 | move(0, -1, false) | |
| 57 | 47 | if m.cardAt(w) != 1 { | |
| 58 | 48 | t.Fatalf("up selected card %d, want 1", m.cardAt(w)) | |
| @@ -9,7 +9,7 @@ import ( | |||||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | func TestOverviewStashInputNavigation(t *testing.T) { | 11 | func TestOverviewStashInputNavigation(t *testing.T) { | ||
| 12 | - | for _, input := range []string{"swipe", "finger", "wheel", "bind"} { | 12 | + | for _, input := range []string{"swipe", "wheel", "bind"} { |
| 13 | t.Run(input, func(t *testing.T) { | 13 | t.Run(input, func(t *testing.T) { | ||
| 14 | c, _, sc, _ := stashRig(t, true) | 14 | c, _, sc, _ := stashRig(t, true) | ||
| 15 | m := sc.mon | 15 | m := sc.mon | ||
| @@ -28,12 +28,8 @@ func TestOverviewStashInputNavigation(t *testing.T) { | |||||
| 28 | c.swipeUpdate(ports.SwipeUpdate{DX: float64(dx) * 40, DY: float64(dy) * 40, Time: at}) | 28 | c.swipeUpdate(ports.SwipeUpdate{DX: float64(dx) * 40, DY: float64(dy) * 40, Time: at}) | ||
| 29 | } | 29 | } | ||
| 30 | c.swipeEnd(ports.SwipeEnd{Cancelled: cancelled, Time: at}) | 30 | c.swipeEnd(ports.SwipeEnd{Cancelled: cancelled, Time: at}) | ||
| 31 | - | case "finger", "wheel": | 31 | + | case "wheel": |
| 32 | - | source := ports.AxisFinger | 32 | + | m.overviewScroll(ports.PointerAxis{Source: ports.AxisWheel, |
| 33 | - | if input == "wheel" { | |||
| 34 | - | source = ports.AxisWheel | |||
| 35 | - | } | |||
| 36 | - | m.overviewScroll(ports.PointerAxis{Source: source, | |||
| 37 | Horizontal: ports.ScrollAxis{Set: dx != 0, Value: float64(dx) * 70, V120: int32(dx * 120)}, | 33 | Horizontal: ports.ScrollAxis{Set: dx != 0, Value: float64(dx) * 70, V120: int32(dx * 120)}, | ||
| 38 | Vertical: ports.ScrollAxis{Set: dy != 0, Value: float64(dy) * 70, V120: int32(dy * 120)}}) | 34 | Vertical: ports.ScrollAxis{Set: dy != 0, Value: float64(dy) * 70, V120: int32(dy * 120)}}) | ||
| 39 | c.scrollStop(ports.PointerAxis{Vertical: ports.ScrollAxis{Stop: true}}) | 35 | c.scrollStop(ports.PointerAxis{Vertical: ports.ScrollAxis{Stop: true}}) | ||
| @@ -47,12 +43,6 @@ func TestOverviewStashInputNavigation(t *testing.T) { | |||||
| 47 | m.overviewFocus(a) | 43 | m.overviewFocus(a) | ||
| 48 | } | 44 | } | ||
| 49 | } | 45 | } | ||
| 50 | - | if input == "swipe" { | |||
| 51 | - | move(0, -1, true) | |||
| 52 | - | if m.cardAt(w) != 2 { | |||
| 53 | - | t.Fatal("cancelled swipe changed selection") | |||
| 54 | - | } | |||
| 55 | - | } | |||
| 56 | move(0, -1, false) | 46 | move(0, -1, false) | ||
| 57 | if m.cardAt(w) != 1 { | 47 | if m.cardAt(w) != 1 { | ||
| 58 | t.Fatalf("up selected card %d, want 1", m.cardAt(w)) | 48 | t.Fatalf("up selected card %d, want 1", m.cardAt(w)) | ||
A internal/core/overview_touchpad_internal_test.go +50 -0
| @@ -0,0 +1,50 @@ | |||
| 1 | + | package core | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "testing" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/neferwl/internal/ports" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func TestOverviewTwoFingerDoesNotNavigate(t *testing.T) { | |
| 11 | + | c, _, sc, _ := stashRig(t, true) | |
| 12 | + | m := sc.mon | |
| 13 | + | m.ToggleOverview() | |
| 14 | + | w, card := m.Current(), m.cardAt(m.Current()) | |
| 15 | + | for _, axis := range []ports.PointerAxis{ | |
| 16 | + | {Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Value: 1000}}, | |
| 17 | + | {Source: ports.AxisFinger, Vertical: ports.ScrollAxis{Set: true, Value: 1000}}, | |
| 18 | + | {Source: ports.AxisFinger, Vertical: ports.ScrollAxis{Stop: true}}, | |
| 19 | + | } { | |
| 20 | + | if err := c.handleInput(context.Background(), axis); err != nil { | |
| 21 | + | t.Fatal(err) | |
| 22 | + | } | |
| 23 | + | } | |
| 24 | + | if m.Current() != w || m.cardAt(w) != card { | |
| 25 | + | t.Fatal("two-finger scroll navigated the overview") | |
| 26 | + | } | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | func TestOverviewSwipeCancellationResetsDistance(t *testing.T) { | |
| 30 | + | c, _, sc, _ := stashRig(t, true) | |
| 31 | + | m := sc.mon | |
| 32 | + | m.ToggleOverview() | |
| 33 | + | w := m.Current() | |
| 34 | + | c.swipeBegin(ports.SwipeBegin{Fingers: 3}) | |
| 35 | + | c.swipeUpdate(ports.SwipeUpdate{DY: -40}) | |
| 36 | + | c.swipeEnd(ports.SwipeEnd{Cancelled: true}) | |
| 37 | + | c.swipeBegin(ports.SwipeBegin{Fingers: 3}) | |
| 38 | + | c.swipeUpdate(ports.SwipeUpdate{DY: -40}) | |
| 39 | + | if m.cardAt(w) != 2 { | |
| 40 | + | t.Fatal("cancelled gesture left accumulated distance") | |
| 41 | + | } | |
| 42 | + | c.swipeUpdate(ports.SwipeUpdate{DY: -30}) | |
| 43 | + | if m.cardAt(w) != 1 { | |
| 44 | + | t.Fatal("selection did not change during movement") | |
| 45 | + | } | |
| 46 | + | c.swipeEnd(ports.SwipeEnd{Cancelled: true}) | |
| 47 | + | if m.cardAt(w) != 1 { | |
| 48 | + | t.Fatal("cancellation undid an already selected card") | |
| 49 | + | } | |
| 50 | + | } | |
| @@ -0,0 +1,50 @@ | |||||
| 1 | + | package core | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "testing" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func TestOverviewTwoFingerDoesNotNavigate(t *testing.T) { | |||
| 11 | + | c, _, sc, _ := stashRig(t, true) | |||
| 12 | + | m := sc.mon | |||
| 13 | + | m.ToggleOverview() | |||
| 14 | + | w, card := m.Current(), m.cardAt(m.Current()) | |||
| 15 | + | for _, axis := range []ports.PointerAxis{ | |||
| 16 | + | {Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Value: 1000}}, | |||
| 17 | + | {Source: ports.AxisFinger, Vertical: ports.ScrollAxis{Set: true, Value: 1000}}, | |||
| 18 | + | {Source: ports.AxisFinger, Vertical: ports.ScrollAxis{Stop: true}}, | |||
| 19 | + | } { | |||
| 20 | + | if err := c.handleInput(context.Background(), axis); err != nil { | |||
| 21 | + | t.Fatal(err) | |||
| 22 | + | } | |||
| 23 | + | } | |||
| 24 | + | if m.Current() != w || m.cardAt(w) != card { | |||
| 25 | + | t.Fatal("two-finger scroll navigated the overview") | |||
| 26 | + | } | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | func TestOverviewSwipeCancellationResetsDistance(t *testing.T) { | |||
| 30 | + | c, _, sc, _ := stashRig(t, true) | |||
| 31 | + | m := sc.mon | |||
| 32 | + | m.ToggleOverview() | |||
| 33 | + | w := m.Current() | |||
| 34 | + | c.swipeBegin(ports.SwipeBegin{Fingers: 3}) | |||
| 35 | + | c.swipeUpdate(ports.SwipeUpdate{DY: -40}) | |||
| 36 | + | c.swipeEnd(ports.SwipeEnd{Cancelled: true}) | |||
| 37 | + | c.swipeBegin(ports.SwipeBegin{Fingers: 3}) | |||
| 38 | + | c.swipeUpdate(ports.SwipeUpdate{DY: -40}) | |||
| 39 | + | if m.cardAt(w) != 2 { | |||
| 40 | + | t.Fatal("cancelled gesture left accumulated distance") | |||
| 41 | + | } | |||
| 42 | + | c.swipeUpdate(ports.SwipeUpdate{DY: -30}) | |||
| 43 | + | if m.cardAt(w) != 1 { | |||
| 44 | + | t.Fatal("selection did not change during movement") | |||
| 45 | + | } | |||
| 46 | + | c.swipeEnd(ports.SwipeEnd{Cancelled: true}) | |||
| 47 | + | if m.cardAt(w) != 1 { | |||
| 48 | + | t.Fatal("cancellation undid an already selected card") | |||
| 49 | + | } | |||
| 50 | + | } | |||
M internal/core/swipe_test.go +6 -3
| @@ -601,6 +601,7 @@ func TestSwipeInOverview(t *testing.T) { | |||
| 601 | 601 | r.at += 8 * time.Millisecond | |
| 602 | 602 | r.input <- ports.SwipeUpdate{DX: -40, Time: r.at} | |
| 603 | 603 | } | |
| 604 | + | sceneMatch(t, r.scenes, func(s ports.Scene) bool { return selected(s) == 2 }) | |
| 604 | 605 | s := r.end(t, false) | |
| 605 | 606 | if id := selected(s); id != 2 { | |
| 606 | 607 | t.Fatalf("selected %d after a left swipe, want 2", id) | |
| @@ -945,16 +946,18 @@ func TestOverviewScrollStopOnOtherOutput(t *testing.T) { | |||
| 945 | 946 | on := func(id ports.WindowID) func(ports.Scene) bool { | |
| 946 | 947 | return func(s ports.Scene) bool { return s.Output == wide.Name && selected(s) == id } | |
| 947 | 948 | } | |
| 948 | - | finger := func(dx float64) ports.PointerAxis { | |
| 949 | - | return ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Value: dx}} | |
| 949 | + | finger := func(dx float64) ports.SwipeUpdate { | |
| 950 | + | return ports.SwipeUpdate{DX: dx} | |
| 950 | 951 | } | |
| 951 | 952 | r.input <- ports.PointerMotion{X: 100, Y: 300} | |
| 953 | + | r.input <- ports.SwipeBegin{Fingers: 3} | |
| 952 | 954 | r.input <- finger(-70) | |
| 953 | 955 | sceneMatch(t, r.scenes, on(2)) | |
| 954 | 956 | // The pointer crosses to DP-2 and the fingers lift there. | |
| 955 | 957 | r.input <- ports.PointerMotion{X: 1000, Y: 300} | |
| 956 | - | r.input <- ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Stop: true}} | |
| 958 | + | r.input <- ports.SwipeEnd{} | |
| 957 | 959 | r.input <- ports.PointerMotion{X: 100, Y: 300} | |
| 960 | + | r.input <- ports.SwipeBegin{Fingers: 3} | |
| 958 | 961 | r.input <- finger(-70) | |
| 959 | 962 | sceneMatch(t, r.scenes, on(1)) | |
| 960 | 963 | } | |
| @@ -601,6 +601,7 @@ func TestSwipeInOverview(t *testing.T) { | |||||
| 601 | r.at += 8 * time.Millisecond | 601 | r.at += 8 * time.Millisecond | ||
| 602 | r.input <- ports.SwipeUpdate{DX: -40, Time: r.at} | 602 | r.input <- ports.SwipeUpdate{DX: -40, Time: r.at} | ||
| 603 | } | 603 | } | ||
| 604 | + | sceneMatch(t, r.scenes, func(s ports.Scene) bool { return selected(s) == 2 }) | |||
| 604 | s := r.end(t, false) | 605 | s := r.end(t, false) | ||
| 605 | if id := selected(s); id != 2 { | 606 | if id := selected(s); id != 2 { | ||
| 606 | t.Fatalf("selected %d after a left swipe, want 2", id) | 607 | t.Fatalf("selected %d after a left swipe, want 2", id) | ||
| @@ -945,16 +946,18 @@ func TestOverviewScrollStopOnOtherOutput(t *testing.T) { | |||||
| 945 | on := func(id ports.WindowID) func(ports.Scene) bool { | 946 | on := func(id ports.WindowID) func(ports.Scene) bool { | ||
| 946 | return func(s ports.Scene) bool { return s.Output == wide.Name && selected(s) == id } | 947 | return func(s ports.Scene) bool { return s.Output == wide.Name && selected(s) == id } | ||
| 947 | } | 948 | } | ||
| 948 | - | finger := func(dx float64) ports.PointerAxis { | 949 | + | finger := func(dx float64) ports.SwipeUpdate { |
| 949 | - | return ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Value: dx}} | 950 | + | return ports.SwipeUpdate{DX: dx} |
| 950 | } | 951 | } | ||
| 951 | r.input <- ports.PointerMotion{X: 100, Y: 300} | 952 | r.input <- ports.PointerMotion{X: 100, Y: 300} | ||
| 953 | + | r.input <- ports.SwipeBegin{Fingers: 3} | |||
| 952 | r.input <- finger(-70) | 954 | r.input <- finger(-70) | ||
| 953 | sceneMatch(t, r.scenes, on(2)) | 955 | sceneMatch(t, r.scenes, on(2)) | ||
| 954 | // The pointer crosses to DP-2 and the fingers lift there. | 956 | // The pointer crosses to DP-2 and the fingers lift there. | ||
| 955 | r.input <- ports.PointerMotion{X: 1000, Y: 300} | 957 | r.input <- ports.PointerMotion{X: 1000, Y: 300} | ||
| 956 | - | r.input <- ports.PointerAxis{Source: ports.AxisFinger, Horizontal: ports.ScrollAxis{Set: true, Stop: true}} | 958 | + | r.input <- ports.SwipeEnd{} |
| 957 | r.input <- ports.PointerMotion{X: 100, Y: 300} | 959 | r.input <- ports.PointerMotion{X: 100, Y: 300} | ||
| 960 | + | r.input <- ports.SwipeBegin{Fingers: 3} | |||
| 958 | r.input <- finger(-70) | 961 | r.input <- finger(-70) | ||
| 959 | sceneMatch(t, r.scenes, on(1)) | 962 | sceneMatch(t, r.scenes, on(1)) | ||
| 960 | } | 963 | } | ||