feat(core): give a fullscreen cascade window its own band
A cascade window going fullscreen leaves its band for a new one right below, so focus changes among the other columns no longer recompose the fullscreen output. move-column-left/right on a fullscreen window sends it to the neighbor monitor, still fullscreen.
- commit
- dbd98309bcdd8e48b5bb98f4b9a3e6d6e1f0f2be
- author
- brice · · 2026-10-22 19:43 UTC
- parent
- fdd661a
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +158 -6
M docs/config.md +2 -0
| @@ -307,6 +307,8 @@ The default layout. Columns sit side by side in bands of up to `max-columns`, st | |||
| 307 | 307 | - A new window joins the focused band when it has room, else it gets a new band right below. | |
| 308 | 308 | - `cycle-column-width` in a full band makes room: the rightmost column goes to the band below (from the last column, the leftmost goes to the band above). The end of the cycle brings it back. | |
| 309 | 309 | - `maximize-column` gives the column a band of its own: the columns on its left go to the band above, the ones on its right to the band below. Maximizing again forms the band back. | |
| 310 | + | - A fullscreen window gets a band of its own right below the band it was in (expelled into its own column first when it shares one), so the other columns stay put and focus changes among them leave the fullscreen output alone. Leaving fullscreen keeps the column in that band. | |
| 311 | + | - `move-column-left/right` on a fullscreen window sends it to the neighbor monitor, where it stays fullscreen and focused; with no neighbor monitor nothing moves. | |
| 310 | 312 | - A band left empty goes away. A smaller `max-columns` moves the extra columns of a band to a new band below it. | |
| 311 | 313 | ||
| 312 | 314 | ### Column widths | |
| @@ -307,6 +307,8 @@ The default layout. Columns sit side by side in bands of up to `max-columns`, st | |||||
| 307 | - A new window joins the focused band when it has room, else it gets a new band right below. | 307 | - A new window joins the focused band when it has room, else it gets a new band right below. | ||
| 308 | - `cycle-column-width` in a full band makes room: the rightmost column goes to the band below (from the last column, the leftmost goes to the band above). The end of the cycle brings it back. | 308 | - `cycle-column-width` in a full band makes room: the rightmost column goes to the band below (from the last column, the leftmost goes to the band above). The end of the cycle brings it back. | ||
| 309 | - `maximize-column` gives the column a band of its own: the columns on its left go to the band above, the ones on its right to the band below. Maximizing again forms the band back. | 309 | - `maximize-column` gives the column a band of its own: the columns on its left go to the band above, the ones on its right to the band below. Maximizing again forms the band back. | ||
| 310 | + | - A fullscreen window gets a band of its own right below the band it was in (expelled into its own column first when it shares one), so the other columns stay put and focus changes among them leave the fullscreen output alone. Leaving fullscreen keeps the column in that band. | |||
| 311 | + | - `move-column-left/right` on a fullscreen window sends it to the neighbor monitor, where it stays fullscreen and focused; with no neighbor monitor nothing moves. | |||
| 310 | - A band left empty goes away. A smaller `max-columns` moves the extra columns of a band to a new band below it. | 312 | - A band left empty goes away. A smaller `max-columns` moves the extra columns of a band to a new band below it. | ||
| 311 | 313 | ||||
| 312 | ### Column widths | 314 | ### Column widths | ||
M internal/core/action.go +12 -1
| @@ -388,7 +388,15 @@ func (c *Core) dispatchAction(a Action) Effect { | |||
| 388 | 388 | if a == ActionMoveColumnLeft { | |
| 389 | 389 | dir, d = -1, dirLeft | |
| 390 | 390 | } | |
| 391 | - | if i := c.neighbor(d); w.leavesBand(w.Focus+dir) && i >= 0 { | |
| 391 | + | // A fullscreen window leaves for the neighbor screen at once, and | |
| 392 | + | // stays fullscreen there; with no screen, nothing moves. | |
| 393 | + | fid, _ := w.Focused() | |
| 394 | + | full := fid != 0 && !w.onFloat() && w.fullscreen == fid | |
| 395 | + | i := c.neighbor(d) | |
| 396 | + | if full && i < 0 { | |
| 397 | + | return Effect{} | |
| 398 | + | } | |
| 399 | + | if i >= 0 && (full || w.leavesBand(w.Focus+dir)) { | |
| 392 | 400 | col, ok := w.takeColumn() | |
| 393 | 401 | if !ok { | |
| 394 | 402 | return Effect{} | |
| @@ -401,6 +409,9 @@ func (c *Core) dispatchAction(a Action) Effect { | |||
| 401 | 409 | at = len(to.Columns) | |
| 402 | 410 | } | |
| 403 | 411 | to.receive(col, at) | |
| 412 | + | if full { | |
| 413 | + | to.ToggleFullscreen() | |
| 414 | + | } | |
| 404 | 415 | from.mon.normalize() | |
| 405 | 416 | c.cur().mon.normalize() | |
| 406 | 417 | return Effect{} | |
| @@ -388,7 +388,15 @@ func (c *Core) dispatchAction(a Action) Effect { | |||||
| 388 | if a == ActionMoveColumnLeft { | 388 | if a == ActionMoveColumnLeft { | ||
| 389 | dir, d = -1, dirLeft | 389 | dir, d = -1, dirLeft | ||
| 390 | } | 390 | } | ||
| 391 | - | if i := c.neighbor(d); w.leavesBand(w.Focus+dir) && i >= 0 { | 391 | + | // A fullscreen window leaves for the neighbor screen at once, and |
| 392 | + | // stays fullscreen there; with no screen, nothing moves. | |||
| 393 | + | fid, _ := w.Focused() | |||
| 394 | + | full := fid != 0 && !w.onFloat() && w.fullscreen == fid | |||
| 395 | + | i := c.neighbor(d) | |||
| 396 | + | if full && i < 0 { | |||
| 397 | + | return Effect{} | |||
| 398 | + | } | |||
| 399 | + | if i >= 0 && (full || w.leavesBand(w.Focus+dir)) { | |||
| 392 | col, ok := w.takeColumn() | 400 | col, ok := w.takeColumn() | ||
| 393 | if !ok { | 401 | if !ok { | ||
| 394 | return Effect{} | 402 | return Effect{} | ||
| @@ -401,6 +409,9 @@ func (c *Core) dispatchAction(a Action) Effect { | |||||
| 401 | at = len(to.Columns) | 409 | at = len(to.Columns) | ||
| 402 | } | 410 | } | ||
| 403 | to.receive(col, at) | 411 | to.receive(col, at) | ||
| 412 | + | if full { | |||
| 413 | + | to.ToggleFullscreen() | |||
| 414 | + | } | |||
| 404 | from.mon.normalize() | 415 | from.mon.normalize() | ||
| 405 | c.cur().mon.normalize() | 416 | c.cur().mon.normalize() | ||
| 406 | return Effect{} | 417 | return Effect{} | ||
M internal/core/cascade.go +39 -0
| @@ -419,6 +419,45 @@ func (w *Workspace) isolateBand() { | |||
| 419 | 419 | w.setBands(bands, id) | |
| 420 | 420 | } | |
| 421 | 421 | ||
| 422 | + | // fullscreenBand gives tiled window id, about to go fullscreen, a band of | |
| 423 | + | // its own right below the band it is in, so the other columns keep their | |
| 424 | + | // band and a focus change among them never touches the fullscreen output. | |
| 425 | + | // A window stacked in a column is expelled into a column of its own first. | |
| 426 | + | // Leaving fullscreen does not move it back: the column keeps its band, so | |
| 427 | + | // nothing jumps when the user leaves. Only cascade layouts do this; the | |
| 428 | + | // focus stays on the same window. | |
| 429 | + | func (w *Workspace) fullscreenBand(id WindowID) { | |
| 430 | + | i := w.columnOf(id) | |
| 431 | + | if !w.policy().wraps || i < 0 { | |
| 432 | + | return | |
| 433 | + | } | |
| 434 | + | b := w.band(i) | |
| 435 | + | first, last := w.bandBounds(b) | |
| 436 | + | if len(w.Columns[i].Windows) == 1 && first == last { | |
| 437 | + | return | |
| 438 | + | } | |
| 439 | + | fc := w.Columns[w.Focus] | |
| 440 | + | focus := fc.Windows[fc.Focus] | |
| 441 | + | w.dropSlotOf(id) | |
| 442 | + | bands := w.bandList() | |
| 443 | + | col := bands[b][i-first] | |
| 444 | + | nc := col | |
| 445 | + | if len(col.Windows) == 1 { | |
| 446 | + | bands[b] = slices.Delete(bands[b], i-first, i-first+1) | |
| 447 | + | } else { | |
| 448 | + | j := slices.Index(col.Windows, id) | |
| 449 | + | nc = Column{Windows: []WindowID{id}, Cells: col.Cells} | |
| 450 | + | col.Windows = slices.Delete(slices.Clone(col.Windows), j, j+1) | |
| 451 | + | switch { | |
| 452 | + | case col.Focus > j, col.Focus >= len(col.Windows): | |
| 453 | + | col.Focus = max(min(col.Focus-1, len(col.Windows)-1), 0) | |
| 454 | + | } | |
| 455 | + | bands[b][i-first] = col | |
| 456 | + | } | |
| 457 | + | bands = slices.Insert(bands, b+1, []Column{nc}) | |
| 458 | + | w.setBands(bands, focus) | |
| 459 | + | } | |
| 460 | + | ||
| 422 | 461 | // pushNeighbor makes room in the focused column's full cascade band for a | |
| 423 | 462 | // wider column: the column at the far end of the band leaves it. The | |
| 424 | 463 | // rightmost goes to the band below; from the last column, the leftmost | |
| @@ -419,6 +419,45 @@ func (w *Workspace) isolateBand() { | |||||
| 419 | w.setBands(bands, id) | 419 | w.setBands(bands, id) | ||
| 420 | } | 420 | } | ||
| 421 | 421 | ||||
| 422 | + | // fullscreenBand gives tiled window id, about to go fullscreen, a band of | |||
| 423 | + | // its own right below the band it is in, so the other columns keep their | |||
| 424 | + | // band and a focus change among them never touches the fullscreen output. | |||
| 425 | + | // A window stacked in a column is expelled into a column of its own first. | |||
| 426 | + | // Leaving fullscreen does not move it back: the column keeps its band, so | |||
| 427 | + | // nothing jumps when the user leaves. Only cascade layouts do this; the | |||
| 428 | + | // focus stays on the same window. | |||
| 429 | + | func (w *Workspace) fullscreenBand(id WindowID) { | |||
| 430 | + | i := w.columnOf(id) | |||
| 431 | + | if !w.policy().wraps || i < 0 { | |||
| 432 | + | return | |||
| 433 | + | } | |||
| 434 | + | b := w.band(i) | |||
| 435 | + | first, last := w.bandBounds(b) | |||
| 436 | + | if len(w.Columns[i].Windows) == 1 && first == last { | |||
| 437 | + | return | |||
| 438 | + | } | |||
| 439 | + | fc := w.Columns[w.Focus] | |||
| 440 | + | focus := fc.Windows[fc.Focus] | |||
| 441 | + | w.dropSlotOf(id) | |||
| 442 | + | bands := w.bandList() | |||
| 443 | + | col := bands[b][i-first] | |||
| 444 | + | nc := col | |||
| 445 | + | if len(col.Windows) == 1 { | |||
| 446 | + | bands[b] = slices.Delete(bands[b], i-first, i-first+1) | |||
| 447 | + | } else { | |||
| 448 | + | j := slices.Index(col.Windows, id) | |||
| 449 | + | nc = Column{Windows: []WindowID{id}, Cells: col.Cells} | |||
| 450 | + | col.Windows = slices.Delete(slices.Clone(col.Windows), j, j+1) | |||
| 451 | + | switch { | |||
| 452 | + | case col.Focus > j, col.Focus >= len(col.Windows): | |||
| 453 | + | col.Focus = max(min(col.Focus-1, len(col.Windows)-1), 0) | |||
| 454 | + | } | |||
| 455 | + | bands[b][i-first] = col | |||
| 456 | + | } | |||
| 457 | + | bands = slices.Insert(bands, b+1, []Column{nc}) | |||
| 458 | + | w.setBands(bands, focus) | |||
| 459 | + | } | |||
| 460 | + | ||||
| 422 | // pushNeighbor makes room in the focused column's full cascade band for a | 461 | // pushNeighbor makes room in the focused column's full cascade band for a | ||
| 423 | // wider column: the column at the far end of the band leaves it. The | 462 | // wider column: the column at the far end of the band leaves it. The | ||
| 424 | // rightmost goes to the band below; from the last column, the leftmost | 463 | // rightmost goes to the band below; from the last column, the leftmost | ||
A internal/core/cascade_fullscreen_band_test.go +94 -0
| @@ -0,0 +1,94 @@ | |||
| 1 | + | package core | |
| 2 | + | ||
| 3 | + | import "testing" | |
| 4 | + | ||
| 5 | + | // Entering fullscreen gives a cascade column a band below its own; the | |
| 6 | + | // others stay. Leaving keeps it there. | |
| 7 | + | func TestCascadeFullscreenOwnBand(t *testing.T) { | |
| 8 | + | for _, tc := range []struct { | |
| 9 | + | name string | |
| 10 | + | do func(c *Core, w *Workspace) | |
| 11 | + | }{ | |
| 12 | + | {"toggle", func(_ *Core, w *Workspace) { w.ToggleFullscreen() }}, | |
| 13 | + | {"client request", func(_ *Core, w *Workspace) { w.SetFullscreen(3, true) }}, | |
| 14 | + | } { | |
| 15 | + | t.Run(tc.name, func(t *testing.T) { | |
| 16 | + | c, w := cascadeMovesCore(t, 3, 3, false) | |
| 17 | + | wantBands(t, w, []WindowID{1, 2, 3}) | |
| 18 | + | tc.do(c, w) | |
| 19 | + | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |
| 20 | + | wantFocus(t, c, "M", 3) | |
| 21 | + | if w.fullscreen != 3 { | |
| 22 | + | t.Fatalf("fullscreen %d, want 3", w.fullscreen) | |
| 23 | + | } | |
| 24 | + | w.ToggleFullscreen() | |
| 25 | + | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |
| 26 | + | }) | |
| 27 | + | } | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | func TestCascadeFullscreenMiddleAndAlone(t *testing.T) { | |
| 31 | + | c, w := cascadeMovesCore(t, 3, 3, false) | |
| 32 | + | w.FocusID(2) | |
| 33 | + | w.ToggleFullscreen() | |
| 34 | + | wantBands(t, w, []WindowID{1, 3}, []WindowID{2}) | |
| 35 | + | wantFocus(t, c, "M", 2) | |
| 36 | + | ||
| 37 | + | _, w = cascadeMovesCore(t, 1, 3, false) | |
| 38 | + | w.ToggleFullscreen() | |
| 39 | + | wantBands(t, w, []WindowID{1}) | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | func TestCascadeFullscreenExpelsFromStack(t *testing.T) { | |
| 43 | + | c, w := cascadeMovesCore(t, 2, 3, false) | |
| 44 | + | w.FocusID(2) | |
| 45 | + | c.applyAction(ActionConsumeOrExpelLeft) // 2 stacks under 1 | |
| 46 | + | wantColumns(t, w, 1) | |
| 47 | + | w.ToggleFullscreen() | |
| 48 | + | wantBands(t, w, []WindowID{1}, []WindowID{2}) | |
| 49 | + | if len(w.Columns[0].Windows) != 1 || w.fullscreen != 2 { | |
| 50 | + | t.Fatalf("columns %v fullscreen %d", w.Columns, w.fullscreen) | |
| 51 | + | } | |
| 52 | + | wantFocus(t, c, "M", 2) | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | func TestScrollFullscreenKeepsColumns(t *testing.T) { | |
| 56 | + | _, w := cascadeMovesCore(t, 3, 3, false) | |
| 57 | + | w.Overflow = OverflowScroll | |
| 58 | + | w.forgetBands() | |
| 59 | + | w.ToggleFullscreen() | |
| 60 | + | if len(w.Columns) != 3 { | |
| 61 | + | t.Fatalf("columns %d", len(w.Columns)) | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | func TestFullscreenColumnMovesToNeighborMonitor(t *testing.T) { | |
| 66 | + | for _, tc := range []struct { | |
| 67 | + | action Action | |
| 68 | + | screen string | |
| 69 | + | }{{ActionMoveColumnLeft, "L"}, {ActionMoveColumnRight, "R"}} { | |
| 70 | + | t.Run(tc.screen, func(t *testing.T) { | |
| 71 | + | c, w := cascadeMovesCore(t, 3, 3, true) | |
| 72 | + | w.ToggleFullscreen() | |
| 73 | + | c.applyAction(tc.action) | |
| 74 | + | wantColumns(t, w, 1, 2) | |
| 75 | + | dst := screenWorkspace(c, tc.screen) | |
| 76 | + | wantColumns(t, dst, 3) | |
| 77 | + | wantFocus(t, c, tc.screen, 3) | |
| 78 | + | if dst.fullscreen != 3 || w.fullscreen != 0 { | |
| 79 | + | t.Fatalf("fullscreen dst %d src %d", dst.fullscreen, w.fullscreen) | |
| 80 | + | } | |
| 81 | + | }) | |
| 82 | + | } | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | func TestFullscreenColumnNoNeighborStays(t *testing.T) { | |
| 86 | + | c, w := cascadeMovesCore(t, 3, 3, false) | |
| 87 | + | w.ToggleFullscreen() | |
| 88 | + | c.applyAction(ActionMoveColumnLeft) | |
| 89 | + | c.applyAction(ActionMoveColumnRight) | |
| 90 | + | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |
| 91 | + | if w.fullscreen != 3 { | |
| 92 | + | t.Fatalf("fullscreen %d", w.fullscreen) | |
| 93 | + | } | |
| 94 | + | } | |
| @@ -0,0 +1,94 @@ | |||||
| 1 | + | package core | |||
| 2 | + | ||||
| 3 | + | import "testing" | |||
| 4 | + | ||||
| 5 | + | // Entering fullscreen gives a cascade column a band below its own; the | |||
| 6 | + | // others stay. Leaving keeps it there. | |||
| 7 | + | func TestCascadeFullscreenOwnBand(t *testing.T) { | |||
| 8 | + | for _, tc := range []struct { | |||
| 9 | + | name string | |||
| 10 | + | do func(c *Core, w *Workspace) | |||
| 11 | + | }{ | |||
| 12 | + | {"toggle", func(_ *Core, w *Workspace) { w.ToggleFullscreen() }}, | |||
| 13 | + | {"client request", func(_ *Core, w *Workspace) { w.SetFullscreen(3, true) }}, | |||
| 14 | + | } { | |||
| 15 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 16 | + | c, w := cascadeMovesCore(t, 3, 3, false) | |||
| 17 | + | wantBands(t, w, []WindowID{1, 2, 3}) | |||
| 18 | + | tc.do(c, w) | |||
| 19 | + | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |||
| 20 | + | wantFocus(t, c, "M", 3) | |||
| 21 | + | if w.fullscreen != 3 { | |||
| 22 | + | t.Fatalf("fullscreen %d, want 3", w.fullscreen) | |||
| 23 | + | } | |||
| 24 | + | w.ToggleFullscreen() | |||
| 25 | + | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |||
| 26 | + | }) | |||
| 27 | + | } | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | func TestCascadeFullscreenMiddleAndAlone(t *testing.T) { | |||
| 31 | + | c, w := cascadeMovesCore(t, 3, 3, false) | |||
| 32 | + | w.FocusID(2) | |||
| 33 | + | w.ToggleFullscreen() | |||
| 34 | + | wantBands(t, w, []WindowID{1, 3}, []WindowID{2}) | |||
| 35 | + | wantFocus(t, c, "M", 2) | |||
| 36 | + | ||||
| 37 | + | _, w = cascadeMovesCore(t, 1, 3, false) | |||
| 38 | + | w.ToggleFullscreen() | |||
| 39 | + | wantBands(t, w, []WindowID{1}) | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | func TestCascadeFullscreenExpelsFromStack(t *testing.T) { | |||
| 43 | + | c, w := cascadeMovesCore(t, 2, 3, false) | |||
| 44 | + | w.FocusID(2) | |||
| 45 | + | c.applyAction(ActionConsumeOrExpelLeft) // 2 stacks under 1 | |||
| 46 | + | wantColumns(t, w, 1) | |||
| 47 | + | w.ToggleFullscreen() | |||
| 48 | + | wantBands(t, w, []WindowID{1}, []WindowID{2}) | |||
| 49 | + | if len(w.Columns[0].Windows) != 1 || w.fullscreen != 2 { | |||
| 50 | + | t.Fatalf("columns %v fullscreen %d", w.Columns, w.fullscreen) | |||
| 51 | + | } | |||
| 52 | + | wantFocus(t, c, "M", 2) | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | func TestScrollFullscreenKeepsColumns(t *testing.T) { | |||
| 56 | + | _, w := cascadeMovesCore(t, 3, 3, false) | |||
| 57 | + | w.Overflow = OverflowScroll | |||
| 58 | + | w.forgetBands() | |||
| 59 | + | w.ToggleFullscreen() | |||
| 60 | + | if len(w.Columns) != 3 { | |||
| 61 | + | t.Fatalf("columns %d", len(w.Columns)) | |||
| 62 | + | } | |||
| 63 | + | } | |||
| 64 | + | ||||
| 65 | + | func TestFullscreenColumnMovesToNeighborMonitor(t *testing.T) { | |||
| 66 | + | for _, tc := range []struct { | |||
| 67 | + | action Action | |||
| 68 | + | screen string | |||
| 69 | + | }{{ActionMoveColumnLeft, "L"}, {ActionMoveColumnRight, "R"}} { | |||
| 70 | + | t.Run(tc.screen, func(t *testing.T) { | |||
| 71 | + | c, w := cascadeMovesCore(t, 3, 3, true) | |||
| 72 | + | w.ToggleFullscreen() | |||
| 73 | + | c.applyAction(tc.action) | |||
| 74 | + | wantColumns(t, w, 1, 2) | |||
| 75 | + | dst := screenWorkspace(c, tc.screen) | |||
| 76 | + | wantColumns(t, dst, 3) | |||
| 77 | + | wantFocus(t, c, tc.screen, 3) | |||
| 78 | + | if dst.fullscreen != 3 || w.fullscreen != 0 { | |||
| 79 | + | t.Fatalf("fullscreen dst %d src %d", dst.fullscreen, w.fullscreen) | |||
| 80 | + | } | |||
| 81 | + | }) | |||
| 82 | + | } | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | func TestFullscreenColumnNoNeighborStays(t *testing.T) { | |||
| 86 | + | c, w := cascadeMovesCore(t, 3, 3, false) | |||
| 87 | + | w.ToggleFullscreen() | |||
| 88 | + | c.applyAction(ActionMoveColumnLeft) | |||
| 89 | + | c.applyAction(ActionMoveColumnRight) | |||
| 90 | + | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |||
| 91 | + | if w.fullscreen != 3 { | |||
| 92 | + | t.Fatalf("fullscreen %d", w.fullscreen) | |||
| 93 | + | } | |||
| 94 | + | } | |||
M internal/core/cascade_moves_test.go +3 -2
| @@ -299,13 +299,14 @@ func TestCascadeMoveWindowStackedFirst(t *testing.T) { | |||
| 299 | 299 | wantFocus(t, c, "M", 5) | |
| 300 | 300 | } | |
| 301 | 301 | ||
| 302 | - | // A fullscreen window keeps its band. | |
| 302 | + | // A fullscreen window keeps its own band (it left {4 5} when it went | |
| 303 | + | // fullscreen). | |
| 303 | 304 | func TestCascadeMoveWindowFullscreenStays(t *testing.T) { | |
| 304 | 305 | c, w := cascadeMovesCore(t, 5, 3, false) | |
| 305 | 306 | w.FocusID(4) | |
| 306 | 307 | w.ToggleFullscreen() | |
| 307 | 308 | c.applyAction(ActionMoveWindowUp) | |
| 308 | - | wantColumns(t, w, 1, 2, 3, 4, 5) | |
| 309 | + | wantColumns(t, w, 1, 2, 3, 5, 4) | |
| 309 | 310 | } | |
| 310 | 311 | ||
| 311 | 312 | // Scroll overflow has no bands: up/down stop at the column edge. | |
| @@ -299,13 +299,14 @@ func TestCascadeMoveWindowStackedFirst(t *testing.T) { | |||||
| 299 | wantFocus(t, c, "M", 5) | 299 | wantFocus(t, c, "M", 5) | ||
| 300 | } | 300 | } | ||
| 301 | 301 | ||||
| 302 | - | // A fullscreen window keeps its band. | 302 | + | // A fullscreen window keeps its own band (it left {4 5} when it went |
| 303 | + | // fullscreen). | |||
| 303 | func TestCascadeMoveWindowFullscreenStays(t *testing.T) { | 304 | func TestCascadeMoveWindowFullscreenStays(t *testing.T) { | ||
| 304 | c, w := cascadeMovesCore(t, 5, 3, false) | 305 | c, w := cascadeMovesCore(t, 5, 3, false) | ||
| 305 | w.FocusID(4) | 306 | w.FocusID(4) | ||
| 306 | w.ToggleFullscreen() | 307 | w.ToggleFullscreen() | ||
| 307 | c.applyAction(ActionMoveWindowUp) | 308 | c.applyAction(ActionMoveWindowUp) | ||
| 308 | - | wantColumns(t, w, 1, 2, 3, 4, 5) | 309 | + | wantColumns(t, w, 1, 2, 3, 5, 4) |
| 309 | } | 310 | } | ||
| 310 | 311 | ||||
| 311 | // Scroll overflow has no bands: up/down stop at the column edge. | 312 | // Scroll overflow has no bands: up/down stop at the column edge. | ||
M internal/core/cascade_test.go +1 -1
| @@ -195,7 +195,7 @@ func TestCascadeCovers(t *testing.T) { | |||
| 195 | 195 | } | |
| 196 | 196 | w.ToggleFullscreen() | |
| 197 | 197 | w.FocusID(4) | |
| 198 | - | if w.View != 600 { | |
| 198 | + | if w.View != 1200 { | |
| 199 | 199 | t.Fatal("fullscreen restore lost band reveal") | |
| 200 | 200 | } | |
| 201 | 201 | } | |
| @@ -195,7 +195,7 @@ func TestCascadeCovers(t *testing.T) { | |||||
| 195 | } | 195 | } | ||
| 196 | w.ToggleFullscreen() | 196 | w.ToggleFullscreen() | ||
| 197 | w.FocusID(4) | 197 | w.FocusID(4) | ||
| 198 | - | if w.View != 600 { | 198 | + | if w.View != 1200 { |
| 199 | t.Fatal("fullscreen restore lost band reveal") | 199 | t.Fatal("fullscreen restore lost band reveal") | ||
| 200 | } | 200 | } | ||
| 201 | } | 201 | } | ||
M internal/core/layout.go +4 -0
| @@ -1335,6 +1335,7 @@ func (w *Workspace) ToggleFullscreen() { | |||
| 1335 | 1335 | w.endFullscreen() | |
| 1336 | 1336 | return | |
| 1337 | 1337 | } | |
| 1338 | + | w.fullscreenBand(id) | |
| 1338 | 1339 | w.fullscreen = id | |
| 1339 | 1340 | w.scroll() | |
| 1340 | 1341 | } | |
| @@ -1444,6 +1445,9 @@ func (w *Workspace) SetFullscreen(id WindowID, on bool) { | |||
| 1444 | 1445 | // Another window covers: the override was for the old cover. | |
| 1445 | 1446 | w.dropStashOver() | |
| 1446 | 1447 | } | |
| 1448 | + | if !w.isFloat(id) { | |
| 1449 | + | w.fullscreenBand(id) | |
| 1450 | + | } | |
| 1447 | 1451 | w.fullscreen = id | |
| 1448 | 1452 | if !w.isFloat(id) { | |
| 1449 | 1453 | w.scroll() | |
| @@ -1335,6 +1335,7 @@ func (w *Workspace) ToggleFullscreen() { | |||||
| 1335 | w.endFullscreen() | 1335 | w.endFullscreen() | ||
| 1336 | return | 1336 | return | ||
| 1337 | } | 1337 | } | ||
| 1338 | + | w.fullscreenBand(id) | |||
| 1338 | w.fullscreen = id | 1339 | w.fullscreen = id | ||
| 1339 | w.scroll() | 1340 | w.scroll() | ||
| 1340 | } | 1341 | } | ||
| @@ -1444,6 +1445,9 @@ func (w *Workspace) SetFullscreen(id WindowID, on bool) { | |||||
| 1444 | // Another window covers: the override was for the old cover. | 1445 | // Another window covers: the override was for the old cover. | ||
| 1445 | w.dropStashOver() | 1446 | w.dropStashOver() | ||
| 1446 | } | 1447 | } | ||
| 1448 | + | if !w.isFloat(id) { | |||
| 1449 | + | w.fullscreenBand(id) | |||
| 1450 | + | } | |||
| 1447 | w.fullscreen = id | 1451 | w.fullscreen = id | ||
| 1448 | if !w.isFloat(id) { | 1452 | if !w.isFloat(id) { | ||
| 1449 | w.scroll() | 1453 | w.scroll() | ||
M internal/core/overview_cascade_test.go +3 -2
| @@ -728,7 +728,8 @@ func TestOverviewCascadeCoveringFloat(t *testing.T) { | |||
| 728 | 728 | } | |
| 729 | 729 | } | |
| 730 | 730 | ||
| 731 | - | // T10b: a pinned fullscreen column keeps its Fullscreen tile in the band. | |
| 731 | + | // T10b: a pinned fullscreen column keeps its Fullscreen tile, and the | |
| 732 | + | // columns of the other bands stay shown. | |
| 732 | 733 | func TestOverviewCascadePinnedFullscreenTile(t *testing.T) { | |
| 733 | 734 | m := cascadeOverview() | |
| 734 | 735 | w := m.Current() | |
| @@ -744,7 +745,7 @@ func TestOverviewCascadePinnedFullscreenTile(t *testing.T) { | |||
| 744 | 745 | t.Fatalf("fullscreen tile: %+v", fs) | |
| 745 | 746 | } | |
| 746 | 747 | for _, id := range []WindowID{7, 9} { | |
| 747 | - | if p := previewOf(t, ps, id); p.Hidden || p.Fullscreen || p.Peek != (fs.Peek) { | |
| 748 | + | if p := previewOf(t, ps, id); p.Hidden || p.Fullscreen { | |
| 748 | 749 | t.Fatalf("band tile %d: %+v", id, p) | |
| 749 | 750 | } | |
| 750 | 751 | } | |
| @@ -728,7 +728,8 @@ func TestOverviewCascadeCoveringFloat(t *testing.T) { | |||||
| 728 | } | 728 | } | ||
| 729 | } | 729 | } | ||
| 730 | 730 | ||||
| 731 | - | // T10b: a pinned fullscreen column keeps its Fullscreen tile in the band. | 731 | + | // T10b: a pinned fullscreen column keeps its Fullscreen tile, and the |
| 732 | + | // columns of the other bands stay shown. | |||
| 732 | func TestOverviewCascadePinnedFullscreenTile(t *testing.T) { | 733 | func TestOverviewCascadePinnedFullscreenTile(t *testing.T) { | ||
| 733 | m := cascadeOverview() | 734 | m := cascadeOverview() | ||
| 734 | w := m.Current() | 735 | w := m.Current() | ||
| @@ -744,7 +745,7 @@ func TestOverviewCascadePinnedFullscreenTile(t *testing.T) { | |||||
| 744 | t.Fatalf("fullscreen tile: %+v", fs) | 745 | t.Fatalf("fullscreen tile: %+v", fs) | ||
| 745 | } | 746 | } | ||
| 746 | for _, id := range []WindowID{7, 9} { | 747 | for _, id := range []WindowID{7, 9} { | ||
| 747 | - | if p := previewOf(t, ps, id); p.Hidden || p.Fullscreen || p.Peek != (fs.Peek) { | 748 | + | if p := previewOf(t, ps, id); p.Hidden || p.Fullscreen { |
| 748 | t.Fatalf("band tile %d: %+v", id, p) | 749 | t.Fatalf("band tile %d: %+v", id, p) | ||
| 749 | } | 750 | } | ||
| 750 | } | 751 | } | ||