detached 4183d39

fix(core): hit the overview stash pile before the row it covers

commit
4183d39fcbd37055d7369e185dac662dbd8dd6e3
author
brice · · 2026-10-14 19:58 UTC
parent
bafc122
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +42 -2
M internal/core/overview.go +18 -2
@@ -1026,7 +1026,7 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect)
10261026
10271027 // previewRow scales the tiles of w into a row placed in slot, centred
10281028 // in the slot's width, or scrolled so the focused column shows when the
1029-// row is wider, right of the stash pile. Dimmed rows (neighbor
1029+// row is wider (right of the stash pile in a vertical overview). Dimmed rows (neighbor
10301030 // workspaces) have no focus; neither has a row whose pile has it (lit
10311031 // false).
10321032 func (w *Workspace) previewRow(axis layoutAxis, slot Rect, dim, lit bool) []Placement {
@@ -1148,6 +1148,22 @@ func overviewIn(layout []Placement, x, y float64) WindowID {
11481148 return 0
11491149 }
11501150
1151+// overviewHit is overviewIn with the stash piles first: a pile is drawn
1152+// over its row (a horizontal overview), the cards behind its front one
1153+// included.
1154+func (m *Monitor) overviewHit(layout []Placement, x, y float64) WindowID {
1155+ var piles []Placement
1156+ for _, p := range layout {
1157+ if w, _ := m.find(p.ID); w != nil && w.stashIndex(p.ID) >= 0 {
1158+ piles = append(piles, p)
1159+ }
1160+ }
1161+ if id := overviewIn(piles, x, y); id != 0 {
1162+ return id
1163+ }
1164+ return overviewIn(layout, x, y)
1165+}
1166+
11511167 // overviewClick closes the overview on the preview under the pointer, on
11521168 // the output it shows on. picked is false when no preview is there.
11531169 func (c *Core) overviewClick(ctx context.Context) (picked bool, err error) {
@@ -1161,7 +1177,7 @@ func (c *Core) overviewClick(ctx context.Context) (picked bool, err error) {
11611177 }
11621178 // The cards are hit where they are drawn: a click during the open or a
11631179 // navigation animation picks what moves under the pointer.
1164- id := overviewIn(sc.shownLayout(), c.cursorX-float64(sc.x), c.cursorY-float64(sc.y))
1180+ id := sc.mon.overviewHit(sc.shownLayout(), c.cursorX-float64(sc.x), c.cursorY-float64(sc.y))
11651181 if id == 0 {
11661182 return false, nil
11671183 }
@@ -1026,7 +1026,7 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect)
1026 1026
1027 // previewRow scales the tiles of w into a row placed in slot, centred1027 // previewRow scales the tiles of w into a row placed in slot, centred
1028 // in the slot's width, or scrolled so the focused column shows when the1028 // in the slot's width, or scrolled so the focused column shows when the
1029-// row is wider, right of the stash pile. Dimmed rows (neighbor1029+// row is wider (right of the stash pile in a vertical overview). Dimmed rows (neighbor
1030 // workspaces) have no focus; neither has a row whose pile has it (lit1030 // workspaces) have no focus; neither has a row whose pile has it (lit
1031 // false).1031 // false).
1032 func (w *Workspace) previewRow(axis layoutAxis, slot Rect, dim, lit bool) []Placement {1032 func (w *Workspace) previewRow(axis layoutAxis, slot Rect, dim, lit bool) []Placement {
@@ -1148,6 +1148,22 @@ func overviewIn(layout []Placement, x, y float64) WindowID {
1148 return 01148 return 0
1149 }1149 }
1150 1150
1151+// overviewHit is overviewIn with the stash piles first: a pile is drawn
1152+// over its row (a horizontal overview), the cards behind its front one
1153+// included.
1154+func (m *Monitor) overviewHit(layout []Placement, x, y float64) WindowID {
1155+ var piles []Placement
1156+ for _, p := range layout {
1157+ if w, _ := m.find(p.ID); w != nil && w.stashIndex(p.ID) >= 0 {
1158+ piles = append(piles, p)
1159+ }
1160+ }
1161+ if id := overviewIn(piles, x, y); id != 0 {
1162+ return id
1163+ }
1164+ return overviewIn(layout, x, y)
1165+}
1166+
1151 // overviewClick closes the overview on the preview under the pointer, on1167 // overviewClick closes the overview on the preview under the pointer, on
1152 // the output it shows on. picked is false when no preview is there.1168 // the output it shows on. picked is false when no preview is there.
1153 func (c *Core) overviewClick(ctx context.Context) (picked bool, err error) {1169 func (c *Core) overviewClick(ctx context.Context) (picked bool, err error) {
@@ -1161,7 +1177,7 @@ func (c *Core) overviewClick(ctx context.Context) (picked bool, err error) {
1161 }1177 }
1162 // The cards are hit where they are drawn: a click during the open or a1178 // The cards are hit where they are drawn: a click during the open or a
1163 // navigation animation picks what moves under the pointer.1179 // navigation animation picks what moves under the pointer.
1164- id := overviewIn(sc.shownLayout(), c.cursorX-float64(sc.x), c.cursorY-float64(sc.y))1180+ id := sc.mon.overviewHit(sc.shownLayout(), c.cursorX-float64(sc.x), c.cursorY-float64(sc.y))
1165 if id == 0 {1181 if id == 0 {
1166 return false, nil1182 return false, nil
1167 }1183 }
M internal/core/overview_cascade_test.go +24 -0
@@ -538,6 +538,30 @@ func TestOverviewCascadeNamedDivider(t *testing.T) {
538538 }
539539 }
540540
541+// A card behind the front one of the pile is clicked where it shows,
542+// over the selected lane's tiles.
543+func TestOverviewCascadeStashPeekClick(t *testing.T) {
544+ m := cascadeOverview()
545+ withStash(m, 2)
546+ m.ToggleOverview()
547+ ps := m.Layout()
548+ front, back := previewOf(t, ps, 41).Rect, previewOf(t, ps, 40).Rect
549+ // The strip of 40 above the front card, inside the lane's tiles.
550+ x, y := float64(back.X+back.W-1), float64(back.Y)
551+ if back.Y >= front.Y || back.Y < previewOf(t, ps, 8).Rect.Y {
552+ t.Fatalf("setup: back %v front %v", back, front)
553+ }
554+ if id := overviewIn(ps, x, y); id == 40 {
555+ t.Fatal("setup: the tiles do not cover the strip")
556+ }
557+ if id := m.overviewHit(ps, x, y); id != 40 {
558+ t.Fatalf("clicked %d, want 40", id)
559+ }
560+ if id := m.overviewHit(ps, float64(front.X+front.W/2), float64(front.Y+front.H/2)); id != 41 {
561+ t.Fatalf("front card clicked %d, want 41", id)
562+ }
563+}
564+
541565 // The pile stays inside the area, down to a tiny output.
542566 func TestOverviewCascadeStashInsideArea(t *testing.T) {
543567 for _, size := range [][2]int{{300, 200}, {1920, 1080}, {1080, 1920}, {3440, 1440}, {60, 40}} {
@@ -538,6 +538,30 @@ func TestOverviewCascadeNamedDivider(t *testing.T) {
538 }538 }
539 }539 }
540 540
541+// A card behind the front one of the pile is clicked where it shows,
542+// over the selected lane's tiles.
543+func TestOverviewCascadeStashPeekClick(t *testing.T) {
544+ m := cascadeOverview()
545+ withStash(m, 2)
546+ m.ToggleOverview()
547+ ps := m.Layout()
548+ front, back := previewOf(t, ps, 41).Rect, previewOf(t, ps, 40).Rect
549+ // The strip of 40 above the front card, inside the lane's tiles.
550+ x, y := float64(back.X+back.W-1), float64(back.Y)
551+ if back.Y >= front.Y || back.Y < previewOf(t, ps, 8).Rect.Y {
552+ t.Fatalf("setup: back %v front %v", back, front)
553+ }
554+ if id := overviewIn(ps, x, y); id == 40 {
555+ t.Fatal("setup: the tiles do not cover the strip")
556+ }
557+ if id := m.overviewHit(ps, x, y); id != 40 {
558+ t.Fatalf("clicked %d, want 40", id)
559+ }
560+ if id := m.overviewHit(ps, float64(front.X+front.W/2), float64(front.Y+front.H/2)); id != 41 {
561+ t.Fatalf("front card clicked %d, want 41", id)
562+ }
563+}
564+
541 // The pile stays inside the area, down to a tiny output.565 // The pile stays inside the area, down to a tiny output.
542 func TestOverviewCascadeStashInsideArea(t *testing.T) {566 func TestOverviewCascadeStashInsideArea(t *testing.T) {
543 for _, size := range [][2]int{{300, 200}, {1920, 1080}, {1080, 1920}, {3440, 1440}, {60, 40}} {567 for _, size := range [][2]int{{300, 200}, {1920, 1080}, {1080, 1920}, {3440, 1440}, {60, 40}} {

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 26

Tags 10

↑↓ moveenter openesc close