fix(core): harden the fullscreen cascade band
Re-requests no longer re-split bands, the expelled column drops stale shares and slot only when the slot window left, the source workspace promotes its demoted floats after a fullscreen move, and restoreBand looks past an emptied band so maximize round-trips still restore.
- commit
- aa552bf9a14032dcd310ff2b88d92502b9213b2b
- author
- brice · · 2026-10-22 19:51 UTC
- parent
- c73000c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +137 -9
M internal/core/action.go +3 -1
| @@ -394,6 +394,7 @@ func (c *Core) dispatchAction(a Action) Effect { | |||
| 394 | 394 | from := c.cur() | |
| 395 | 395 | c.focusScreen = i | |
| 396 | 396 | moveFloat(w, c.cur().mon.Current(), fid) | |
| 397 | + | w.reconcileFloats() | |
| 397 | 398 | from.mon.normalize() | |
| 398 | 399 | c.cur().mon.normalize() | |
| 399 | 400 | } | |
| @@ -412,6 +413,7 @@ func (c *Core) dispatchAction(a Action) Effect { | |||
| 412 | 413 | if !ok { | |
| 413 | 414 | return Effect{} | |
| 414 | 415 | } | |
| 416 | + | w.reconcileFloats() // floats the fullscreen kept demoted show again | |
| 415 | 417 | from := c.cur() | |
| 416 | 418 | c.focusScreen = i | |
| 417 | 419 | to := c.cur().mon.Current() | |
| @@ -421,7 +423,7 @@ func (c *Core) dispatchAction(a Action) Effect { | |||
| 421 | 423 | } | |
| 422 | 424 | to.receive(col, at) | |
| 423 | 425 | if full { | |
| 424 | - | to.ToggleFullscreen() | |
| 426 | + | to.SetFullscreen(fid, true) | |
| 425 | 427 | } | |
| 426 | 428 | from.mon.normalize() | |
| 427 | 429 | c.cur().mon.normalize() | |
| @@ -394,6 +394,7 @@ func (c *Core) dispatchAction(a Action) Effect { | |||||
| 394 | from := c.cur() | 394 | from := c.cur() | ||
| 395 | c.focusScreen = i | 395 | c.focusScreen = i | ||
| 396 | moveFloat(w, c.cur().mon.Current(), fid) | 396 | moveFloat(w, c.cur().mon.Current(), fid) | ||
| 397 | + | w.reconcileFloats() | |||
| 397 | from.mon.normalize() | 398 | from.mon.normalize() | ||
| 398 | c.cur().mon.normalize() | 399 | c.cur().mon.normalize() | ||
| 399 | } | 400 | } | ||
| @@ -412,6 +413,7 @@ func (c *Core) dispatchAction(a Action) Effect { | |||||
| 412 | if !ok { | 413 | if !ok { | ||
| 413 | return Effect{} | 414 | return Effect{} | ||
| 414 | } | 415 | } | ||
| 416 | + | w.reconcileFloats() // floats the fullscreen kept demoted show again | |||
| 415 | from := c.cur() | 417 | from := c.cur() | ||
| 416 | c.focusScreen = i | 418 | c.focusScreen = i | ||
| 417 | to := c.cur().mon.Current() | 419 | to := c.cur().mon.Current() | ||
| @@ -421,7 +423,7 @@ func (c *Core) dispatchAction(a Action) Effect { | |||||
| 421 | } | 423 | } | ||
| 422 | to.receive(col, at) | 424 | to.receive(col, at) | ||
| 423 | if full { | 425 | if full { | ||
| 424 | - | to.ToggleFullscreen() | 426 | + | to.SetFullscreen(fid, true) |
| 425 | } | 427 | } | ||
| 426 | from.mon.normalize() | 428 | from.mon.normalize() | ||
| 427 | c.cur().mon.normalize() | 429 | c.cur().mon.normalize() | ||
M internal/core/cascade.go +19 -3
| @@ -438,18 +438,25 @@ func (w *Workspace) fullscreenBand(id WindowID) { | |||
| 438 | 438 | } | |
| 439 | 439 | fc := w.Columns[w.Focus] | |
| 440 | 440 | focus := fc.Windows[fc.Focus] | |
| 441 | - | w.dropSlotOf(id) | |
| 442 | 441 | bands := w.bandList() | |
| 443 | 442 | col := bands[b][i-first] | |
| 444 | 443 | nc := col | |
| 445 | 444 | if len(col.Windows) == 1 { | |
| 445 | + | // The column leaves its lane, as deleteColumn: manual widths of the | |
| 446 | + | // lane no longer add up. It keeps its slot and its own width. | |
| 447 | + | w.clearSpans(i) | |
| 446 | 448 | bands[b] = slices.Delete(bands[b], i-first, i-first+1) | |
| 447 | 449 | } else { | |
| 448 | 450 | j := slices.Index(col.Windows, id) | |
| 451 | + | // The expelled window gets a plain column: no Span (a manual width | |
| 452 | + | // is the stack's, and the new band is alone), no shares. | |
| 449 | 453 | nc = Column{Windows: []WindowID{id}, Cells: col.Cells} | |
| 450 | 454 | col.Windows = slices.Delete(slices.Clone(col.Windows), j, j+1) | |
| 451 | - | switch { | |
| 452 | - | case col.Focus > j, col.Focus >= len(col.Windows): | |
| 455 | + | col.Shares = nil | |
| 456 | + | if j == 0 { | |
| 457 | + | col.Slot = 0 // the slot window left | |
| 458 | + | } | |
| 459 | + | if col.Focus > j || col.Focus >= len(col.Windows) { | |
| 453 | 460 | col.Focus = max(min(col.Focus-1, len(col.Windows)-1), 0) | |
| 454 | 461 | } | |
| 455 | 462 | bands[b][i-first] = col | |
| @@ -533,8 +540,17 @@ func (w *Workspace) restoreBand() bool { | |||
| 533 | 540 | } | |
| 534 | 541 | return got | |
| 535 | 542 | } | |
| 543 | + | // A band the take left empty (a fullscreen column that split off its | |
| 544 | + | // own band meanwhile) is looked past: the remembered columns may lie | |
| 545 | + | // one band farther. | |
| 536 | 546 | above := take(b-1, up, true) | |
| 547 | + | if b >= 2 && len(bands[b-1]) == 0 { | |
| 548 | + | above = slices.Concat(take(b-2, up, true), above) | |
| 549 | + | } | |
| 537 | 550 | below := take(b+1, down, false) | |
| 551 | + | if b+2 < len(bands) && len(bands[b+1]) == 0 { | |
| 552 | + | below = slices.Concat(below, take(b+2, down, false)) | |
| 553 | + | } | |
| 538 | 554 | bands[b] = slices.Concat(above, bands[b], below) | |
| 539 | 555 | w.setBands(bands, id) | |
| 540 | 556 | return true | |
| @@ -438,18 +438,25 @@ func (w *Workspace) fullscreenBand(id WindowID) { | |||||
| 438 | } | 438 | } | ||
| 439 | fc := w.Columns[w.Focus] | 439 | fc := w.Columns[w.Focus] | ||
| 440 | focus := fc.Windows[fc.Focus] | 440 | focus := fc.Windows[fc.Focus] | ||
| 441 | - | w.dropSlotOf(id) | |||
| 442 | bands := w.bandList() | 441 | bands := w.bandList() | ||
| 443 | col := bands[b][i-first] | 442 | col := bands[b][i-first] | ||
| 444 | nc := col | 443 | nc := col | ||
| 445 | if len(col.Windows) == 1 { | 444 | if len(col.Windows) == 1 { | ||
| 445 | + | // The column leaves its lane, as deleteColumn: manual widths of the | |||
| 446 | + | // lane no longer add up. It keeps its slot and its own width. | |||
| 447 | + | w.clearSpans(i) | |||
| 446 | bands[b] = slices.Delete(bands[b], i-first, i-first+1) | 448 | bands[b] = slices.Delete(bands[b], i-first, i-first+1) | ||
| 447 | } else { | 449 | } else { | ||
| 448 | j := slices.Index(col.Windows, id) | 450 | j := slices.Index(col.Windows, id) | ||
| 451 | + | // The expelled window gets a plain column: no Span (a manual width | |||
| 452 | + | // is the stack's, and the new band is alone), no shares. | |||
| 449 | nc = Column{Windows: []WindowID{id}, Cells: col.Cells} | 453 | nc = Column{Windows: []WindowID{id}, Cells: col.Cells} | ||
| 450 | col.Windows = slices.Delete(slices.Clone(col.Windows), j, j+1) | 454 | col.Windows = slices.Delete(slices.Clone(col.Windows), j, j+1) | ||
| 451 | - | switch { | 455 | + | col.Shares = nil |
| 452 | - | case col.Focus > j, col.Focus >= len(col.Windows): | 456 | + | if j == 0 { |
| 457 | + | col.Slot = 0 // the slot window left | |||
| 458 | + | } | |||
| 459 | + | if col.Focus > j || col.Focus >= len(col.Windows) { | |||
| 453 | col.Focus = max(min(col.Focus-1, len(col.Windows)-1), 0) | 460 | col.Focus = max(min(col.Focus-1, len(col.Windows)-1), 0) | ||
| 454 | } | 461 | } | ||
| 455 | bands[b][i-first] = col | 462 | bands[b][i-first] = col | ||
| @@ -533,8 +540,17 @@ func (w *Workspace) restoreBand() bool { | |||||
| 533 | } | 540 | } | ||
| 534 | return got | 541 | return got | ||
| 535 | } | 542 | } | ||
| 543 | + | // A band the take left empty (a fullscreen column that split off its | |||
| 544 | + | // own band meanwhile) is looked past: the remembered columns may lie | |||
| 545 | + | // one band farther. | |||
| 536 | above := take(b-1, up, true) | 546 | above := take(b-1, up, true) | ||
| 547 | + | if b >= 2 && len(bands[b-1]) == 0 { | |||
| 548 | + | above = slices.Concat(take(b-2, up, true), above) | |||
| 549 | + | } | |||
| 537 | below := take(b+1, down, false) | 550 | below := take(b+1, down, false) | ||
| 551 | + | if b+2 < len(bands) && len(bands[b+1]) == 0 { | |||
| 552 | + | below = slices.Concat(below, take(b+2, down, false)) | |||
| 553 | + | } | |||
| 538 | bands[b] = slices.Concat(above, bands[b], below) | 554 | bands[b] = slices.Concat(above, bands[b], below) | ||
| 539 | w.setBands(bands, id) | 555 | w.setBands(bands, id) | ||
| 540 | return true | 556 | return true | ||
M internal/core/cascade_fullscreen_band_test.go +114 -4
| @@ -7,15 +7,15 @@ import "testing" | |||
| 7 | 7 | func TestCascadeFullscreenOwnBand(t *testing.T) { | |
| 8 | 8 | for _, tc := range []struct { | |
| 9 | 9 | name string | |
| 10 | - | do func(c *Core, w *Workspace) | |
| 10 | + | do func(w *Workspace) | |
| 11 | 11 | }{ | |
| 12 | - | {"toggle", func(_ *Core, w *Workspace) { w.ToggleFullscreen() }}, | |
| 13 | - | {"client request", func(_ *Core, w *Workspace) { w.SetFullscreen(3, true) }}, | |
| 12 | + | {"toggle", func(w *Workspace) { w.ToggleFullscreen() }}, | |
| 13 | + | {"client request", func(w *Workspace) { w.SetFullscreen(3, true) }}, | |
| 14 | 14 | } { | |
| 15 | 15 | t.Run(tc.name, func(t *testing.T) { | |
| 16 | 16 | c, w := cascadeMovesCore(t, 3, 3, false) | |
| 17 | 17 | wantBands(t, w, []WindowID{1, 2, 3}) | |
| 18 | - | tc.do(c, w) | |
| 18 | + | tc.do(w) | |
| 19 | 19 | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |
| 20 | 20 | wantFocus(t, c, "M", 3) | |
| 21 | 21 | if w.fullscreen != 3 { | |
| @@ -122,3 +122,113 @@ func TestFullscreenFloatNoNeighborStays(t *testing.T) { | |||
| 122 | 122 | t.Fatalf("float moved: %v fullscreen %d", w.Floats, w.fullscreen) | |
| 123 | 123 | } | |
| 124 | 124 | } | |
| 125 | + | ||
| 126 | + | func TestCascadeFullscreenRepeatedRequestKeepsBands(t *testing.T) { | |
| 127 | + | _, w := cascadeMovesCore(t, 3, 3, false) | |
| 128 | + | w.SetFullscreen(3, true) | |
| 129 | + | w.SetFullscreen(3, true) | |
| 130 | + | w.SetFullscreen(3, true) | |
| 131 | + | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | func TestCascadeFullscreenStackedFixups(t *testing.T) { | |
| 135 | + | for _, tc := range []struct { | |
| 136 | + | name string | |
| 137 | + | focus WindowID | |
| 138 | + | rest WindowID | |
| 139 | + | slotKept bool | |
| 140 | + | }{{"last row", 3, 2, true}, {"first row", 2, 3, false}} { | |
| 141 | + | t.Run(tc.name, func(t *testing.T) { | |
| 142 | + | c, w := cascadeMovesCore(t, 3, 3, false) | |
| 143 | + | w.FocusID(3) | |
| 144 | + | c.applyAction(ActionConsumeOrExpelLeft) // 3 stacks under 2 | |
| 145 | + | w.FocusID(tc.focus) | |
| 146 | + | w.Columns[1].Shares = []int{5000, 5000} | |
| 147 | + | w.Columns[1].Span = 3000 | |
| 148 | + | w.Columns[1].Slot = 4 | |
| 149 | + | w.SetFullscreen(tc.focus, true) | |
| 150 | + | wantBands(t, w, []WindowID{1, tc.rest}, []WindowID{tc.focus}) | |
| 151 | + | col := w.Columns[1] | |
| 152 | + | if len(col.Windows) != 1 || col.Windows[0] != tc.rest || col.Shares != nil || col.Focus != 0 || (col.Slot == 4) != tc.slotKept { | |
| 153 | + | t.Fatalf("remaining column %+v", col) | |
| 154 | + | } | |
| 155 | + | if got, _ := w.Focused(); got != tc.focus { | |
| 156 | + | t.Fatalf("focus %d, want %d", got, tc.focus) | |
| 157 | + | } | |
| 158 | + | if w.Columns[2].Span != 0 || w.Columns[2].Slot != 0 { | |
| 159 | + | t.Fatalf("expelled column %+v", w.Columns[2]) | |
| 160 | + | } | |
| 161 | + | }) | |
| 162 | + | } | |
| 163 | + | } | |
| 164 | + | ||
| 165 | + | func TestCascadeFullscreenKeepsSlotOfIntactColumn(t *testing.T) { | |
| 166 | + | _, w := cascadeMovesCore(t, 3, 3, false) | |
| 167 | + | w.Columns[2].Slot = 4 | |
| 168 | + | w.ToggleFullscreen() | |
| 169 | + | if i := w.columnOf(3); w.Columns[i].Slot != 4 { | |
| 170 | + | t.Fatalf("slot %d, want 4", w.Columns[i].Slot) | |
| 171 | + | } | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | func TestCascadeFullscreenAfterMaximizeRestores(t *testing.T) { | |
| 175 | + | _, w := cascadeMovesCore(t, 3, 3, false) | |
| 176 | + | w.FocusID(1) | |
| 177 | + | w.ToggleFullWidth() | |
| 178 | + | before := columnOrder(w) | |
| 179 | + | w.FocusID(3) | |
| 180 | + | w.ToggleFullscreen() | |
| 181 | + | w.ToggleFullscreen() | |
| 182 | + | w.FocusID(1) | |
| 183 | + | w.ToggleFullWidth() | |
| 184 | + | t.Logf("before %v bands after %v", before, columnOrder(w)) | |
| 185 | + | wantBands(t, w, []WindowID{1, 2, 3}) | |
| 186 | + | } | |
| 187 | + | ||
| 188 | + | func TestFullscreenMoveEdges(t *testing.T) { | |
| 189 | + | t.Run("destination fullscreen elsewhere", func(t *testing.T) { | |
| 190 | + | c, w := cascadeMovesCore(t, 2, 3, true) | |
| 191 | + | dst := screenWorkspace(c, "R") | |
| 192 | + | dst.AddWindow(20) | |
| 193 | + | dst.SetFullscreen(20, true) | |
| 194 | + | w.ToggleFullscreen() | |
| 195 | + | c.applyAction(ActionMoveColumnRight) | |
| 196 | + | if dst.fullscreen != 2 { | |
| 197 | + | t.Fatalf("fullscreen %d, want 2", dst.fullscreen) | |
| 198 | + | } | |
| 199 | + | wantFocus(t, c, "R", 2) | |
| 200 | + | }) | |
| 201 | + | t.Run("scroll layout", func(t *testing.T) { | |
| 202 | + | c, w := cascadeMovesCore(t, 2, 3, true) | |
| 203 | + | w.Overflow = OverflowScroll | |
| 204 | + | w.ToggleFullscreen() | |
| 205 | + | c.applyAction(ActionMoveColumnLeft) | |
| 206 | + | dst := screenWorkspace(c, "L") | |
| 207 | + | wantColumns(t, dst, 2) | |
| 208 | + | if dst.fullscreen != 2 || w.fullscreen != 0 { | |
| 209 | + | t.Fatalf("fullscreen dst %d src %d", dst.fullscreen, w.fullscreen) | |
| 210 | + | } | |
| 211 | + | }) | |
| 212 | + | t.Run("source floats promoted", func(t *testing.T) { | |
| 213 | + | for _, float := range []bool{false, true} { | |
| 214 | + | c, w := cascadeMovesCore(t, 2, 3, true) | |
| 215 | + | w.AddFloating(9, 100, 100) | |
| 216 | + | w.SetFullscreen(9, true) | |
| 217 | + | if !float { | |
| 218 | + | w.FocusID(2) | |
| 219 | + | w.ToggleFullscreen() | |
| 220 | + | } | |
| 221 | + | demoted := w.Floats[0].below | |
| 222 | + | c.applyAction(ActionMoveColumnRight) | |
| 223 | + | if float { | |
| 224 | + | demoted = false | |
| 225 | + | } | |
| 226 | + | _ = demoted | |
| 227 | + | for _, f := range w.Floats { | |
| 228 | + | if f.below { | |
| 229 | + | t.Fatalf("float=%v: float %d still demoted", float, f.ID) | |
| 230 | + | } | |
| 231 | + | } | |
| 232 | + | } | |
| 233 | + | }) | |
| 234 | + | } | |
| @@ -7,15 +7,15 @@ import "testing" | |||||
| 7 | func TestCascadeFullscreenOwnBand(t *testing.T) { | 7 | func TestCascadeFullscreenOwnBand(t *testing.T) { | ||
| 8 | for _, tc := range []struct { | 8 | for _, tc := range []struct { | ||
| 9 | name string | 9 | name string | ||
| 10 | - | do func(c *Core, w *Workspace) | 10 | + | do func(w *Workspace) |
| 11 | }{ | 11 | }{ | ||
| 12 | - | {"toggle", func(_ *Core, w *Workspace) { w.ToggleFullscreen() }}, | 12 | + | {"toggle", func(w *Workspace) { w.ToggleFullscreen() }}, |
| 13 | - | {"client request", func(_ *Core, w *Workspace) { w.SetFullscreen(3, true) }}, | 13 | + | {"client request", func(w *Workspace) { w.SetFullscreen(3, true) }}, |
| 14 | } { | 14 | } { | ||
| 15 | t.Run(tc.name, func(t *testing.T) { | 15 | t.Run(tc.name, func(t *testing.T) { | ||
| 16 | c, w := cascadeMovesCore(t, 3, 3, false) | 16 | c, w := cascadeMovesCore(t, 3, 3, false) | ||
| 17 | wantBands(t, w, []WindowID{1, 2, 3}) | 17 | wantBands(t, w, []WindowID{1, 2, 3}) | ||
| 18 | - | tc.do(c, w) | 18 | + | tc.do(w) |
| 19 | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | 19 | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | ||
| 20 | wantFocus(t, c, "M", 3) | 20 | wantFocus(t, c, "M", 3) | ||
| 21 | if w.fullscreen != 3 { | 21 | if w.fullscreen != 3 { | ||
| @@ -122,3 +122,113 @@ func TestFullscreenFloatNoNeighborStays(t *testing.T) { | |||||
| 122 | t.Fatalf("float moved: %v fullscreen %d", w.Floats, w.fullscreen) | 122 | t.Fatalf("float moved: %v fullscreen %d", w.Floats, w.fullscreen) | ||
| 123 | } | 123 | } | ||
| 124 | } | 124 | } | ||
| 125 | + | ||||
| 126 | + | func TestCascadeFullscreenRepeatedRequestKeepsBands(t *testing.T) { | |||
| 127 | + | _, w := cascadeMovesCore(t, 3, 3, false) | |||
| 128 | + | w.SetFullscreen(3, true) | |||
| 129 | + | w.SetFullscreen(3, true) | |||
| 130 | + | w.SetFullscreen(3, true) | |||
| 131 | + | wantBands(t, w, []WindowID{1, 2}, []WindowID{3}) | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | func TestCascadeFullscreenStackedFixups(t *testing.T) { | |||
| 135 | + | for _, tc := range []struct { | |||
| 136 | + | name string | |||
| 137 | + | focus WindowID | |||
| 138 | + | rest WindowID | |||
| 139 | + | slotKept bool | |||
| 140 | + | }{{"last row", 3, 2, true}, {"first row", 2, 3, false}} { | |||
| 141 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 142 | + | c, w := cascadeMovesCore(t, 3, 3, false) | |||
| 143 | + | w.FocusID(3) | |||
| 144 | + | c.applyAction(ActionConsumeOrExpelLeft) // 3 stacks under 2 | |||
| 145 | + | w.FocusID(tc.focus) | |||
| 146 | + | w.Columns[1].Shares = []int{5000, 5000} | |||
| 147 | + | w.Columns[1].Span = 3000 | |||
| 148 | + | w.Columns[1].Slot = 4 | |||
| 149 | + | w.SetFullscreen(tc.focus, true) | |||
| 150 | + | wantBands(t, w, []WindowID{1, tc.rest}, []WindowID{tc.focus}) | |||
| 151 | + | col := w.Columns[1] | |||
| 152 | + | if len(col.Windows) != 1 || col.Windows[0] != tc.rest || col.Shares != nil || col.Focus != 0 || (col.Slot == 4) != tc.slotKept { | |||
| 153 | + | t.Fatalf("remaining column %+v", col) | |||
| 154 | + | } | |||
| 155 | + | if got, _ := w.Focused(); got != tc.focus { | |||
| 156 | + | t.Fatalf("focus %d, want %d", got, tc.focus) | |||
| 157 | + | } | |||
| 158 | + | if w.Columns[2].Span != 0 || w.Columns[2].Slot != 0 { | |||
| 159 | + | t.Fatalf("expelled column %+v", w.Columns[2]) | |||
| 160 | + | } | |||
| 161 | + | }) | |||
| 162 | + | } | |||
| 163 | + | } | |||
| 164 | + | ||||
| 165 | + | func TestCascadeFullscreenKeepsSlotOfIntactColumn(t *testing.T) { | |||
| 166 | + | _, w := cascadeMovesCore(t, 3, 3, false) | |||
| 167 | + | w.Columns[2].Slot = 4 | |||
| 168 | + | w.ToggleFullscreen() | |||
| 169 | + | if i := w.columnOf(3); w.Columns[i].Slot != 4 { | |||
| 170 | + | t.Fatalf("slot %d, want 4", w.Columns[i].Slot) | |||
| 171 | + | } | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | func TestCascadeFullscreenAfterMaximizeRestores(t *testing.T) { | |||
| 175 | + | _, w := cascadeMovesCore(t, 3, 3, false) | |||
| 176 | + | w.FocusID(1) | |||
| 177 | + | w.ToggleFullWidth() | |||
| 178 | + | before := columnOrder(w) | |||
| 179 | + | w.FocusID(3) | |||
| 180 | + | w.ToggleFullscreen() | |||
| 181 | + | w.ToggleFullscreen() | |||
| 182 | + | w.FocusID(1) | |||
| 183 | + | w.ToggleFullWidth() | |||
| 184 | + | t.Logf("before %v bands after %v", before, columnOrder(w)) | |||
| 185 | + | wantBands(t, w, []WindowID{1, 2, 3}) | |||
| 186 | + | } | |||
| 187 | + | ||||
| 188 | + | func TestFullscreenMoveEdges(t *testing.T) { | |||
| 189 | + | t.Run("destination fullscreen elsewhere", func(t *testing.T) { | |||
| 190 | + | c, w := cascadeMovesCore(t, 2, 3, true) | |||
| 191 | + | dst := screenWorkspace(c, "R") | |||
| 192 | + | dst.AddWindow(20) | |||
| 193 | + | dst.SetFullscreen(20, true) | |||
| 194 | + | w.ToggleFullscreen() | |||
| 195 | + | c.applyAction(ActionMoveColumnRight) | |||
| 196 | + | if dst.fullscreen != 2 { | |||
| 197 | + | t.Fatalf("fullscreen %d, want 2", dst.fullscreen) | |||
| 198 | + | } | |||
| 199 | + | wantFocus(t, c, "R", 2) | |||
| 200 | + | }) | |||
| 201 | + | t.Run("scroll layout", func(t *testing.T) { | |||
| 202 | + | c, w := cascadeMovesCore(t, 2, 3, true) | |||
| 203 | + | w.Overflow = OverflowScroll | |||
| 204 | + | w.ToggleFullscreen() | |||
| 205 | + | c.applyAction(ActionMoveColumnLeft) | |||
| 206 | + | dst := screenWorkspace(c, "L") | |||
| 207 | + | wantColumns(t, dst, 2) | |||
| 208 | + | if dst.fullscreen != 2 || w.fullscreen != 0 { | |||
| 209 | + | t.Fatalf("fullscreen dst %d src %d", dst.fullscreen, w.fullscreen) | |||
| 210 | + | } | |||
| 211 | + | }) | |||
| 212 | + | t.Run("source floats promoted", func(t *testing.T) { | |||
| 213 | + | for _, float := range []bool{false, true} { | |||
| 214 | + | c, w := cascadeMovesCore(t, 2, 3, true) | |||
| 215 | + | w.AddFloating(9, 100, 100) | |||
| 216 | + | w.SetFullscreen(9, true) | |||
| 217 | + | if !float { | |||
| 218 | + | w.FocusID(2) | |||
| 219 | + | w.ToggleFullscreen() | |||
| 220 | + | } | |||
| 221 | + | demoted := w.Floats[0].below | |||
| 222 | + | c.applyAction(ActionMoveColumnRight) | |||
| 223 | + | if float { | |||
| 224 | + | demoted = false | |||
| 225 | + | } | |||
| 226 | + | _ = demoted | |||
| 227 | + | for _, f := range w.Floats { | |||
| 228 | + | if f.below { | |||
| 229 | + | t.Fatalf("float=%v: float %d still demoted", float, f.ID) | |||
| 230 | + | } | |||
| 231 | + | } | |||
| 232 | + | } | |||
| 233 | + | }) | |||
| 234 | + | } | |||
M internal/core/layout.go +1 -1
| @@ -1445,7 +1445,7 @@ func (w *Workspace) SetFullscreen(id WindowID, on bool) { | |||
| 1445 | 1445 | // Another window covers: the override was for the old cover. | |
| 1446 | 1446 | w.dropStashOver() | |
| 1447 | 1447 | } | |
| 1448 | - | if !w.isFloat(id) { | |
| 1448 | + | if !w.isFloat(id) && w.fullscreen != id { | |
| 1449 | 1449 | w.fullscreenBand(id) | |
| 1450 | 1450 | } | |
| 1451 | 1451 | w.fullscreen = id | |
| @@ -1445,7 +1445,7 @@ func (w *Workspace) SetFullscreen(id WindowID, on bool) { | |||||
| 1445 | // Another window covers: the override was for the old cover. | 1445 | // Another window covers: the override was for the old cover. | ||
| 1446 | w.dropStashOver() | 1446 | w.dropStashOver() | ||
| 1447 | } | 1447 | } | ||
| 1448 | - | if !w.isFloat(id) { | 1448 | + | if !w.isFloat(id) && w.fullscreen != id { |
| 1449 | w.fullscreenBand(id) | 1449 | w.fullscreenBand(id) | ||
| 1450 | } | 1450 | } | ||
| 1451 | w.fullscreen = id | 1451 | w.fullscreen = id | ||