detached 9376c1c

fix(overview): let three-finger swipes select cards behind a maximized cascade column

commit
9376c1c480308894c7484b74ebec02189aa4aed8
author
brice · · 2026-10-07 15:23 UTC
parent
4a1ef6d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +49 -3
M docs/config.md +1 -1
@@ -198,7 +198,7 @@ Three-finger vertical swipes scroll bands; horizontal swipes switch workspaces.
198198
199199 In the [overview](#overview), the selected band is centred at the usual overview zoom, whatever the band count. The other bands of the workspace sit above and below it, dimmed, and may run off screen. Neighbor workspaces sit left and right, fully dimmed, with their selected row level with the centred band. The stash pile stays left of the band, inside the workspace's slot. A maximized column shows the columns it hides as dimmed cards behind it, peeking out on their side of the band: the nearest one on each side, then the next ones stacked at the band's edge. Selecting a card (left/right, Return or a click on a visible card) moves the maximization to it, and Escape gives it back.
200200
201-In the overview, up/down move to the previous or next band and stop at the first and last; with a stash card selected they browse the pile. Left/right move along the band; past its first column they enter the stash, or go to the previous workspace when there is none; past its last column they go to the next workspace (with the overview closed, focus crosses into the next band instead). Covering floats fan left and right of the band: left/right traverse them before entering the stash or changing workspace, and up/down do nothing on a float. `h`/`j`/`k`/`l` and the focus binds follow the arrows; `focus-workspace-prev/next` always change workspace, except over a selected stash card, where they browse the pile. Three-finger swipes follow the fingers: vertical moves one band (or one stash card) and horizontal one workspace per gesture, even over a stash card. A vertical wheel or continuous scroll changes workspace, except over a selected stash card, where it browses the pile; a horizontal one moves along the band.
201+In the overview, up/down move to the previous or next band and stop at the first and last; with a stash card selected they browse the pile. Left/right move along the band; past its first column they enter the stash, or go to the previous workspace when there is none; past its last column they go to the next workspace (with the overview closed, focus crosses into the next band instead). Covering floats fan left and right of the band: left/right traverse them before entering the stash or changing workspace, and up/down do nothing on a float. `h`/`j`/`k`/`l` and the focus binds follow the arrows; `focus-workspace-prev/next` always change workspace, except over a selected stash card, where they browse the pile. Three-finger swipes follow the fingers: vertical moves one band (or one stash card) and horizontal one workspace per gesture, even over a stash card. Over a maximized column that hides cards, horizontal moves one step per gesture like left/right: the next card, then past the band's edge the stash or a workspace. A vertical wheel or continuous scroll changes workspace, except over a selected stash card, where it browses the pile; a horizontal one moves along the band.
202202
203203 Cascade supports dropping onto tiles, but not gap insertion or edge scrolling during a drag.
204204
@@ -198,7 +198,7 @@ Three-finger vertical swipes scroll bands; horizontal swipes switch workspaces.
198 198
199 In the [overview](#overview), the selected band is centred at the usual overview zoom, whatever the band count. The other bands of the workspace sit above and below it, dimmed, and may run off screen. Neighbor workspaces sit left and right, fully dimmed, with their selected row level with the centred band. The stash pile stays left of the band, inside the workspace's slot. A maximized column shows the columns it hides as dimmed cards behind it, peeking out on their side of the band: the nearest one on each side, then the next ones stacked at the band's edge. Selecting a card (left/right, Return or a click on a visible card) moves the maximization to it, and Escape gives it back.199 In the [overview](#overview), the selected band is centred at the usual overview zoom, whatever the band count. The other bands of the workspace sit above and below it, dimmed, and may run off screen. Neighbor workspaces sit left and right, fully dimmed, with their selected row level with the centred band. The stash pile stays left of the band, inside the workspace's slot. A maximized column shows the columns it hides as dimmed cards behind it, peeking out on their side of the band: the nearest one on each side, then the next ones stacked at the band's edge. Selecting a card (left/right, Return or a click on a visible card) moves the maximization to it, and Escape gives it back.
200 200
201-In the overview, up/down move to the previous or next band and stop at the first and last; with a stash card selected they browse the pile. Left/right move along the band; past its first column they enter the stash, or go to the previous workspace when there is none; past its last column they go to the next workspace (with the overview closed, focus crosses into the next band instead). Covering floats fan left and right of the band: left/right traverse them before entering the stash or changing workspace, and up/down do nothing on a float. `h`/`j`/`k`/`l` and the focus binds follow the arrows; `focus-workspace-prev/next` always change workspace, except over a selected stash card, where they browse the pile. Three-finger swipes follow the fingers: vertical moves one band (or one stash card) and horizontal one workspace per gesture, even over a stash card. A vertical wheel or continuous scroll changes workspace, except over a selected stash card, where it browses the pile; a horizontal one moves along the band.201+In the overview, up/down move to the previous or next band and stop at the first and last; with a stash card selected they browse the pile. Left/right move along the band; past its first column they enter the stash, or go to the previous workspace when there is none; past its last column they go to the next workspace (with the overview closed, focus crosses into the next band instead). Covering floats fan left and right of the band: left/right traverse them before entering the stash or changing workspace, and up/down do nothing on a float. `h`/`j`/`k`/`l` and the focus binds follow the arrows; `focus-workspace-prev/next` always change workspace, except over a selected stash card, where they browse the pile. Three-finger swipes follow the fingers: vertical moves one band (or one stash card) and horizontal one workspace per gesture, even over a stash card. Over a maximized column that hides cards, horizontal moves one step per gesture like left/right: the next card, then past the band's edge the stash or a workspace. A vertical wheel or continuous scroll changes workspace, except over a selected stash card, where it browses the pile; a horizontal one moves along the band.
202 202
203 Cascade supports dropping onto tiles, but not gap insertion or edge scrolling during a drag.203 Cascade supports dropping onto tiles, but not gap insertion or edge scrolling during a drag.
204 204
M internal/core/overview.go +18 -2
@@ -1346,8 +1346,14 @@ func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) {
13461346 case math.Abs(m.ov.scrollX) >= stepX:
13471347 d := sign(m.ov.scrollX)
13481348 if cascade && finger {
1349- // One workspace per gesture, even over a stash card.
1350- m.overviewStep(d)
1349+ // One step per gesture: a card behind a maximized
1350+ // column first, like the arrows, else one workspace,
1351+ // even over a stash card.
1352+ if m.bandCardsAt(m.Current()) {
1353+ m.OverviewMove(d, 0)
1354+ } else {
1355+ m.overviewStep(d)
1356+ }
13511357 m.ov.scrollX, m.ov.scrollY, m.ov.scrolled = 0, 0, true
13521358 return true
13531359 }
@@ -1367,6 +1373,16 @@ func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) {
13671373 }
13681374 }
13691375
1376+// bandCardsAt reports whether the selection of w is a maximized cascade
1377+// column hiding cards of its band, not a stash card.
1378+func (m *Monitor) bandCardsAt(w *Workspace) bool {
1379+ if m.cardAt(w) >= 0 || len(w.Columns) == 0 || !w.Columns[w.Focus].FullWidth {
1380+ return false
1381+ }
1382+ first, last := w.laneBounds(w.laneOf(w.Focus))
1383+ return first < last
1384+}
1385+
13701386 // overviewTurned reports whether a scroll step turned the overview from the
13711387 // axis before. A finger scroll then waits for the fingers to lift, so it
13721388 // cannot bounce back.
@@ -1346,8 +1346,14 @@ func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) {
1346 case math.Abs(m.ov.scrollX) >= stepX:1346 case math.Abs(m.ov.scrollX) >= stepX:
1347 d := sign(m.ov.scrollX)1347 d := sign(m.ov.scrollX)
1348 if cascade && finger {1348 if cascade && finger {
1349- // One workspace per gesture, even over a stash card.1349+ // One step per gesture: a card behind a maximized
1350- m.overviewStep(d)1350+ // column first, like the arrows, else one workspace,
1351+ // even over a stash card.
1352+ if m.bandCardsAt(m.Current()) {
1353+ m.OverviewMove(d, 0)
1354+ } else {
1355+ m.overviewStep(d)
1356+ }
1351 m.ov.scrollX, m.ov.scrollY, m.ov.scrolled = 0, 0, true1357 m.ov.scrollX, m.ov.scrollY, m.ov.scrolled = 0, 0, true
1352 return true1358 return true
1353 }1359 }
@@ -1367,6 +1373,16 @@ func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) {
1367 }1373 }
1368 }1374 }
1369 1375
1376+// bandCardsAt reports whether the selection of w is a maximized cascade
1377+// column hiding cards of its band, not a stash card.
1378+func (m *Monitor) bandCardsAt(w *Workspace) bool {
1379+ if m.cardAt(w) >= 0 || len(w.Columns) == 0 || !w.Columns[w.Focus].FullWidth {
1380+ return false
1381+ }
1382+ first, last := w.laneBounds(w.laneOf(w.Focus))
1383+ return first < last
1384+}
1385+
1370 // overviewTurned reports whether a scroll step turned the overview from the1386 // overviewTurned reports whether a scroll step turned the overview from the
1371 // axis before. A finger scroll then waits for the fingers to lift, so it1387 // axis before. A finger scroll then waits for the fingers to lift, so it
1372 // cannot bounce back.1388 // cannot bounce back.
M internal/core/overview_cascade_test.go +30 -0
@@ -635,6 +635,36 @@ func TestOverviewCascadeThreeFinger(t *testing.T) {
635635 }
636636 }
637637
638+// Three fingers sideways walk the cards behind a maximized column one per
639+// gesture, like the arrows, then change workspace past the band's edge.
640+func TestOverviewCascadeThreeFingerMaximizedCards(t *testing.T) {
641+ c, m := cascadeCore(t)
642+ w := m.Current()
643+ w.ToggleFullWidth() // Column 2 hides 3, the other column of its band.
644+ m.ToggleOverview()
645+ swipe := func(dx float64) {
646+ t.Helper()
647+ c.swipeBegin(ports.SwipeBegin{Fingers: 3})
648+ for range 4 {
649+ c.swipeUpdate(ports.SwipeUpdate{DX: dx})
650+ }
651+ c.swipeEnd(ports.SwipeEnd{})
652+ }
653+ swipe(40)
654+ if m.Current() != w || m.overviewTarget() != 3 || !w.Columns[w.Focus].FullWidth {
655+ t.Fatalf("swipe right: workspace %d, target %d, want maximized 3", indexOf(m.Workspaces, m.Current()), m.overviewTarget())
656+ }
657+ swipe(-40)
658+ if m.Current() != w || m.overviewTarget() != 2 || !w.Columns[w.Focus].FullWidth {
659+ t.Fatalf("swipe left: workspace %d, target %d, want maximized 2", indexOf(m.Workspaces, m.Current()), m.overviewTarget())
660+ }
661+ swipe(40)
662+ swipe(40)
663+ if m.Current() != m.Workspaces[2] {
664+ t.Fatalf("swipe right past the band: workspace %d", indexOf(m.Workspaces, m.Current()))
665+ }
666+}
667+
638668 // T8: a named boundary of a cascade overview is a vertical rule strictly
639669 // between the two slots.
640670 func TestOverviewCascadeNamedDivider(t *testing.T) {
@@ -635,6 +635,36 @@ func TestOverviewCascadeThreeFinger(t *testing.T) {
635 }635 }
636 }636 }
637 637
638+// Three fingers sideways walk the cards behind a maximized column one per
639+// gesture, like the arrows, then change workspace past the band's edge.
640+func TestOverviewCascadeThreeFingerMaximizedCards(t *testing.T) {
641+ c, m := cascadeCore(t)
642+ w := m.Current()
643+ w.ToggleFullWidth() // Column 2 hides 3, the other column of its band.
644+ m.ToggleOverview()
645+ swipe := func(dx float64) {
646+ t.Helper()
647+ c.swipeBegin(ports.SwipeBegin{Fingers: 3})
648+ for range 4 {
649+ c.swipeUpdate(ports.SwipeUpdate{DX: dx})
650+ }
651+ c.swipeEnd(ports.SwipeEnd{})
652+ }
653+ swipe(40)
654+ if m.Current() != w || m.overviewTarget() != 3 || !w.Columns[w.Focus].FullWidth {
655+ t.Fatalf("swipe right: workspace %d, target %d, want maximized 3", indexOf(m.Workspaces, m.Current()), m.overviewTarget())
656+ }
657+ swipe(-40)
658+ if m.Current() != w || m.overviewTarget() != 2 || !w.Columns[w.Focus].FullWidth {
659+ t.Fatalf("swipe left: workspace %d, target %d, want maximized 2", indexOf(m.Workspaces, m.Current()), m.overviewTarget())
660+ }
661+ swipe(40)
662+ swipe(40)
663+ if m.Current() != m.Workspaces[2] {
664+ t.Fatalf("swipe right past the band: workspace %d", indexOf(m.Workspaces, m.Current()))
665+ }
666+}
667+
638 // T8: a named boundary of a cascade overview is a vertical rule strictly668 // T8: a named boundary of a cascade overview is a vertical rule strictly
639 // between the two slots.669 // between the two slots.
640 func TestOverviewCascadeNamedDivider(t *testing.T) {670 func TestOverviewCascadeNamedDivider(t *testing.T) {

Keyboard shortcuts

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

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

Branches 25

Tags 10

↑↓ moveenter openesc close