fix(overview): show maximized cascade columns at full band width
- commit
- 61bb6e0bdd5033b239a222190c752ed979d0af65
- author
- brice · · 2026-10-07 07:23 UTC
- committer
- brice · · 2026-10-07 07:36 UTC
- parent
- d7a02c5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email - refs
- feat/overview-cascade
2 files · +45 -5
M internal/core/overview.go +9 -5
| @@ -970,14 +970,18 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||
| 970 | 970 | rects = w.colBuf | |
| 971 | 971 | span = w.Usable.W // Fixed overflow never scrolls, even with a stash pile. | |
| 972 | 972 | } | |
| 973 | - | maximized := w.Overflow == OverflowFixed && len(w.Columns) > 0 && w.Columns[w.Focus].FullWidth | |
| 973 | + | maximized := w.policy().equalCells && len(w.Columns) > 0 && w.Columns[w.Focus].FullWidth | |
| 974 | 974 | for i, c := range w.Columns { | |
| 975 | 975 | r := Rect{X: w.columnX(i) - w.Usable.X, Y: g, W: w.columnWidth(i), H: h} | |
| 976 | 976 | if w.policy().wraps { | |
| 977 | 977 | // The band is the lane: it is encoded in Y, one screen apart. | |
| 978 | + | // A maximized column fills its band as on screen. | |
| 978 | 979 | r.X = w.cellX(i) | |
| 979 | 980 | r.Y = g + w.band(i)*w.Usable.H | |
| 980 | 981 | r.W = w.cellWidth() | |
| 982 | + | if c.FullWidth { | |
| 983 | + | r.X, r.W = g, max(w.Usable.W-2*g, 0) | |
| 984 | + | } | |
| 981 | 985 | } else if w.Overflow == OverflowFixed { | |
| 982 | 986 | r = rects[i] | |
| 983 | 987 | r.X, r.Y = r.X-w.Usable.X, r.Y-w.Usable.Y | |
| @@ -985,10 +989,10 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||
| 985 | 989 | // A fullscreen column shows at the width it had in the row. | |
| 986 | 990 | r.W = max(w.Usable.W-2*g, 0) | |
| 987 | 991 | } | |
| 988 | - | if maximized && i != w.Focus { | |
| 989 | - | // FullWidth hides other columns on screen. Their old buffers | |
| 990 | - | // cannot fit their current spiral rects; show only the maximized | |
| 991 | - | // column until navigation restores the normal layout. | |
| 992 | + | if maximized && i != w.Focus && w.sameBand(i, w.Focus) { | |
| 993 | + | // FullWidth hides the other columns of its band on screen. | |
| 994 | + | // Their old buffers cannot fit their current rects; show only | |
| 995 | + | // the maximized column until navigation restores the layout. | |
| 992 | 996 | for _, id := range c.Windows { | |
| 993 | 997 | tiles = append(tiles, Placement{ID: id, Hidden: true}) | |
| 994 | 998 | } | |
| @@ -970,14 +970,18 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||||
| 970 | rects = w.colBuf | 970 | rects = w.colBuf | ||
| 971 | span = w.Usable.W // Fixed overflow never scrolls, even with a stash pile. | 971 | span = w.Usable.W // Fixed overflow never scrolls, even with a stash pile. | ||
| 972 | } | 972 | } | ||
| 973 | - | maximized := w.Overflow == OverflowFixed && len(w.Columns) > 0 && w.Columns[w.Focus].FullWidth | 973 | + | maximized := w.policy().equalCells && len(w.Columns) > 0 && w.Columns[w.Focus].FullWidth |
| 974 | for i, c := range w.Columns { | 974 | for i, c := range w.Columns { | ||
| 975 | r := Rect{X: w.columnX(i) - w.Usable.X, Y: g, W: w.columnWidth(i), H: h} | 975 | r := Rect{X: w.columnX(i) - w.Usable.X, Y: g, W: w.columnWidth(i), H: h} | ||
| 976 | if w.policy().wraps { | 976 | if w.policy().wraps { | ||
| 977 | // The band is the lane: it is encoded in Y, one screen apart. | 977 | // The band is the lane: it is encoded in Y, one screen apart. | ||
| 978 | + | // A maximized column fills its band as on screen. | |||
| 978 | r.X = w.cellX(i) | 979 | r.X = w.cellX(i) | ||
| 979 | r.Y = g + w.band(i)*w.Usable.H | 980 | r.Y = g + w.band(i)*w.Usable.H | ||
| 980 | r.W = w.cellWidth() | 981 | r.W = w.cellWidth() | ||
| 982 | + | if c.FullWidth { | |||
| 983 | + | r.X, r.W = g, max(w.Usable.W-2*g, 0) | |||
| 984 | + | } | |||
| 981 | } else if w.Overflow == OverflowFixed { | 985 | } else if w.Overflow == OverflowFixed { | ||
| 982 | r = rects[i] | 986 | r = rects[i] | ||
| 983 | r.X, r.Y = r.X-w.Usable.X, r.Y-w.Usable.Y | 987 | r.X, r.Y = r.X-w.Usable.X, r.Y-w.Usable.Y | ||
| @@ -985,10 +989,10 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||||
| 985 | // A fullscreen column shows at the width it had in the row. | 989 | // A fullscreen column shows at the width it had in the row. | ||
| 986 | r.W = max(w.Usable.W-2*g, 0) | 990 | r.W = max(w.Usable.W-2*g, 0) | ||
| 987 | } | 991 | } | ||
| 988 | - | if maximized && i != w.Focus { | 992 | + | if maximized && i != w.Focus && w.sameBand(i, w.Focus) { |
| 989 | - | // FullWidth hides other columns on screen. Their old buffers | 993 | + | // FullWidth hides the other columns of its band on screen. |
| 990 | - | // cannot fit their current spiral rects; show only the maximized | 994 | + | // Their old buffers cannot fit their current rects; show only |
| 991 | - | // column until navigation restores the normal layout. | 995 | + | // the maximized column until navigation restores the layout. |
| 992 | for _, id := range c.Windows { | 996 | for _, id := range c.Windows { | ||
| 993 | tiles = append(tiles, Placement{ID: id, Hidden: true}) | 997 | tiles = append(tiles, Placement{ID: id, Hidden: true}) | ||
| 994 | } | 998 | } | ||
M internal/core/overview_cascade_test.go +36 -0
| @@ -75,6 +75,42 @@ func TestOverviewCascadeZoomIgnoresBandCount(t *testing.T) { | |||
| 75 | 75 | } | |
| 76 | 76 | } | |
| 77 | 77 | ||
| 78 | + | // A maximized column fills its band in the preview as on screen, and hides | |
| 79 | + | // only the other columns of its band. | |
| 80 | + | func TestOverviewCascadeMaximizedColumnFillsBand(t *testing.T) { | |
| 81 | + | // Four columns, three per band: band 0 holds 0..2, band 1 holds 3. | |
| 82 | + | for _, tc := range []struct { | |
| 83 | + | n, max int | |
| 84 | + | hidden []bool | |
| 85 | + | }{ | |
| 86 | + | {n: 1, max: 0, hidden: []bool{false}}, | |
| 87 | + | {n: 4, max: 0, hidden: []bool{false, true, true, false}}, | |
| 88 | + | {n: 4, max: 3, hidden: []bool{false, false, false, false}}, | |
| 89 | + | } { | |
| 90 | + | w := cascadeWorkspace(tc.n) | |
| 91 | + | w.Focus = tc.max | |
| 92 | + | w.maximize(tc.max) | |
| 93 | + | tiles, _, _ := w.previewTiles() | |
| 94 | + | g, h := w.gap(), w.Usable.H-2*w.gap() | |
| 95 | + | for i, p := range tiles { | |
| 96 | + | if p.Hidden != tc.hidden[i] { | |
| 97 | + | t.Fatalf("n=%d max=%d column %d hidden %v, want %v", tc.n, tc.max, i, p.Hidden, tc.hidden[i]) | |
| 98 | + | } | |
| 99 | + | if p.Hidden { | |
| 100 | + | continue | |
| 101 | + | } | |
| 102 | + | band := i / 3 | |
| 103 | + | want := Rect{X: w.cellX(i), Y: g + band*w.Usable.H, W: w.cellWidth(), H: h} | |
| 104 | + | if i == tc.max { | |
| 105 | + | want.X, want.W = g, w.Usable.W-2*g | |
| 106 | + | } | |
| 107 | + | if p.Rect != want { | |
| 108 | + | t.Fatalf("n=%d max=%d column %d rect %+v, want %+v", tc.n, tc.max, i, p.Rect, want) | |
| 109 | + | } | |
| 110 | + | } | |
| 111 | + | } | |
| 112 | + | } | |
| 113 | + | ||
| 78 | 114 | // T2: the selected band is the centre lane: undimmed, centred, selected tile | |
| 79 | 115 | // focused. | |
| 80 | 116 | func TestOverviewCascadeCentreLane(t *testing.T) { | |
| @@ -75,6 +75,42 @@ func TestOverviewCascadeZoomIgnoresBandCount(t *testing.T) { | |||||
| 75 | } | 75 | } | ||
| 76 | } | 76 | } | ||
| 77 | 77 | ||||
| 78 | + | // A maximized column fills its band in the preview as on screen, and hides | |||
| 79 | + | // only the other columns of its band. | |||
| 80 | + | func TestOverviewCascadeMaximizedColumnFillsBand(t *testing.T) { | |||
| 81 | + | // Four columns, three per band: band 0 holds 0..2, band 1 holds 3. | |||
| 82 | + | for _, tc := range []struct { | |||
| 83 | + | n, max int | |||
| 84 | + | hidden []bool | |||
| 85 | + | }{ | |||
| 86 | + | {n: 1, max: 0, hidden: []bool{false}}, | |||
| 87 | + | {n: 4, max: 0, hidden: []bool{false, true, true, false}}, | |||
| 88 | + | {n: 4, max: 3, hidden: []bool{false, false, false, false}}, | |||
| 89 | + | } { | |||
| 90 | + | w := cascadeWorkspace(tc.n) | |||
| 91 | + | w.Focus = tc.max | |||
| 92 | + | w.maximize(tc.max) | |||
| 93 | + | tiles, _, _ := w.previewTiles() | |||
| 94 | + | g, h := w.gap(), w.Usable.H-2*w.gap() | |||
| 95 | + | for i, p := range tiles { | |||
| 96 | + | if p.Hidden != tc.hidden[i] { | |||
| 97 | + | t.Fatalf("n=%d max=%d column %d hidden %v, want %v", tc.n, tc.max, i, p.Hidden, tc.hidden[i]) | |||
| 98 | + | } | |||
| 99 | + | if p.Hidden { | |||
| 100 | + | continue | |||
| 101 | + | } | |||
| 102 | + | band := i / 3 | |||
| 103 | + | want := Rect{X: w.cellX(i), Y: g + band*w.Usable.H, W: w.cellWidth(), H: h} | |||
| 104 | + | if i == tc.max { | |||
| 105 | + | want.X, want.W = g, w.Usable.W-2*g | |||
| 106 | + | } | |||
| 107 | + | if p.Rect != want { | |||
| 108 | + | t.Fatalf("n=%d max=%d column %d rect %+v, want %+v", tc.n, tc.max, i, p.Rect, want) | |||
| 109 | + | } | |||
| 110 | + | } | |||
| 111 | + | } | |||
| 112 | + | } | |||
| 113 | + | ||||
| 78 | // T2: the selected band is the centre lane: undimmed, centred, selected tile | 114 | // T2: the selected band is the centre lane: undimmed, centred, selected tile | ||
| 79 | // focused. | 115 | // focused. | ||
| 80 | func TestOverviewCascadeCentreLane(t *testing.T) { | 116 | func TestOverviewCascadeCentreLane(t *testing.T) { | ||