detached 61bb6e0

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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)
970970 rects = w.colBuf
971971 span = w.Usable.W // Fixed overflow never scrolls, even with a stash pile.
972972 }
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
974974 for i, c := range w.Columns {
975975 r := Rect{X: w.columnX(i) - w.Usable.X, Y: g, W: w.columnWidth(i), H: h}
976976 if w.policy().wraps {
977977 // The band is the lane: it is encoded in Y, one screen apart.
978+ // A maximized column fills its band as on screen.
978979 r.X = w.cellX(i)
979980 r.Y = g + w.band(i)*w.Usable.H
980981 r.W = w.cellWidth()
982+ if c.FullWidth {
983+ r.X, r.W = g, max(w.Usable.W-2*g, 0)
984+ }
981985 } else if w.Overflow == OverflowFixed {
982986 r = rects[i]
983987 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)
985989 // A fullscreen column shows at the width it had in the row.
986990 r.W = max(w.Usable.W-2*g, 0)
987991 }
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.
992996 for _, id := range c.Windows {
993997 tiles = append(tiles, Placement{ID: id, Hidden: true})
994998 }
@@ -970,14 +970,18 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect)
970 rects = w.colBuf970 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].FullWidth973+ 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.H980 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.Y987 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 buffers993+ // FullWidth hides the other columns of its band on screen.
990- // cannot fit their current spiral rects; show only the maximized994+ // 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) {
7575 }
7676 }
7777
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+
78114 // T2: the selected band is the centre lane: undimmed, centred, selected tile
79115 // focused.
80116 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 tile114 // 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) {

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 24

Tags 10

↑↓ moveenter openesc close