fix(layout): windows joining a fullscreen workspace float then tile home, dialogs never steal focus or the link
- commit
- 3bc98dac890c459d79179210631900c162c42607
- author
- brice · · 2026-09-25 20:22 UTC
- parent
- 12f359f
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +78 -17
M internal/core/layout.go +3 -1
| @@ -145,8 +145,10 @@ type Workspace struct { | |||
| 145 | 145 | // origPlace remembers where a window was: its column, its row in a stacked | |
| 146 | 146 | // column, the column's width and slot, or its floating size. | |
| 147 | 147 | type origPlace struct { | |
| 148 | - | // id is the window that went fullscreen. | |
| 148 | + | // id is the window that went fullscreen; tiled lists the windows that | |
| 149 | + | // joined its workspace tiled, floating there until they go home. | |
| 149 | 150 | id WindowID | |
| 151 | + | tiled []WindowID | |
| 150 | 152 | col, row, slot int | |
| 151 | 153 | // stacked holds the windows left in the column: the window returns to | |
| 152 | 154 | // its row in the column that still holds one of them. | |
| @@ -145,8 +145,10 @@ type Workspace struct { | |||||
| 145 | // origPlace remembers where a window was: its column, its row in a stacked | 145 | // origPlace remembers where a window was: its column, its row in a stacked | ||
| 146 | // column, the column's width and slot, or its floating size. | 146 | // column, the column's width and slot, or its floating size. | ||
| 147 | type origPlace struct { | 147 | type origPlace struct { | ||
| 148 | - | // id is the window that went fullscreen. | 148 | + | // id is the window that went fullscreen; tiled lists the windows that |
| 149 | + | // joined its workspace tiled, floating there until they go home. | |||
| 149 | id WindowID | 150 | id WindowID | ||
| 151 | + | tiled []WindowID | |||
| 150 | col, row, slot int | 152 | col, row, slot int | ||
| 151 | // stacked holds the windows left in the column: the window returns to | 153 | // stacked holds the windows left in the column: the window returns to | ||
| 152 | // its row in the column that still holds one of them. | 154 | // its row in the column that still holds one of them. | ||
M internal/core/monitor.go +39 -8
| @@ -231,7 +231,7 @@ func (m *Monitor) AddWindow(id WindowID) { | |||
| 231 | 231 | return | |
| 232 | 232 | } | |
| 233 | 233 | if cur := m.Current(); cur.origin != nil { | |
| 234 | - | cur.AddFloating(id, 0, 0) | |
| 234 | + | cur.joinFullscreen(id) | |
| 235 | 235 | m.normalize() | |
| 236 | 236 | return | |
| 237 | 237 | } | |
| @@ -254,10 +254,10 @@ func (m *Monitor) RemoveWindow(id WindowID) { | |||
| 254 | 254 | if w, _ := m.find(id); w != nil { | |
| 255 | 255 | if w.origin != nil && id == w.back.id && m.has(w.origin) { | |
| 256 | 256 | // Its dialogs go home with the view. | |
| 257 | - | origin, at := w.origin, w.back.col | |
| 257 | + | origin, back := w.origin, w.back | |
| 258 | 258 | w.RemoveWindow(id) | |
| 259 | 259 | w.origin, w.back = nil, origPlace{} | |
| 260 | - | m.foldInto(w, origin, at, false) | |
| 260 | + | m.foldInto(w, origin, back.col, false, back.tiled) | |
| 261 | 261 | } else { | |
| 262 | 262 | w.RemoveWindow(id) | |
| 263 | 263 | } | |
| @@ -273,6 +273,9 @@ func (m *Monitor) SetFullscreen(id WindowID, on bool) { | |||
| 273 | 273 | w, _ := m.find(id) | |
| 274 | 274 | switch { | |
| 275 | 275 | case w == nil: | |
| 276 | + | case w.origin != nil && id != w.back.id: | |
| 277 | + | // A dialog of a fullscreen workspace already floats above it; its | |
| 278 | + | // own fullscreen would take the workspace from the window it holds. | |
| 276 | 279 | case on && m.ownWorkspace(w, id): | |
| 277 | 280 | focused, _ := w.Focused() | |
| 278 | 281 | m.enterFullscreen(w, id, w == m.Current() && focused == id) | |
| @@ -364,7 +367,7 @@ func (m *Monitor) leaveFullscreen(fs *Workspace, focus bool) { | |||
| 364 | 367 | origin, back, id := fs.origin, fs.back, fs.back.id | |
| 365 | 368 | fs.origin, fs.back = nil, origPlace{} | |
| 366 | 369 | fs.RemoveWindow(id) | |
| 367 | - | defer m.foldInto(fs, origin, back.col+1, focus) | |
| 370 | + | defer m.foldInto(fs, origin, back.col+1, focus, back.tiled) | |
| 368 | 371 | switch prev := origin.Focus; { | |
| 369 | 372 | case back.float != nil && focus: | |
| 370 | 373 | origin.AddFloating(id, back.float.W, back.float.H) | |
| @@ -405,7 +408,7 @@ func (m *Monitor) leaveFullscreen(fs *Workspace, focus bool) { | |||
| 405 | 408 | // foldInto moves what is left in fs (windows opened while fullscreen: its | |
| 406 | 409 | // dialogs) to origin, tiled ones from column at, floating ones on top, and | |
| 407 | 410 | // drops fs. The view goes along when fs was on screen. | |
| 408 | - | func (m *Monitor) foldInto(fs, origin *Workspace, at int, focus bool) { | |
| 411 | + | func (m *Monitor) foldInto(fs, origin *Workspace, at int, focus bool, tiled []WindowID) { | |
| 409 | 412 | shown := m.Current() == fs | |
| 410 | 413 | at = min(max(at, 0), len(origin.Columns)) | |
| 411 | 414 | for _, c := range slices.Backward(fs.Columns) { | |
| @@ -414,8 +417,25 @@ func (m *Monitor) foldInto(fs, origin *Workspace, at int, focus bool) { | |||
| 414 | 417 | origin.Focus++ | |
| 415 | 418 | } | |
| 416 | 419 | } | |
| 417 | - | origin.Floats = append(origin.Floats, fs.Floats...) | |
| 418 | - | origin.floatFocus = origin.floatFocus || len(fs.Floats) > 0 && (focus || shown) && fs.floatFocus | |
| 420 | + | var floats []Float | |
| 421 | + | for _, f := range fs.Floats { | |
| 422 | + | if !slices.Contains(tiled, f.ID) { | |
| 423 | + | floats = append(floats, f) | |
| 424 | + | continue | |
| 425 | + | } | |
| 426 | + | origin.Columns = slices.Insert(origin.Columns, at, Column{Windows: []WindowID{f.ID}}) | |
| 427 | + | if at <= origin.Focus && len(origin.Columns) > 1 { | |
| 428 | + | origin.Focus++ | |
| 429 | + | } | |
| 430 | + | at++ | |
| 431 | + | } | |
| 432 | + | if focus || shown { | |
| 433 | + | origin.Floats = append(origin.Floats, floats...) | |
| 434 | + | origin.floatFocus = origin.floatFocus || len(floats) > 0 && fs.floatFocus | |
| 435 | + | } else { | |
| 436 | + | // The user is elsewhere: below, the focused float keeps the focus. | |
| 437 | + | origin.Floats = append(floats, origin.Floats...) | |
| 438 | + | } | |
| 419 | 439 | fs.Columns, fs.Floats = nil, nil | |
| 420 | 440 | origin.scroll() | |
| 421 | 441 | m.take(fs) | |
| @@ -469,13 +489,24 @@ func (m *Monitor) MoveToWorkspace(i int) { | |||
| 469 | 489 | fl := cur.Floats[f] | |
| 470 | 490 | cur.RemoveWindow(id) | |
| 471 | 491 | m.Workspaces[i].AddFloating(id, fl.W, fl.H) | |
| 492 | + | } else if to := m.Workspaces[i]; to.origin != nil { | |
| 493 | + | cur.RemoveWindow(id) | |
| 494 | + | to.joinFullscreen(id) | |
| 472 | 495 | } else { | |
| 473 | 496 | cur.RemoveWindow(id) | |
| 474 | - | m.Workspaces[i].AddWindow(id) | |
| 497 | + | to.AddWindow(id) | |
| 475 | 498 | } | |
| 476 | 499 | m.normalize() | |
| 477 | 500 | } | |
| 478 | 501 | ||
| 502 | + | // joinFullscreen adds a tiled window to a fullscreen workspace fs: it floats | |
| 503 | + | // above the fullscreen window, which would hide it (fixed overflow), and | |
| 504 | + | // tiles again once home. | |
| 505 | + | func (fs *Workspace) joinFullscreen(id WindowID) { | |
| 506 | + | fs.AddFloating(id, 0, 0) | |
| 507 | + | fs.back.tiled = append(fs.back.tiled, id) | |
| 508 | + | } | |
| 509 | + | ||
| 479 | 510 | func (m *Monitor) Focused() (WindowID, bool) { return m.Current().Focused() } | |
| 480 | 511 | ||
| 481 | 512 | // Layout places every window: the workspace on screen laid out, others hidden. | |
| @@ -231,7 +231,7 @@ func (m *Monitor) AddWindow(id WindowID) { | |||||
| 231 | return | 231 | return | ||
| 232 | } | 232 | } | ||
| 233 | if cur := m.Current(); cur.origin != nil { | 233 | if cur := m.Current(); cur.origin != nil { | ||
| 234 | - | cur.AddFloating(id, 0, 0) | 234 | + | cur.joinFullscreen(id) |
| 235 | m.normalize() | 235 | m.normalize() | ||
| 236 | return | 236 | return | ||
| 237 | } | 237 | } | ||
| @@ -254,10 +254,10 @@ func (m *Monitor) RemoveWindow(id WindowID) { | |||||
| 254 | if w, _ := m.find(id); w != nil { | 254 | if w, _ := m.find(id); w != nil { | ||
| 255 | if w.origin != nil && id == w.back.id && m.has(w.origin) { | 255 | if w.origin != nil && id == w.back.id && m.has(w.origin) { | ||
| 256 | // Its dialogs go home with the view. | 256 | // Its dialogs go home with the view. | ||
| 257 | - | origin, at := w.origin, w.back.col | 257 | + | origin, back := w.origin, w.back |
| 258 | w.RemoveWindow(id) | 258 | w.RemoveWindow(id) | ||
| 259 | w.origin, w.back = nil, origPlace{} | 259 | w.origin, w.back = nil, origPlace{} | ||
| 260 | - | m.foldInto(w, origin, at, false) | 260 | + | m.foldInto(w, origin, back.col, false, back.tiled) |
| 261 | } else { | 261 | } else { | ||
| 262 | w.RemoveWindow(id) | 262 | w.RemoveWindow(id) | ||
| 263 | } | 263 | } | ||
| @@ -273,6 +273,9 @@ func (m *Monitor) SetFullscreen(id WindowID, on bool) { | |||||
| 273 | w, _ := m.find(id) | 273 | w, _ := m.find(id) | ||
| 274 | switch { | 274 | switch { | ||
| 275 | case w == nil: | 275 | case w == nil: | ||
| 276 | + | case w.origin != nil && id != w.back.id: | |||
| 277 | + | // A dialog of a fullscreen workspace already floats above it; its | |||
| 278 | + | // own fullscreen would take the workspace from the window it holds. | |||
| 276 | case on && m.ownWorkspace(w, id): | 279 | case on && m.ownWorkspace(w, id): | ||
| 277 | focused, _ := w.Focused() | 280 | focused, _ := w.Focused() | ||
| 278 | m.enterFullscreen(w, id, w == m.Current() && focused == id) | 281 | m.enterFullscreen(w, id, w == m.Current() && focused == id) | ||
| @@ -364,7 +367,7 @@ func (m *Monitor) leaveFullscreen(fs *Workspace, focus bool) { | |||||
| 364 | origin, back, id := fs.origin, fs.back, fs.back.id | 367 | origin, back, id := fs.origin, fs.back, fs.back.id | ||
| 365 | fs.origin, fs.back = nil, origPlace{} | 368 | fs.origin, fs.back = nil, origPlace{} | ||
| 366 | fs.RemoveWindow(id) | 369 | fs.RemoveWindow(id) | ||
| 367 | - | defer m.foldInto(fs, origin, back.col+1, focus) | 370 | + | defer m.foldInto(fs, origin, back.col+1, focus, back.tiled) |
| 368 | switch prev := origin.Focus; { | 371 | switch prev := origin.Focus; { | ||
| 369 | case back.float != nil && focus: | 372 | case back.float != nil && focus: | ||
| 370 | origin.AddFloating(id, back.float.W, back.float.H) | 373 | origin.AddFloating(id, back.float.W, back.float.H) | ||
| @@ -405,7 +408,7 @@ func (m *Monitor) leaveFullscreen(fs *Workspace, focus bool) { | |||||
| 405 | // foldInto moves what is left in fs (windows opened while fullscreen: its | 408 | // foldInto moves what is left in fs (windows opened while fullscreen: its | ||
| 406 | // dialogs) to origin, tiled ones from column at, floating ones on top, and | 409 | // dialogs) to origin, tiled ones from column at, floating ones on top, and | ||
| 407 | // drops fs. The view goes along when fs was on screen. | 410 | // drops fs. The view goes along when fs was on screen. | ||
| 408 | - | func (m *Monitor) foldInto(fs, origin *Workspace, at int, focus bool) { | 411 | + | func (m *Monitor) foldInto(fs, origin *Workspace, at int, focus bool, tiled []WindowID) { |
| 409 | shown := m.Current() == fs | 412 | shown := m.Current() == fs | ||
| 410 | at = min(max(at, 0), len(origin.Columns)) | 413 | at = min(max(at, 0), len(origin.Columns)) | ||
| 411 | for _, c := range slices.Backward(fs.Columns) { | 414 | for _, c := range slices.Backward(fs.Columns) { | ||
| @@ -414,8 +417,25 @@ func (m *Monitor) foldInto(fs, origin *Workspace, at int, focus bool) { | |||||
| 414 | origin.Focus++ | 417 | origin.Focus++ | ||
| 415 | } | 418 | } | ||
| 416 | } | 419 | } | ||
| 417 | - | origin.Floats = append(origin.Floats, fs.Floats...) | 420 | + | var floats []Float |
| 418 | - | origin.floatFocus = origin.floatFocus || len(fs.Floats) > 0 && (focus || shown) && fs.floatFocus | 421 | + | for _, f := range fs.Floats { |
| 422 | + | if !slices.Contains(tiled, f.ID) { | |||
| 423 | + | floats = append(floats, f) | |||
| 424 | + | continue | |||
| 425 | + | } | |||
| 426 | + | origin.Columns = slices.Insert(origin.Columns, at, Column{Windows: []WindowID{f.ID}}) | |||
| 427 | + | if at <= origin.Focus && len(origin.Columns) > 1 { | |||
| 428 | + | origin.Focus++ | |||
| 429 | + | } | |||
| 430 | + | at++ | |||
| 431 | + | } | |||
| 432 | + | if focus || shown { | |||
| 433 | + | origin.Floats = append(origin.Floats, floats...) | |||
| 434 | + | origin.floatFocus = origin.floatFocus || len(floats) > 0 && fs.floatFocus | |||
| 435 | + | } else { | |||
| 436 | + | // The user is elsewhere: below, the focused float keeps the focus. | |||
| 437 | + | origin.Floats = append(floats, origin.Floats...) | |||
| 438 | + | } | |||
| 419 | fs.Columns, fs.Floats = nil, nil | 439 | fs.Columns, fs.Floats = nil, nil | ||
| 420 | origin.scroll() | 440 | origin.scroll() | ||
| 421 | m.take(fs) | 441 | m.take(fs) | ||
| @@ -469,13 +489,24 @@ func (m *Monitor) MoveToWorkspace(i int) { | |||||
| 469 | fl := cur.Floats[f] | 489 | fl := cur.Floats[f] | ||
| 470 | cur.RemoveWindow(id) | 490 | cur.RemoveWindow(id) | ||
| 471 | m.Workspaces[i].AddFloating(id, fl.W, fl.H) | 491 | m.Workspaces[i].AddFloating(id, fl.W, fl.H) | ||
| 492 | + | } else if to := m.Workspaces[i]; to.origin != nil { | |||
| 493 | + | cur.RemoveWindow(id) | |||
| 494 | + | to.joinFullscreen(id) | |||
| 472 | } else { | 495 | } else { | ||
| 473 | cur.RemoveWindow(id) | 496 | cur.RemoveWindow(id) | ||
| 474 | - | m.Workspaces[i].AddWindow(id) | 497 | + | to.AddWindow(id) |
| 475 | } | 498 | } | ||
| 476 | m.normalize() | 499 | m.normalize() | ||
| 477 | } | 500 | } | ||
| 478 | 501 | ||||
| 502 | + | // joinFullscreen adds a tiled window to a fullscreen workspace fs: it floats | |||
| 503 | + | // above the fullscreen window, which would hide it (fixed overflow), and | |||
| 504 | + | // tiles again once home. | |||
| 505 | + | func (fs *Workspace) joinFullscreen(id WindowID) { | |||
| 506 | + | fs.AddFloating(id, 0, 0) | |||
| 507 | + | fs.back.tiled = append(fs.back.tiled, id) | |||
| 508 | + | } | |||
| 509 | + | ||||
| 479 | func (m *Monitor) Focused() (WindowID, bool) { return m.Current().Focused() } | 510 | func (m *Monitor) Focused() (WindowID, bool) { return m.Current().Focused() } | ||
| 480 | 511 | ||||
| 481 | // Layout places every window: the workspace on screen laid out, others hidden. | 512 | // Layout places every window: the workspace on screen laid out, others hidden. | ||
M internal/core/monitor_test.go +36 -8
| @@ -487,7 +487,7 @@ func TestMonitorFixedFullscreenEdges(t *testing.T) { | |||
| 487 | 487 | } | |
| 488 | 488 | return m | |
| 489 | 489 | } | |
| 490 | - | t.Run("a dialog opened there floats and follows it home", func(t *testing.T) { | |
| 490 | + | t.Run("a window opened there floats, then tiles after it at home", func(t *testing.T) { | |
| 491 | 491 | m := fixed() | |
| 492 | 492 | m.Current().FocusID(2) | |
| 493 | 493 | m.ToggleFullscreen() | |
| @@ -496,28 +496,56 @@ func TestMonitorFixedFullscreenEdges(t *testing.T) { | |||
| 496 | 496 | t.Fatal(fs.Floats, fs.fullscreen) | |
| 497 | 497 | } | |
| 498 | 498 | m.SetFullscreen(2, false) | |
| 499 | - | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) || m.Active != 0 || m.Current().floatIndex(4) != 0 { | |
| 500 | - | t.Fatal(got, m.Active, m.Current().Floats) | |
| 499 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 4, 3}, {}}) || m.Active != 0 { | |
| 500 | + | t.Fatal(got, m.Active) | |
| 501 | 501 | } | |
| 502 | 502 | }) | |
| 503 | 503 | t.Run("the bind from a dialog returns the fullscreen window", func(t *testing.T) { | |
| 504 | 504 | m := fixed() | |
| 505 | 505 | m.Current().FocusID(2) | |
| 506 | 506 | m.ToggleFullscreen() | |
| 507 | - | m.AddWindow(4) // focused dialog | |
| 507 | + | m.AddFloating(4, 10, 10) // focused dialog | |
| 508 | 508 | m.ToggleFullscreen() | |
| 509 | 509 | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) || m.Current().floatIndex(4) != 0 { | |
| 510 | 510 | t.Fatal(got, m.Current().Floats) | |
| 511 | 511 | } | |
| 512 | 512 | }) | |
| 513 | - | t.Run("closing it brings its dialogs home", func(t *testing.T) { | |
| 513 | + | t.Run("a dialog asking fullscreen keeps the link", func(t *testing.T) { | |
| 514 | 514 | m := fixed() | |
| 515 | 515 | m.Current().FocusID(2) | |
| 516 | 516 | m.ToggleFullscreen() | |
| 517 | - | m.AddWindow(4) | |
| 517 | + | m.AddFloating(4, 10, 10) | |
| 518 | + | m.SetFullscreen(4, true) | |
| 519 | + | m.SetFullscreen(4, false) | |
| 520 | + | m.ToggleFullscreen() | |
| 521 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) { | |
| 522 | + | t.Fatal(got) | |
| 523 | + | } | |
| 524 | + | }) | |
| 525 | + | t.Run("moved in by hand it floats", func(t *testing.T) { | |
| 526 | + | m := fixed() | |
| 527 | + | m.Current().FocusID(2) | |
| 528 | + | m.ToggleFullscreen() | |
| 529 | + | m.Apply(ActionFocusWorkspaceUp) | |
| 530 | + | m.Current().FocusID(1) | |
| 531 | + | m.Apply(ActionMoveToWorkspaceDown) | |
| 532 | + | if fs := m.Workspaces[1]; fs.floatIndex(1) != 0 || fs.origin == nil { | |
| 533 | + | t.Fatal(fs.Floats) | |
| 534 | + | } | |
| 535 | + | }) | |
| 536 | + | t.Run("closing it brings its dialogs home, below a focused float", func(t *testing.T) { | |
| 537 | + | m := fixed() | |
| 538 | + | m.Current().FocusID(2) | |
| 539 | + | m.ToggleFullscreen() | |
| 540 | + | m.AddFloating(4, 10, 10) | |
| 541 | + | m.Apply(ActionFocusWorkspaceUp) | |
| 542 | + | m.AddFloating(9, 10, 10) | |
| 518 | 543 | m.RemoveWindow(2) | |
| 519 | - | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 3}, {}}) || m.Active != 0 || m.Current().floatIndex(4) != 0 { | |
| 520 | - | t.Fatal(got, m.Active) | |
| 544 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 3}, {}}) || m.Current().floatIndex(4) != 0 { | |
| 545 | + | t.Fatal(got, m.Current().Floats) | |
| 546 | + | } | |
| 547 | + | if id, _ := m.Focused(); id != 9 { | |
| 548 | + | t.Fatal("focus", id) | |
| 521 | 549 | } | |
| 522 | 550 | }) | |
| 523 | 551 | t.Run("stack of three keeps its column", func(t *testing.T) { | |
| @@ -487,7 +487,7 @@ func TestMonitorFixedFullscreenEdges(t *testing.T) { | |||||
| 487 | } | 487 | } | ||
| 488 | return m | 488 | return m | ||
| 489 | } | 489 | } | ||
| 490 | - | t.Run("a dialog opened there floats and follows it home", func(t *testing.T) { | 490 | + | t.Run("a window opened there floats, then tiles after it at home", func(t *testing.T) { |
| 491 | m := fixed() | 491 | m := fixed() | ||
| 492 | m.Current().FocusID(2) | 492 | m.Current().FocusID(2) | ||
| 493 | m.ToggleFullscreen() | 493 | m.ToggleFullscreen() | ||
| @@ -496,28 +496,56 @@ func TestMonitorFixedFullscreenEdges(t *testing.T) { | |||||
| 496 | t.Fatal(fs.Floats, fs.fullscreen) | 496 | t.Fatal(fs.Floats, fs.fullscreen) | ||
| 497 | } | 497 | } | ||
| 498 | m.SetFullscreen(2, false) | 498 | m.SetFullscreen(2, false) | ||
| 499 | - | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) || m.Active != 0 || m.Current().floatIndex(4) != 0 { | 499 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 4, 3}, {}}) || m.Active != 0 { |
| 500 | - | t.Fatal(got, m.Active, m.Current().Floats) | 500 | + | t.Fatal(got, m.Active) |
| 501 | } | 501 | } | ||
| 502 | }) | 502 | }) | ||
| 503 | t.Run("the bind from a dialog returns the fullscreen window", func(t *testing.T) { | 503 | t.Run("the bind from a dialog returns the fullscreen window", func(t *testing.T) { | ||
| 504 | m := fixed() | 504 | m := fixed() | ||
| 505 | m.Current().FocusID(2) | 505 | m.Current().FocusID(2) | ||
| 506 | m.ToggleFullscreen() | 506 | m.ToggleFullscreen() | ||
| 507 | - | m.AddWindow(4) // focused dialog | 507 | + | m.AddFloating(4, 10, 10) // focused dialog |
| 508 | m.ToggleFullscreen() | 508 | m.ToggleFullscreen() | ||
| 509 | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) || m.Current().floatIndex(4) != 0 { | 509 | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) || m.Current().floatIndex(4) != 0 { | ||
| 510 | t.Fatal(got, m.Current().Floats) | 510 | t.Fatal(got, m.Current().Floats) | ||
| 511 | } | 511 | } | ||
| 512 | }) | 512 | }) | ||
| 513 | - | t.Run("closing it brings its dialogs home", func(t *testing.T) { | 513 | + | t.Run("a dialog asking fullscreen keeps the link", func(t *testing.T) { |
| 514 | m := fixed() | 514 | m := fixed() | ||
| 515 | m.Current().FocusID(2) | 515 | m.Current().FocusID(2) | ||
| 516 | m.ToggleFullscreen() | 516 | m.ToggleFullscreen() | ||
| 517 | - | m.AddWindow(4) | 517 | + | m.AddFloating(4, 10, 10) |
| 518 | + | m.SetFullscreen(4, true) | |||
| 519 | + | m.SetFullscreen(4, false) | |||
| 520 | + | m.ToggleFullscreen() | |||
| 521 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) { | |||
| 522 | + | t.Fatal(got) | |||
| 523 | + | } | |||
| 524 | + | }) | |||
| 525 | + | t.Run("moved in by hand it floats", func(t *testing.T) { | |||
| 526 | + | m := fixed() | |||
| 527 | + | m.Current().FocusID(2) | |||
| 528 | + | m.ToggleFullscreen() | |||
| 529 | + | m.Apply(ActionFocusWorkspaceUp) | |||
| 530 | + | m.Current().FocusID(1) | |||
| 531 | + | m.Apply(ActionMoveToWorkspaceDown) | |||
| 532 | + | if fs := m.Workspaces[1]; fs.floatIndex(1) != 0 || fs.origin == nil { | |||
| 533 | + | t.Fatal(fs.Floats) | |||
| 534 | + | } | |||
| 535 | + | }) | |||
| 536 | + | t.Run("closing it brings its dialogs home, below a focused float", func(t *testing.T) { | |||
| 537 | + | m := fixed() | |||
| 538 | + | m.Current().FocusID(2) | |||
| 539 | + | m.ToggleFullscreen() | |||
| 540 | + | m.AddFloating(4, 10, 10) | |||
| 541 | + | m.Apply(ActionFocusWorkspaceUp) | |||
| 542 | + | m.AddFloating(9, 10, 10) | |||
| 518 | m.RemoveWindow(2) | 543 | m.RemoveWindow(2) | ||
| 519 | - | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 3}, {}}) || m.Active != 0 || m.Current().floatIndex(4) != 0 { | 544 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 3}, {}}) || m.Current().floatIndex(4) != 0 { |
| 520 | - | t.Fatal(got, m.Active) | 545 | + | t.Fatal(got, m.Current().Floats) |
| 546 | + | } | |||
| 547 | + | if id, _ := m.Focused(); id != 9 { | |||
| 548 | + | t.Fatal("focus", id) | |||
| 521 | } | 549 | } | ||
| 522 | }) | 550 | }) | ||
| 523 | t.Run("stack of three keeps its column", func(t *testing.T) { | 551 | t.Run("stack of three keeps its column", func(t *testing.T) { | ||