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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) | |||
| 1026 | 1026 | ||
| 1027 | 1027 | // previewRow scales the tiles of w into a row placed in slot, centred | |
| 1028 | 1028 | // 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 | |
| 1030 | 1030 | // workspaces) have no focus; neither has a row whose pile has it (lit | |
| 1031 | 1031 | // false). | |
| 1032 | 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 | 1148 | 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 | 1167 | // overviewClick closes the overview on the preview under the pointer, on | |
| 1152 | 1168 | // the output it shows on. picked is false when no preview is there. | |
| 1153 | 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 | 1178 | // The cards are hit where they are drawn: a click during the open or a | |
| 1163 | 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 | 1181 | if id == 0 { | |
| 1166 | 1182 | return false, nil | |
| 1167 | 1183 | } | |
| @@ -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, centred | 1027 | // 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 the | 1028 | // 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 |
| 1030 | // workspaces) have no focus; neither has a row whose pile has it (lit | 1030 | // 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 0 | 1148 | 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, on | 1167 | // 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 a | 1178 | // 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, nil | 1182 | return false, nil | ||
| 1167 | } | 1183 | } | ||
M internal/core/overview_cascade_test.go +24 -0
| @@ -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 | 565 | // The pile stays inside the area, down to a tiny output. | |
| 542 | 566 | func TestOverviewCascadeStashInsideArea(t *testing.T) { | |
| 543 | 567 | 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}} { | ||