fix(overview): keep a maximized cascade column at full size beside its cards
The cards a maximized cascade column hides now peek out past the band, as in a fixed workspace, instead of narrowing the column: its preview keeps the workspace's aspect ratio and its buffer is no longer cropped.
- commit
- 553cc10919e33244450d3faabce7b68b798a7da6
- author
- brice · · 2026-10-08 12:14 UTC
- parent
- 4fd770d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +24 -23
M internal/core/overview.go +14 -15
| @@ -978,7 +978,11 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||
| 978 | 978 | rects = w.colBuf | |
| 979 | 979 | span = w.Usable.W // Fixed overflow never scrolls, even with a stash pile. | |
| 980 | 980 | } | |
| 981 | + | // off is the room, left of the band, for the cards a maximized | |
| 982 | + | // cascade column hides (the same room is kept on its right). | |
| 983 | + | off := 0 | |
| 981 | 984 | if w.hasBandCards() { | |
| 985 | + | off = 2 * w.bandCardStep() | |
| 982 | 986 | // The columns the maximized one hides are cards behind it, | |
| 983 | 987 | // farthest first so the nearest paint over them. | |
| 984 | 988 | first, last := w.laneBounds(w.laneOf(w.Focus)) | |
| @@ -998,18 +1002,13 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||
| 998 | 1002 | continue // A card, above. | |
| 999 | 1003 | } | |
| 1000 | 1004 | // The band is the lane: it is encoded in Y, one screen apart. | |
| 1001 | - | // A maximized column fills its band as on screen, inset for | |
| 1002 | - | // the cards it hides. | |
| 1003 | - | r.X = w.cellX(i) | |
| 1005 | + | // A maximized column fills its band as on screen; the cards | |
| 1006 | + | // it hides peek out on either side, past the band. | |
| 1007 | + | r.X = off + w.cellX(i) | |
| 1004 | 1008 | r.Y = g + w.band(i)*w.Usable.H | |
| 1005 | 1009 | r.W = w.cellWidth(i) | |
| 1006 | 1010 | if c.FullWidth { | |
| 1007 | - | in := 0 | |
| 1008 | - | if i == w.Focus && w.hasBandCards() { | |
| 1009 | - | // Only the focused column has cards behind it. | |
| 1010 | - | in = 2 * w.bandCardStep() | |
| 1011 | - | } | |
| 1012 | - | r.X, r.W = g+in, max(w.Usable.W-2*g-2*in, 0) | |
| 1011 | + | r.X, r.W = off+g, max(w.Usable.W-2*g, 0) | |
| 1013 | 1012 | } | |
| 1014 | 1013 | } else if w.Overflow == OverflowFixed { | |
| 1015 | 1014 | r = rects[i] | |
| @@ -1042,7 +1041,7 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||
| 1042 | 1041 | } | |
| 1043 | 1042 | } | |
| 1044 | 1043 | if w.policy().wraps && len(w.Columns) > 0 { | |
| 1045 | - | span = w.Usable.W | |
| 1044 | + | span = w.Usable.W + 2*off | |
| 1046 | 1045 | } | |
| 1047 | 1046 | return tiles, span, sel | |
| 1048 | 1047 | } | |
| @@ -1055,8 +1054,7 @@ func (w *Workspace) bandCardStep() int { | |||
| 1055 | 1054 | ||
| 1056 | 1055 | // hasBandCards reports whether the focused column of a cascade is | |
| 1057 | 1056 | // maximized and hides other columns of its band: the overview shows them | |
| 1058 | - | // as cards behind it, the maximized column inset by two card steps on | |
| 1059 | - | // each side. | |
| 1057 | + | // as cards behind it, peeking out up to two card steps on each side. | |
| 1060 | 1058 | func (w *Workspace) hasBandCards() bool { | |
| 1061 | 1059 | if !w.policy().wraps || w.Focus < 0 || w.Focus >= len(w.Columns) || !w.Columns[w.Focus].FullWidth { | |
| 1062 | 1060 | return false | |
| @@ -1067,8 +1065,9 @@ func (w *Workspace) hasBandCards() bool { | |||
| 1067 | 1065 | ||
| 1068 | 1066 | // appendBandCard appends hidden column i of a cascade band as a dimmed card | |
| 1069 | 1067 | // at its cell size behind the maximized column, on its side: the nearest | |
| 1070 | - | // column one step out from the maximized one, the next ones two steps out | |
| 1071 | - | // (on the band's edge), like the cards of a stack. | |
| 1068 | + | // column one step out from the maximized one, the next ones two steps out, | |
| 1069 | + | // like the cards of a stack. The maximized column keeps its full size two | |
| 1070 | + | // steps right of the row's start (see previewTilesInto). | |
| 1072 | 1071 | func (w *Workspace) appendBandCard(tiles []Placement, i int) []Placement { | |
| 1073 | 1072 | g, s, c := w.gap(), w.bandCardStep(), w.Columns[i] | |
| 1074 | 1073 | r := Rect{Y: g + w.band(i)*w.Usable.H, W: w.cellWidth(i), H: max(w.Usable.H-2*g, 0)} | |
| @@ -1076,7 +1075,7 @@ func (w *Workspace) appendBandCard(tiles []Placement, i int) []Placement { | |||
| 1076 | 1075 | if i < w.Focus { | |
| 1077 | 1076 | r.X = g + (2-k)*s | |
| 1078 | 1077 | } else { | |
| 1079 | - | r.X = w.Usable.W - g - (2-k)*s - r.W | |
| 1078 | + | r.X = w.Usable.W - g + (2+k)*s - r.W | |
| 1080 | 1079 | } | |
| 1081 | 1080 | w.rowBuf = rowRectsInto(w.rowBuf[:0], r, c, g) | |
| 1082 | 1081 | for j, t := range w.rowBuf { | |
| @@ -978,7 +978,11 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||||
| 978 | rects = w.colBuf | 978 | rects = w.colBuf | ||
| 979 | span = w.Usable.W // Fixed overflow never scrolls, even with a stash pile. | 979 | span = w.Usable.W // Fixed overflow never scrolls, even with a stash pile. | ||
| 980 | } | 980 | } | ||
| 981 | + | // off is the room, left of the band, for the cards a maximized | |||
| 982 | + | // cascade column hides (the same room is kept on its right). | |||
| 983 | + | off := 0 | |||
| 981 | if w.hasBandCards() { | 984 | if w.hasBandCards() { | ||
| 985 | + | off = 2 * w.bandCardStep() | |||
| 982 | // The columns the maximized one hides are cards behind it, | 986 | // The columns the maximized one hides are cards behind it, | ||
| 983 | // farthest first so the nearest paint over them. | 987 | // farthest first so the nearest paint over them. | ||
| 984 | first, last := w.laneBounds(w.laneOf(w.Focus)) | 988 | first, last := w.laneBounds(w.laneOf(w.Focus)) | ||
| @@ -998,18 +1002,13 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||||
| 998 | continue // A card, above. | 1002 | continue // A card, above. | ||
| 999 | } | 1003 | } | ||
| 1000 | // The band is the lane: it is encoded in Y, one screen apart. | 1004 | // The band is the lane: it is encoded in Y, one screen apart. | ||
| 1001 | - | // A maximized column fills its band as on screen, inset for | 1005 | + | // A maximized column fills its band as on screen; the cards |
| 1002 | - | // the cards it hides. | 1006 | + | // it hides peek out on either side, past the band. |
| 1003 | - | r.X = w.cellX(i) | 1007 | + | r.X = off + w.cellX(i) |
| 1004 | r.Y = g + w.band(i)*w.Usable.H | 1008 | r.Y = g + w.band(i)*w.Usable.H | ||
| 1005 | r.W = w.cellWidth(i) | 1009 | r.W = w.cellWidth(i) | ||
| 1006 | if c.FullWidth { | 1010 | if c.FullWidth { | ||
| 1007 | - | in := 0 | 1011 | + | r.X, r.W = off+g, max(w.Usable.W-2*g, 0) |
| 1008 | - | if i == w.Focus && w.hasBandCards() { | |||
| 1009 | - | // Only the focused column has cards behind it. | |||
| 1010 | - | in = 2 * w.bandCardStep() | |||
| 1011 | - | } | |||
| 1012 | - | r.X, r.W = g+in, max(w.Usable.W-2*g-2*in, 0) | |||
| 1013 | } | 1012 | } | ||
| 1014 | } else if w.Overflow == OverflowFixed { | 1013 | } else if w.Overflow == OverflowFixed { | ||
| 1015 | r = rects[i] | 1014 | r = rects[i] | ||
| @@ -1042,7 +1041,7 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||||
| 1042 | } | 1041 | } | ||
| 1043 | } | 1042 | } | ||
| 1044 | if w.policy().wraps && len(w.Columns) > 0 { | 1043 | if w.policy().wraps && len(w.Columns) > 0 { | ||
| 1045 | - | span = w.Usable.W | 1044 | + | span = w.Usable.W + 2*off |
| 1046 | } | 1045 | } | ||
| 1047 | return tiles, span, sel | 1046 | return tiles, span, sel | ||
| 1048 | } | 1047 | } | ||
| @@ -1055,8 +1054,7 @@ func (w *Workspace) bandCardStep() int { | |||||
| 1055 | 1054 | ||||
| 1056 | // hasBandCards reports whether the focused column of a cascade is | 1055 | // hasBandCards reports whether the focused column of a cascade is | ||
| 1057 | // maximized and hides other columns of its band: the overview shows them | 1056 | // maximized and hides other columns of its band: the overview shows them | ||
| 1058 | - | // as cards behind it, the maximized column inset by two card steps on | 1057 | + | // as cards behind it, peeking out up to two card steps on each side. |
| 1059 | - | // each side. | |||
| 1060 | func (w *Workspace) hasBandCards() bool { | 1058 | func (w *Workspace) hasBandCards() bool { | ||
| 1061 | if !w.policy().wraps || w.Focus < 0 || w.Focus >= len(w.Columns) || !w.Columns[w.Focus].FullWidth { | 1059 | if !w.policy().wraps || w.Focus < 0 || w.Focus >= len(w.Columns) || !w.Columns[w.Focus].FullWidth { | ||
| 1062 | return false | 1060 | return false | ||
| @@ -1067,8 +1065,9 @@ func (w *Workspace) hasBandCards() bool { | |||||
| 1067 | 1065 | ||||
| 1068 | // appendBandCard appends hidden column i of a cascade band as a dimmed card | 1066 | // appendBandCard appends hidden column i of a cascade band as a dimmed card | ||
| 1069 | // at its cell size behind the maximized column, on its side: the nearest | 1067 | // at its cell size behind the maximized column, on its side: the nearest | ||
| 1070 | - | // column one step out from the maximized one, the next ones two steps out | 1068 | + | // column one step out from the maximized one, the next ones two steps out, |
| 1071 | - | // (on the band's edge), like the cards of a stack. | 1069 | + | // like the cards of a stack. The maximized column keeps its full size two |
| 1070 | + | // steps right of the row's start (see previewTilesInto). | |||
| 1072 | func (w *Workspace) appendBandCard(tiles []Placement, i int) []Placement { | 1071 | func (w *Workspace) appendBandCard(tiles []Placement, i int) []Placement { | ||
| 1073 | g, s, c := w.gap(), w.bandCardStep(), w.Columns[i] | 1072 | g, s, c := w.gap(), w.bandCardStep(), w.Columns[i] | ||
| 1074 | r := Rect{Y: g + w.band(i)*w.Usable.H, W: w.cellWidth(i), H: max(w.Usable.H-2*g, 0)} | 1073 | r := Rect{Y: g + w.band(i)*w.Usable.H, W: w.cellWidth(i), H: max(w.Usable.H-2*g, 0)} | ||
| @@ -1076,7 +1075,7 @@ func (w *Workspace) appendBandCard(tiles []Placement, i int) []Placement { | |||||
| 1076 | if i < w.Focus { | 1075 | if i < w.Focus { | ||
| 1077 | r.X = g + (2-k)*s | 1076 | r.X = g + (2-k)*s | ||
| 1078 | } else { | 1077 | } else { | ||
| 1079 | - | r.X = w.Usable.W - g - (2-k)*s - r.W | 1078 | + | r.X = w.Usable.W - g + (2+k)*s - r.W |
| 1080 | } | 1079 | } | ||
| 1081 | w.rowBuf = rowRectsInto(w.rowBuf[:0], r, c, g) | 1080 | w.rowBuf = rowRectsInto(w.rowBuf[:0], r, c, g) | ||
| 1082 | for j, t := range w.rowBuf { | 1081 | for j, t := range w.rowBuf { | ||
M internal/core/overview_cascade_test.go +10 -8
| @@ -99,9 +99,9 @@ func TestOverviewCascadeMaximizedColumnCards(t *testing.T) { | |||
| 99 | 99 | w.maximize(tc.max) | |
| 100 | 100 | tiles, _, sel := w.previewTiles() | |
| 101 | 101 | g, h, s := w.gap(), w.Usable.H-2*w.gap(), w.bandCardStep() | |
| 102 | - | in := 0 | |
| 102 | + | off := 0 | |
| 103 | 103 | if slices.ContainsFunc(tc.cards, func(k int) bool { return k != 0 }) { | |
| 104 | - | in = 2 * s | |
| 104 | + | off = 2 * s | |
| 105 | 105 | } | |
| 106 | 106 | var order []int | |
| 107 | 107 | for _, p := range tiles { | |
| @@ -110,14 +110,15 @@ func TestOverviewCascadeMaximizedColumnCards(t *testing.T) { | |||
| 110 | 110 | order = append(order, i) | |
| 111 | 111 | } | |
| 112 | 112 | band := i / 3 | |
| 113 | - | want := Rect{X: w.cellX(i), Y: g + band*w.Usable.H, W: w.cellWidth(i), H: h} | |
| 113 | + | want := Rect{X: off + w.cellX(i), Y: g + band*w.Usable.H, W: w.cellWidth(i), H: h} | |
| 114 | 114 | switch k := tc.cards[i]; { | |
| 115 | 115 | case i == tc.max: | |
| 116 | - | want.X, want.W = g+in, w.Usable.W-2*g-2*in | |
| 116 | + | // Full size, as on screen: the workspace's ratio. | |
| 117 | + | want.X, want.W = off+g, w.Usable.W-2*g | |
| 117 | 118 | case k < 0: | |
| 118 | 119 | want.X = g + (2+k)*s | |
| 119 | 120 | case k > 0: | |
| 120 | - | want.X = w.Usable.W - g - (2-k)*s - want.W | |
| 121 | + | want.X = w.Usable.W - g + (2+k)*s - want.W | |
| 121 | 122 | } | |
| 122 | 123 | if p.Hidden || p.Peek != (tc.cards[i] != 0) || p.Focused != (i == tc.max) || p.Rect != want { | |
| 123 | 124 | t.Fatalf("n=%d max=%d column %d: %+v, want rect %+v card %v", tc.n, tc.max, i, p, want, tc.cards[i] != 0) | |
| @@ -206,10 +207,11 @@ func TestOverviewCascadeUnfocusedMaximizedNotInset(t *testing.T) { | |||
| 206 | 207 | w.maximize(4) | |
| 207 | 208 | w.Columns[0].FullWidth = true | |
| 208 | 209 | tiles, _, _ := w.previewTiles() | |
| 209 | - | g := w.gap() | |
| 210 | + | // The row keeps room for the focused column's cards on its left. | |
| 211 | + | g, off := w.gap(), 2*w.bandCardStep() | |
| 210 | 212 | p := previewOf(t, tiles, 1) | |
| 211 | - | if p.Hidden || p.Rect.X != g || p.Rect.W != w.Usable.W-2*g { | |
| 212 | - | t.Fatalf("unfocused maximized column %+v, want X %d W %d", p, g, w.Usable.W-2*g) | |
| 213 | + | if p.Hidden || p.Rect.X != off+g || p.Rect.W != w.Usable.W-2*g { | |
| 214 | + | t.Fatalf("unfocused maximized column %+v, want X %d W %d", p, off+g, w.Usable.W-2*g) | |
| 213 | 215 | } | |
| 214 | 216 | } | |
| 215 | 217 | ||
| @@ -99,9 +99,9 @@ func TestOverviewCascadeMaximizedColumnCards(t *testing.T) { | |||||
| 99 | w.maximize(tc.max) | 99 | w.maximize(tc.max) | ||
| 100 | tiles, _, sel := w.previewTiles() | 100 | tiles, _, sel := w.previewTiles() | ||
| 101 | g, h, s := w.gap(), w.Usable.H-2*w.gap(), w.bandCardStep() | 101 | g, h, s := w.gap(), w.Usable.H-2*w.gap(), w.bandCardStep() | ||
| 102 | - | in := 0 | 102 | + | off := 0 |
| 103 | if slices.ContainsFunc(tc.cards, func(k int) bool { return k != 0 }) { | 103 | if slices.ContainsFunc(tc.cards, func(k int) bool { return k != 0 }) { | ||
| 104 | - | in = 2 * s | 104 | + | off = 2 * s |
| 105 | } | 105 | } | ||
| 106 | var order []int | 106 | var order []int | ||
| 107 | for _, p := range tiles { | 107 | for _, p := range tiles { | ||
| @@ -110,14 +110,15 @@ func TestOverviewCascadeMaximizedColumnCards(t *testing.T) { | |||||
| 110 | order = append(order, i) | 110 | order = append(order, i) | ||
| 111 | } | 111 | } | ||
| 112 | band := i / 3 | 112 | band := i / 3 | ||
| 113 | - | want := Rect{X: w.cellX(i), Y: g + band*w.Usable.H, W: w.cellWidth(i), H: h} | 113 | + | want := Rect{X: off + w.cellX(i), Y: g + band*w.Usable.H, W: w.cellWidth(i), H: h} |
| 114 | switch k := tc.cards[i]; { | 114 | switch k := tc.cards[i]; { | ||
| 115 | case i == tc.max: | 115 | case i == tc.max: | ||
| 116 | - | want.X, want.W = g+in, w.Usable.W-2*g-2*in | 116 | + | // Full size, as on screen: the workspace's ratio. |
| 117 | + | want.X, want.W = off+g, w.Usable.W-2*g | |||
| 117 | case k < 0: | 118 | case k < 0: | ||
| 118 | want.X = g + (2+k)*s | 119 | want.X = g + (2+k)*s | ||
| 119 | case k > 0: | 120 | case k > 0: | ||
| 120 | - | want.X = w.Usable.W - g - (2-k)*s - want.W | 121 | + | want.X = w.Usable.W - g + (2+k)*s - want.W |
| 121 | } | 122 | } | ||
| 122 | if p.Hidden || p.Peek != (tc.cards[i] != 0) || p.Focused != (i == tc.max) || p.Rect != want { | 123 | if p.Hidden || p.Peek != (tc.cards[i] != 0) || p.Focused != (i == tc.max) || p.Rect != want { | ||
| 123 | t.Fatalf("n=%d max=%d column %d: %+v, want rect %+v card %v", tc.n, tc.max, i, p, want, tc.cards[i] != 0) | 124 | t.Fatalf("n=%d max=%d column %d: %+v, want rect %+v card %v", tc.n, tc.max, i, p, want, tc.cards[i] != 0) | ||
| @@ -206,10 +207,11 @@ func TestOverviewCascadeUnfocusedMaximizedNotInset(t *testing.T) { | |||||
| 206 | w.maximize(4) | 207 | w.maximize(4) | ||
| 207 | w.Columns[0].FullWidth = true | 208 | w.Columns[0].FullWidth = true | ||
| 208 | tiles, _, _ := w.previewTiles() | 209 | tiles, _, _ := w.previewTiles() | ||
| 209 | - | g := w.gap() | 210 | + | // The row keeps room for the focused column's cards on its left. |
| 211 | + | g, off := w.gap(), 2*w.bandCardStep() | |||
| 210 | p := previewOf(t, tiles, 1) | 212 | p := previewOf(t, tiles, 1) | ||
| 211 | - | if p.Hidden || p.Rect.X != g || p.Rect.W != w.Usable.W-2*g { | 213 | + | if p.Hidden || p.Rect.X != off+g || p.Rect.W != w.Usable.W-2*g { |
| 212 | - | t.Fatalf("unfocused maximized column %+v, want X %d W %d", p, g, w.Usable.W-2*g) | 214 | + | t.Fatalf("unfocused maximized column %+v, want X %d W %d", p, off+g, w.Usable.W-2*g) |
| 213 | } | 215 | } | ||
| 214 | } | 216 | } | ||
| 215 | 217 | ||||