refactor(overview): place previews in policy-oriented slots
- commit
- 82d612827e9782933ed033bb88bdfbece2040922
- author
- brice · · 2026-10-07 05:34 UTC
- committer
- brice · · 2026-10-07 05:42 UTC
- parent
- 888535e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +79 -50
M internal/core/overview.go +60 -37
| @@ -363,10 +363,16 @@ func (m *Monitor) showOverview(w *Workspace) { | |||
| 363 | 363 | m.selectRow() | |
| 364 | 364 | } | |
| 365 | 365 | ||
| 366 | - | // OverviewMove shares navigation rules across keys, scrolls and swipes. | |
| 367 | - | // Vertical moves stay in a selected stash pile; left stays, and right | |
| 368 | - | // deselects it, even when the row has no main card. | |
| 366 | + | // OverviewMove shares navigation rules across keys, scrolls and swipes. It | |
| 367 | + | // is the physical entry point: it maps dx and dy to the overview's moves. | |
| 369 | 368 | func (m *Monitor) OverviewMove(dx, dy int) { | |
| 369 | + | m.overviewMoveVertical(dx, dy) | |
| 370 | + | } | |
| 371 | + | ||
| 372 | + | // overviewMoveVertical is the move of an overview whose workspaces stack | |
| 373 | + | // vertically. Vertical moves stay in a selected stash pile; left stays, and | |
| 374 | + | // right deselects it, even when the row has no main card. | |
| 375 | + | func (m *Monitor) overviewMoveVertical(dx, dy int) { | |
| 370 | 376 | if dy != 0 { | |
| 371 | 377 | m.overviewVertical(dy) | |
| 372 | 378 | } else if dx != 0 { | |
| @@ -383,8 +389,14 @@ func (m *Monitor) overviewVertical(dy int) { | |||
| 383 | 389 | if m.moveStack(-dy) { | |
| 384 | 390 | return | |
| 385 | 391 | } | |
| 392 | + | m.overviewStep(dy) | |
| 393 | + | } | |
| 394 | + | ||
| 395 | + | // overviewStep shows the workspace d rows from the current one; it stays | |
| 396 | + | // on the first or last row. | |
| 397 | + | func (m *Monitor) overviewStep(d int) { | |
| 386 | 398 | rows := m.overviewWorkspaces() | |
| 387 | - | i := indexOf(rows, w) + dy | |
| 399 | + | i := indexOf(rows, m.Current()) + d | |
| 388 | 400 | if i >= 0 && i < len(rows) { | |
| 389 | 401 | m.showOverview(rows[i]) | |
| 390 | 402 | } | |
| @@ -487,16 +499,22 @@ func (m *Monitor) OverviewPick(id WindowID) { | |||
| 487 | 499 | m.closeOverview() | |
| 488 | 500 | } | |
| 489 | 501 | ||
| 502 | + | // overviewAxis is the axis the overview lays workspaces out along, and the | |
| 503 | + | // stacked cards of a row fan along. | |
| 504 | + | func (m *Monitor) overviewAxis() layoutAxis { return verticalAxis } | |
| 505 | + | ||
| 490 | 506 | // overviewRows centers the selected row with its neighbors above and below. | |
| 491 | - | // The numbered/named boundary gets extra spacing and an inactive horizontal | |
| 492 | - | // rule. Row widths, stash piles and preview scales remain unchanged. | |
| 493 | - | func (m *Monitor) overviewRows() (map[*Workspace]int, []ports.Separator) { | |
| 507 | + | // Each shown workspace gets a slot, in output-local pixels, to place its | |
| 508 | + | // previews in. The numbered/named boundary gets extra spacing and an | |
| 509 | + | // inactive horizontal rule. Row widths, stash piles and preview scales | |
| 510 | + | // remain unchanged. | |
| 511 | + | func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) { | |
| 494 | 512 | cur := m.Current() | |
| 495 | 513 | u := cur.overviewArea() | |
| 496 | 514 | gap := max(1, u.H*3/100) | |
| 497 | 515 | hc := cur.rowHeight() | |
| 498 | 516 | y := u.Y + (u.H-hc)/2 | |
| 499 | - | rows := map[*Workspace]int{cur: y} | |
| 517 | + | rows := map[*Workspace]Rect{cur: {X: u.X, Y: y, W: u.W, H: hc}} | |
| 500 | 518 | var dividers []ports.Separator | |
| 501 | 519 | list := m.overviewWorkspaces() | |
| 502 | 520 | i := indexOf(list, cur) | |
| @@ -510,12 +528,12 @@ func (m *Monitor) overviewRows() (map[*Workspace]int, []ports.Separator) { | |||
| 510 | 528 | if boundary { | |
| 511 | 529 | spacing *= 2 | |
| 512 | 530 | } | |
| 513 | - | top := y + hc | |
| 531 | + | top, h := y+hc, w.rowHeight() | |
| 514 | 532 | if next < i { | |
| 515 | - | rows[w] = y - spacing - w.rowHeight() | |
| 533 | + | rows[w] = Rect{X: u.X, Y: y - spacing - h, W: u.W, H: h} | |
| 516 | 534 | top = y - spacing | |
| 517 | 535 | } else { | |
| 518 | - | rows[w] = y + hc + spacing | |
| 536 | + | rows[w] = Rect{X: u.X, Y: y + hc + spacing, W: u.W, H: h} | |
| 519 | 537 | } | |
| 520 | 538 | if boundary { | |
| 521 | 539 | margin := u.W * 4 / 100 | |
| @@ -531,12 +549,15 @@ func (m *Monitor) overviewRows() (map[*Workspace]int, []ports.Separator) { | |||
| 531 | 549 | func (m *Monitor) overviewLayout(result []Placement) []Placement { | |
| 532 | 550 | cur := m.Current() | |
| 533 | 551 | rows, _ := m.overviewRows() | |
| 552 | + | axis := m.overviewAxis() | |
| 534 | 553 | for w := range m.all() { | |
| 535 | - | ry, shown := rows[w] | |
| 554 | + | slot, shown := rows[w] | |
| 536 | 555 | if shown { | |
| 537 | 556 | at := m.cardAt(w) | |
| 538 | - | result = append(result, m.stackRow(w, ry, w != cur, at < 0)...) | |
| 539 | - | result = append(result, w.pile(ry, w != cur, at)...) | |
| 557 | + | result = append(result, m.stackRow(w, axis, slot, w != cur, at < 0)...) | |
| 558 | + | if len(w.Stash) > 0 { | |
| 559 | + | result = append(result, w.pile(w.frontSlot(axis, slot), w != cur, at)...) | |
| 560 | + | } | |
| 540 | 561 | } | |
| 541 | 562 | for _, f := range w.Floats { | |
| 542 | 563 | if _, drawn := w.itemOf(f.ID); !shown || !drawn { | |
| @@ -637,11 +658,11 @@ func (w *Workspace) cardStep() int { | |||
| 637 | 658 | return min(overviewCardStep, a.W/100, a.H/50) | |
| 638 | 659 | } | |
| 639 | 660 | ||
| 640 | - | // pile places the stash of w as cards on the left of the row whose top is | |
| 641 | - | // at y: entry front (the stash selection when -1) in front, the next ones | |
| 642 | - | // behind it, dimmed, the rest hidden. Only a front card of the current row | |
| 643 | - | // (dim false) with front set is selected. | |
| 644 | - | func (w *Workspace) pile(y int, dim bool, front int) []Placement { | |
| 661 | + | // pile places the stash of w as cards on the left of band, the front band | |
| 662 | + | // of its row (see frontSlot): entry front (the stash selection when -1) in | |
| 663 | + | // front, the next ones behind it, dimmed, the rest hidden. Only a front card | |
| 664 | + | // of the current row (dim false) with front set is selected. | |
| 665 | + | func (w *Workspace) pile(band Rect, dim bool, front int) []Placement { | |
| 645 | 666 | n := len(w.Stash) | |
| 646 | 667 | if n == 0 { | |
| 647 | 668 | return nil | |
| @@ -658,7 +679,7 @@ func (w *Workspace) pile(y int, dim bool, front int) []Placement { | |||
| 658 | 679 | rowH := int(math.Round(float64(w.Usable.H) * w.overviewZoom())) | |
| 659 | 680 | // Stack rows reserve two peek steps above the front card. Align the | |
| 660 | 681 | // stash with that front band, not with the top of its envelope. | |
| 661 | - | x0, y0 := u.X+u.W/100, w.frontRowY(y)+(rowH-ch)/2 | |
| 682 | + | x0, y0 := band.X+u.W/100, band.Y+(rowH-ch)/2 | |
| 662 | 683 | cards := make([][]Placement, n) | |
| 663 | 684 | for i := range w.Stash { | |
| 664 | 685 | cards[i] = []Placement{{ID: w.Stash[i].ID, Rect: Rect{X: x0, Y: y0, W: cw, H: ch}, Preview: overviewCardZoom, Focused: selected}} | |
| @@ -692,13 +713,14 @@ func (w *Workspace) peekStep() int { | |||
| 692 | 713 | return max(1, int(math.Round(float64(w.Usable.H)*w.overviewZoom()*overviewPeekStep))) | |
| 693 | 714 | } | |
| 694 | 715 | ||
| 695 | - | // frontRowY skips the space reserved for two cards peeking above the front. | |
| 696 | - | // A single-card row starts at y, as before. | |
| 697 | - | func (w *Workspace) frontRowY(y int) int { | |
| 716 | + | // frontSlot is the part of slot the front card of w takes: slot shifted | |
| 717 | + | // along axis past the room reserved for two cards peeking before it. A | |
| 718 | + | // single-card row keeps slot. | |
| 719 | + | func (w *Workspace) frontSlot(axis layoutAxis, slot Rect) Rect { | |
| 698 | 720 | if len(w.stack()) > 1 { | |
| 699 | - | return y + 2*w.peekStep() | |
| 721 | + | axis.offset(&slot, 2*w.peekStep()) | |
| 700 | 722 | } | |
| 701 | - | return y | |
| 723 | + | return slot | |
| 702 | 724 | } | |
| 703 | 725 | ||
| 704 | 726 | // rowHeight includes the largest possible stack envelope (two peeks above | |
| @@ -782,29 +804,30 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||
| 782 | 804 | return tiles, span, sel | |
| 783 | 805 | } | |
| 784 | 806 | ||
| 785 | - | // previewRow scales the tiles of w into a row whose top is at y, centred | |
| 786 | - | // in the usable width, or scrolled so the focused column shows when the | |
| 807 | + | // previewRow scales the tiles of w into a row placed in slot, centred | |
| 808 | + | // in the slot's width, or scrolled so the focused column shows when the | |
| 787 | 809 | // row is wider, right of the stash pile. Dimmed rows (neighbor | |
| 788 | 810 | // workspaces) have no focus; neither has a row whose pile has it (lit | |
| 789 | 811 | // false). | |
| 790 | - | func (w *Workspace) previewRow(y int, dim, lit bool) []Placement { | |
| 812 | + | func (w *Workspace) previewRow(slot Rect, dim, lit bool) []Placement { | |
| 791 | 813 | tiles, span, sel := w.previewTiles() | |
| 792 | - | return w.previewRowTiles(y, dim, lit, tiles, span, sel) | |
| 814 | + | return w.previewRowTiles(slot, dim, lit, tiles, span, sel) | |
| 793 | 815 | } | |
| 794 | 816 | ||
| 795 | - | func (w *Workspace) previewRowTiles(y int, dim, lit bool, tiles []Placement, span int, sel Rect) []Placement { | |
| 796 | - | u := w.overviewArea() | |
| 817 | + | // previewRowTiles scales tiles, laid out unscrolled over span with sel the | |
| 818 | + | // focused one, into slot. Placement stays inside slot. | |
| 819 | + | func (w *Workspace) previewRowTiles(slot Rect, dim, lit bool, tiles []Placement, span int, sel Rect) []Placement { | |
| 797 | 820 | z := w.overviewZoom() | |
| 798 | 821 | scale := func(v int) int { return int(math.Round(float64(v) * z)) } | |
| 799 | 822 | width := scale(span) | |
| 800 | 823 | // Rows stay centred on the output like rows without a stash; the pile | |
| 801 | 824 | // only pushes a row right when they would overlap. | |
| 802 | - | x := max(u.X+(u.W-width)/2, u.X+w.pileWidth()) | |
| 803 | 825 | left := w.pileWidth() | |
| 804 | - | u.X, u.W = u.X+left, u.W-left | |
| 805 | - | if width > u.W { | |
| 806 | - | x = u.X + u.W/2 - scale(sel.X+sel.W/2) | |
| 807 | - | x = min(max(x, u.X+u.W-width), u.X) | |
| 826 | + | x := max(slot.X+(slot.W-width)/2, slot.X+left) | |
| 827 | + | ux, uw := slot.X+left, slot.W-left | |
| 828 | + | if width > uw { | |
| 829 | + | x = ux + uw/2 - scale(sel.X+sel.W/2) | |
| 830 | + | x = min(max(x, ux+uw-width), ux) | |
| 808 | 831 | } | |
| 809 | 832 | for i := range tiles { | |
| 810 | 833 | if tiles[i].Hidden { | |
| @@ -812,7 +835,7 @@ func (w *Workspace) previewRowTiles(y int, dim, lit bool, tiles []Placement, spa | |||
| 812 | 835 | } | |
| 813 | 836 | r := tiles[i].Rect | |
| 814 | 837 | x0, y0 := scale(r.X), scale(r.Y) | |
| 815 | - | tiles[i].Rect = Rect{X: x + x0, Y: y + y0, W: scale(r.X+r.W) - x0, H: scale(r.Y+r.H) - y0} | |
| 838 | + | tiles[i].Rect = Rect{X: x + x0, Y: slot.Y + y0, W: scale(r.X+r.W) - x0, H: scale(r.Y+r.H) - y0} | |
| 816 | 839 | tiles[i].Preview = z | |
| 817 | 840 | if dim { | |
| 818 | 841 | tiles[i].peeking(1) | |
| @@ -363,10 +363,16 @@ func (m *Monitor) showOverview(w *Workspace) { | |||||
| 363 | m.selectRow() | 363 | m.selectRow() | ||
| 364 | } | 364 | } | ||
| 365 | 365 | ||||
| 366 | - | // OverviewMove shares navigation rules across keys, scrolls and swipes. | 366 | + | // OverviewMove shares navigation rules across keys, scrolls and swipes. It |
| 367 | - | // Vertical moves stay in a selected stash pile; left stays, and right | 367 | + | // is the physical entry point: it maps dx and dy to the overview's moves. |
| 368 | - | // deselects it, even when the row has no main card. | |||
| 369 | func (m *Monitor) OverviewMove(dx, dy int) { | 368 | func (m *Monitor) OverviewMove(dx, dy int) { | ||
| 369 | + | m.overviewMoveVertical(dx, dy) | |||
| 370 | + | } | |||
| 371 | + | ||||
| 372 | + | // overviewMoveVertical is the move of an overview whose workspaces stack | |||
| 373 | + | // vertically. Vertical moves stay in a selected stash pile; left stays, and | |||
| 374 | + | // right deselects it, even when the row has no main card. | |||
| 375 | + | func (m *Monitor) overviewMoveVertical(dx, dy int) { | |||
| 370 | if dy != 0 { | 376 | if dy != 0 { | ||
| 371 | m.overviewVertical(dy) | 377 | m.overviewVertical(dy) | ||
| 372 | } else if dx != 0 { | 378 | } else if dx != 0 { | ||
| @@ -383,8 +389,14 @@ func (m *Monitor) overviewVertical(dy int) { | |||||
| 383 | if m.moveStack(-dy) { | 389 | if m.moveStack(-dy) { | ||
| 384 | return | 390 | return | ||
| 385 | } | 391 | } | ||
| 392 | + | m.overviewStep(dy) | |||
| 393 | + | } | |||
| 394 | + | ||||
| 395 | + | // overviewStep shows the workspace d rows from the current one; it stays | |||
| 396 | + | // on the first or last row. | |||
| 397 | + | func (m *Monitor) overviewStep(d int) { | |||
| 386 | rows := m.overviewWorkspaces() | 398 | rows := m.overviewWorkspaces() | ||
| 387 | - | i := indexOf(rows, w) + dy | 399 | + | i := indexOf(rows, m.Current()) + d |
| 388 | if i >= 0 && i < len(rows) { | 400 | if i >= 0 && i < len(rows) { | ||
| 389 | m.showOverview(rows[i]) | 401 | m.showOverview(rows[i]) | ||
| 390 | } | 402 | } | ||
| @@ -487,16 +499,22 @@ func (m *Monitor) OverviewPick(id WindowID) { | |||||
| 487 | m.closeOverview() | 499 | m.closeOverview() | ||
| 488 | } | 500 | } | ||
| 489 | 501 | ||||
| 502 | + | // overviewAxis is the axis the overview lays workspaces out along, and the | |||
| 503 | + | // stacked cards of a row fan along. | |||
| 504 | + | func (m *Monitor) overviewAxis() layoutAxis { return verticalAxis } | |||
| 505 | + | ||||
| 490 | // overviewRows centers the selected row with its neighbors above and below. | 506 | // overviewRows centers the selected row with its neighbors above and below. | ||
| 491 | - | // The numbered/named boundary gets extra spacing and an inactive horizontal | 507 | + | // Each shown workspace gets a slot, in output-local pixels, to place its |
| 492 | - | // rule. Row widths, stash piles and preview scales remain unchanged. | 508 | + | // previews in. The numbered/named boundary gets extra spacing and an |
| 493 | - | func (m *Monitor) overviewRows() (map[*Workspace]int, []ports.Separator) { | 509 | + | // inactive horizontal rule. Row widths, stash piles and preview scales |
| 510 | + | // remain unchanged. | |||
| 511 | + | func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) { | |||
| 494 | cur := m.Current() | 512 | cur := m.Current() | ||
| 495 | u := cur.overviewArea() | 513 | u := cur.overviewArea() | ||
| 496 | gap := max(1, u.H*3/100) | 514 | gap := max(1, u.H*3/100) | ||
| 497 | hc := cur.rowHeight() | 515 | hc := cur.rowHeight() | ||
| 498 | y := u.Y + (u.H-hc)/2 | 516 | y := u.Y + (u.H-hc)/2 | ||
| 499 | - | rows := map[*Workspace]int{cur: y} | 517 | + | rows := map[*Workspace]Rect{cur: {X: u.X, Y: y, W: u.W, H: hc}} |
| 500 | var dividers []ports.Separator | 518 | var dividers []ports.Separator | ||
| 501 | list := m.overviewWorkspaces() | 519 | list := m.overviewWorkspaces() | ||
| 502 | i := indexOf(list, cur) | 520 | i := indexOf(list, cur) | ||
| @@ -510,12 +528,12 @@ func (m *Monitor) overviewRows() (map[*Workspace]int, []ports.Separator) { | |||||
| 510 | if boundary { | 528 | if boundary { | ||
| 511 | spacing *= 2 | 529 | spacing *= 2 | ||
| 512 | } | 530 | } | ||
| 513 | - | top := y + hc | 531 | + | top, h := y+hc, w.rowHeight() |
| 514 | if next < i { | 532 | if next < i { | ||
| 515 | - | rows[w] = y - spacing - w.rowHeight() | 533 | + | rows[w] = Rect{X: u.X, Y: y - spacing - h, W: u.W, H: h} |
| 516 | top = y - spacing | 534 | top = y - spacing | ||
| 517 | } else { | 535 | } else { | ||
| 518 | - | rows[w] = y + hc + spacing | 536 | + | rows[w] = Rect{X: u.X, Y: y + hc + spacing, W: u.W, H: h} |
| 519 | } | 537 | } | ||
| 520 | if boundary { | 538 | if boundary { | ||
| 521 | margin := u.W * 4 / 100 | 539 | margin := u.W * 4 / 100 | ||
| @@ -531,12 +549,15 @@ func (m *Monitor) overviewRows() (map[*Workspace]int, []ports.Separator) { | |||||
| 531 | func (m *Monitor) overviewLayout(result []Placement) []Placement { | 549 | func (m *Monitor) overviewLayout(result []Placement) []Placement { | ||
| 532 | cur := m.Current() | 550 | cur := m.Current() | ||
| 533 | rows, _ := m.overviewRows() | 551 | rows, _ := m.overviewRows() | ||
| 552 | + | axis := m.overviewAxis() | |||
| 534 | for w := range m.all() { | 553 | for w := range m.all() { | ||
| 535 | - | ry, shown := rows[w] | 554 | + | slot, shown := rows[w] |
| 536 | if shown { | 555 | if shown { | ||
| 537 | at := m.cardAt(w) | 556 | at := m.cardAt(w) | ||
| 538 | - | result = append(result, m.stackRow(w, ry, w != cur, at < 0)...) | 557 | + | result = append(result, m.stackRow(w, axis, slot, w != cur, at < 0)...) |
| 539 | - | result = append(result, w.pile(ry, w != cur, at)...) | 558 | + | if len(w.Stash) > 0 { |
| 559 | + | result = append(result, w.pile(w.frontSlot(axis, slot), w != cur, at)...) | |||
| 560 | + | } | |||
| 540 | } | 561 | } | ||
| 541 | for _, f := range w.Floats { | 562 | for _, f := range w.Floats { | ||
| 542 | if _, drawn := w.itemOf(f.ID); !shown || !drawn { | 563 | if _, drawn := w.itemOf(f.ID); !shown || !drawn { | ||
| @@ -637,11 +658,11 @@ func (w *Workspace) cardStep() int { | |||||
| 637 | return min(overviewCardStep, a.W/100, a.H/50) | 658 | return min(overviewCardStep, a.W/100, a.H/50) | ||
| 638 | } | 659 | } | ||
| 639 | 660 | ||||
| 640 | - | // pile places the stash of w as cards on the left of the row whose top is | 661 | + | // pile places the stash of w as cards on the left of band, the front band |
| 641 | - | // at y: entry front (the stash selection when -1) in front, the next ones | 662 | + | // of its row (see frontSlot): entry front (the stash selection when -1) in |
| 642 | - | // behind it, dimmed, the rest hidden. Only a front card of the current row | 663 | + | // front, the next ones behind it, dimmed, the rest hidden. Only a front card |
| 643 | - | // (dim false) with front set is selected. | 664 | + | // of the current row (dim false) with front set is selected. |
| 644 | - | func (w *Workspace) pile(y int, dim bool, front int) []Placement { | 665 | + | func (w *Workspace) pile(band Rect, dim bool, front int) []Placement { |
| 645 | n := len(w.Stash) | 666 | n := len(w.Stash) | ||
| 646 | if n == 0 { | 667 | if n == 0 { | ||
| 647 | return nil | 668 | return nil | ||
| @@ -658,7 +679,7 @@ func (w *Workspace) pile(y int, dim bool, front int) []Placement { | |||||
| 658 | rowH := int(math.Round(float64(w.Usable.H) * w.overviewZoom())) | 679 | rowH := int(math.Round(float64(w.Usable.H) * w.overviewZoom())) | ||
| 659 | // Stack rows reserve two peek steps above the front card. Align the | 680 | // Stack rows reserve two peek steps above the front card. Align the | ||
| 660 | // stash with that front band, not with the top of its envelope. | 681 | // stash with that front band, not with the top of its envelope. | ||
| 661 | - | x0, y0 := u.X+u.W/100, w.frontRowY(y)+(rowH-ch)/2 | 682 | + | x0, y0 := band.X+u.W/100, band.Y+(rowH-ch)/2 |
| 662 | cards := make([][]Placement, n) | 683 | cards := make([][]Placement, n) | ||
| 663 | for i := range w.Stash { | 684 | for i := range w.Stash { | ||
| 664 | cards[i] = []Placement{{ID: w.Stash[i].ID, Rect: Rect{X: x0, Y: y0, W: cw, H: ch}, Preview: overviewCardZoom, Focused: selected}} | 685 | cards[i] = []Placement{{ID: w.Stash[i].ID, Rect: Rect{X: x0, Y: y0, W: cw, H: ch}, Preview: overviewCardZoom, Focused: selected}} | ||
| @@ -692,13 +713,14 @@ func (w *Workspace) peekStep() int { | |||||
| 692 | return max(1, int(math.Round(float64(w.Usable.H)*w.overviewZoom()*overviewPeekStep))) | 713 | return max(1, int(math.Round(float64(w.Usable.H)*w.overviewZoom()*overviewPeekStep))) | ||
| 693 | } | 714 | } | ||
| 694 | 715 | ||||
| 695 | - | // frontRowY skips the space reserved for two cards peeking above the front. | 716 | + | // frontSlot is the part of slot the front card of w takes: slot shifted |
| 696 | - | // A single-card row starts at y, as before. | 717 | + | // along axis past the room reserved for two cards peeking before it. A |
| 697 | - | func (w *Workspace) frontRowY(y int) int { | 718 | + | // single-card row keeps slot. |
| 719 | + | func (w *Workspace) frontSlot(axis layoutAxis, slot Rect) Rect { | |||
| 698 | if len(w.stack()) > 1 { | 720 | if len(w.stack()) > 1 { | ||
| 699 | - | return y + 2*w.peekStep() | 721 | + | axis.offset(&slot, 2*w.peekStep()) |
| 700 | } | 722 | } | ||
| 701 | - | return y | 723 | + | return slot |
| 702 | } | 724 | } | ||
| 703 | 725 | ||||
| 704 | // rowHeight includes the largest possible stack envelope (two peeks above | 726 | // rowHeight includes the largest possible stack envelope (two peeks above | ||
| @@ -782,29 +804,30 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||||
| 782 | return tiles, span, sel | 804 | return tiles, span, sel | ||
| 783 | } | 805 | } | ||
| 784 | 806 | ||||
| 785 | - | // previewRow scales the tiles of w into a row whose top is at y, centred | 807 | + | // previewRow scales the tiles of w into a row placed in slot, centred |
| 786 | - | // in the usable width, or scrolled so the focused column shows when the | 808 | + | // in the slot's width, or scrolled so the focused column shows when the |
| 787 | // row is wider, right of the stash pile. Dimmed rows (neighbor | 809 | // row is wider, right of the stash pile. Dimmed rows (neighbor | ||
| 788 | // workspaces) have no focus; neither has a row whose pile has it (lit | 810 | // workspaces) have no focus; neither has a row whose pile has it (lit | ||
| 789 | // false). | 811 | // false). | ||
| 790 | - | func (w *Workspace) previewRow(y int, dim, lit bool) []Placement { | 812 | + | func (w *Workspace) previewRow(slot Rect, dim, lit bool) []Placement { |
| 791 | tiles, span, sel := w.previewTiles() | 813 | tiles, span, sel := w.previewTiles() | ||
| 792 | - | return w.previewRowTiles(y, dim, lit, tiles, span, sel) | 814 | + | return w.previewRowTiles(slot, dim, lit, tiles, span, sel) |
| 793 | } | 815 | } | ||
| 794 | 816 | ||||
| 795 | - | func (w *Workspace) previewRowTiles(y int, dim, lit bool, tiles []Placement, span int, sel Rect) []Placement { | 817 | + | // previewRowTiles scales tiles, laid out unscrolled over span with sel the |
| 796 | - | u := w.overviewArea() | 818 | + | // focused one, into slot. Placement stays inside slot. |
| 819 | + | func (w *Workspace) previewRowTiles(slot Rect, dim, lit bool, tiles []Placement, span int, sel Rect) []Placement { | |||
| 797 | z := w.overviewZoom() | 820 | z := w.overviewZoom() | ||
| 798 | scale := func(v int) int { return int(math.Round(float64(v) * z)) } | 821 | scale := func(v int) int { return int(math.Round(float64(v) * z)) } | ||
| 799 | width := scale(span) | 822 | width := scale(span) | ||
| 800 | // Rows stay centred on the output like rows without a stash; the pile | 823 | // Rows stay centred on the output like rows without a stash; the pile | ||
| 801 | // only pushes a row right when they would overlap. | 824 | // only pushes a row right when they would overlap. | ||
| 802 | - | x := max(u.X+(u.W-width)/2, u.X+w.pileWidth()) | |||
| 803 | left := w.pileWidth() | 825 | left := w.pileWidth() | ||
| 804 | - | u.X, u.W = u.X+left, u.W-left | 826 | + | x := max(slot.X+(slot.W-width)/2, slot.X+left) |
| 805 | - | if width > u.W { | 827 | + | ux, uw := slot.X+left, slot.W-left |
| 806 | - | x = u.X + u.W/2 - scale(sel.X+sel.W/2) | 828 | + | if width > uw { |
| 807 | - | x = min(max(x, u.X+u.W-width), u.X) | 829 | + | x = ux + uw/2 - scale(sel.X+sel.W/2) |
| 830 | + | x = min(max(x, ux+uw-width), ux) | |||
| 808 | } | 831 | } | ||
| 809 | for i := range tiles { | 832 | for i := range tiles { | ||
| 810 | if tiles[i].Hidden { | 833 | if tiles[i].Hidden { | ||
| @@ -812,7 +835,7 @@ func (w *Workspace) previewRowTiles(y int, dim, lit bool, tiles []Placement, spa | |||||
| 812 | } | 835 | } | ||
| 813 | r := tiles[i].Rect | 836 | r := tiles[i].Rect | ||
| 814 | x0, y0 := scale(r.X), scale(r.Y) | 837 | x0, y0 := scale(r.X), scale(r.Y) | ||
| 815 | - | tiles[i].Rect = Rect{X: x + x0, Y: y + y0, W: scale(r.X+r.W) - x0, H: scale(r.Y+r.H) - y0} | 838 | + | tiles[i].Rect = Rect{X: x + x0, Y: slot.Y + y0, W: scale(r.X+r.W) - x0, H: scale(r.Y+r.H) - y0} |
| 816 | tiles[i].Preview = z | 839 | tiles[i].Preview = z | ||
| 817 | if dim { | 840 | if dim { | ||
| 818 | tiles[i].peeking(1) | 841 | tiles[i].peeking(1) | ||
M internal/core/overview_stack.go +19 -13
| @@ -234,10 +234,10 @@ func (w *Workspace) apply(item stackItem, selected WindowID) { | |||
| 234 | 234 | ||
| 235 | 235 | // previewItem draws a group using screen geometry and a single card at its | |
| 236 | 236 | // configured size. A prior maximized column retains its full-width buffer. | |
| 237 | - | func (m *Monitor) previewItem(w *Workspace, item stackItem, y int, dim, lit bool) []Placement { | |
| 237 | + | func (m *Monitor) previewItem(w *Workspace, item stackItem, slot Rect, dim, lit bool) []Placement { | |
| 238 | 238 | if item.kind == stackColumns { | |
| 239 | 239 | if !w.overviewMaximized() { | |
| 240 | - | return w.previewRow(y, dim, lit) | |
| 240 | + | return w.previewRow(slot, dim, lit) | |
| 241 | 241 | } | |
| 242 | 242 | // The hidden columns fill the screen as if the maximized column, | |
| 243 | 243 | // shown on its own card, were not there. In particular, an earlier | |
| @@ -260,20 +260,20 @@ func (m *Monitor) previewItem(w *Workspace, item stackItem, y int, dim, lit bool | |||
| 260 | 260 | } | |
| 261 | 261 | sel := rects[selected] | |
| 262 | 262 | sel.X -= w.Usable.X | |
| 263 | - | return w.previewRowTiles(y, dim, lit, tiles, w.Usable.W, sel) | |
| 263 | + | return w.previewRowTiles(slot, dim, lit, tiles, w.Usable.W, sel) | |
| 264 | 264 | } | |
| 265 | 265 | g := w.gap() | |
| 266 | 266 | if item.kind == stackFloat && item.id == w.cover() { | |
| 267 | 267 | // A fullscreen float shows at the size of a fullscreen column. | |
| 268 | 268 | r := Rect{X: g, Y: g, W: max(w.Usable.W-2*g, 0), H: max(w.Usable.H-2*g, 0)} | |
| 269 | - | return w.previewRowTiles(y, dim, lit, []Placement{{ID: item.id, Rect: r, Focused: true, Fullscreen: true}}, w.Usable.W, r) | |
| 269 | + | return w.previewRowTiles(slot, dim, lit, []Placement{{ID: item.id, Rect: r, Focused: true, Fullscreen: true}}, w.Usable.W, r) | |
| 270 | 270 | } | |
| 271 | 271 | if item.kind == stackFloat { | |
| 272 | 272 | f := w.Floats[w.floatIndex(item.id)] | |
| 273 | 273 | r := w.floatRect(f) | |
| 274 | 274 | r.X -= w.Usable.X | |
| 275 | 275 | r.Y -= w.Usable.Y | |
| 276 | - | return w.previewRowTiles(y, dim, lit, []Placement{{ID: item.id, Rect: r, Floating: true, Focused: true}}, w.Usable.W, r) | |
| 276 | + | return w.previewRowTiles(slot, dim, lit, []Placement{{ID: item.id, Rect: r, Floating: true, Focused: true}}, w.Usable.W, r) | |
| 277 | 277 | } | |
| 278 | 278 | i := w.columnOf(item.id) | |
| 279 | 279 | if i < 0 { | |
| @@ -288,7 +288,7 @@ func (m *Monitor) previewItem(w *Workspace, item stackItem, y int, dim, lit bool | |||
| 288 | 288 | for j, t := range w.rowBuf { | |
| 289 | 289 | tiles = append(tiles, Placement{ID: c.Windows[j], Rect: t, Focused: j == c.Focus, Fullscreen: c.Windows[j] == w.cover()}) | |
| 290 | 290 | } | |
| 291 | - | return w.previewRowTiles(y, dim, lit, tiles, w.Usable.W, r) | |
| 291 | + | return w.previewRowTiles(slot, dim, lit, tiles, w.Usable.W, r) | |
| 292 | 292 | } | |
| 293 | 293 | ||
| 294 | 294 | // hiddenColumnRects lays out the columns behind the maximized one with the | |
| @@ -301,14 +301,15 @@ func (w *Workspace) hiddenColumnRects() []Rect { | |||
| 301 | 301 | return slices.Insert(v.columnRectsFor(true), w.Focus, Rect{}) | |
| 302 | 302 | } | |
| 303 | 303 | ||
| 304 | - | // stackRow draws up to two cards on each side of the provisional front. | |
| 305 | - | func (m *Monitor) stackRow(w *Workspace, y int, dim, lit bool) []Placement { | |
| 304 | + | // stackRow draws up to two cards on each side of the provisional front, | |
| 305 | + | // fanned along axis. | |
| 306 | + | func (m *Monitor) stackRow(w *Workspace, axis layoutAxis, slot Rect, dim, lit bool) []Placement { | |
| 306 | 307 | items := w.stack() | |
| 307 | 308 | if len(items) == 0 { | |
| 308 | - | return w.previewRow(y, dim, lit) | |
| 309 | + | return w.previewRow(slot, dim, lit) | |
| 309 | 310 | } | |
| 310 | 311 | if len(items) == 1 { | |
| 311 | - | return m.previewItem(w, items[0], y, dim, lit) | |
| 312 | + | return m.previewItem(w, items[0], slot, dim, lit) | |
| 312 | 313 | } | |
| 313 | 314 | at := 0 | |
| 314 | 315 | if w == m.Current() { | |
| @@ -316,12 +317,17 @@ func (m *Monitor) stackRow(w *Workspace, y int, dim, lit bool) []Placement { | |||
| 316 | 317 | } | |
| 317 | 318 | cards := make([][]Placement, len(items)) | |
| 318 | 319 | step := w.peekStep() | |
| 320 | + | // along is the offset of d steps on the overview axis. | |
| 321 | + | along := func(d int) (int, int) { | |
| 322 | + | var r Rect | |
| 323 | + | axis.offset(&r, d*step) | |
| 324 | + | return r.X, r.Y | |
| 325 | + | } | |
| 319 | 326 | for i, item := range items { | |
| 320 | - | cards[i] = m.previewItem(w, item, w.frontRowY(y), dim, lit) | |
| 327 | + | cards[i] = m.previewItem(w, item, w.frontSlot(axis, slot), dim, lit) | |
| 321 | 328 | } | |
| 322 | 329 | ||
| 323 | 330 | // fan's behind side advances through the linear stack. | |
| 324 | 331 | return fan(cards, at, 2, 2, false, | |
| 325 | - | func(d int) (int, int) { return 0, -d * step }, | |
| 326 | - | func(d int) (int, int) { return 0, d * step }, dim, lit) | |
| 332 | + | func(d int) (int, int) { return along(-d) }, along, dim, lit) | |
| 327 | 333 | } | |
| @@ -234,10 +234,10 @@ func (w *Workspace) apply(item stackItem, selected WindowID) { | |||||
| 234 | 234 | ||||
| 235 | // previewItem draws a group using screen geometry and a single card at its | 235 | // previewItem draws a group using screen geometry and a single card at its | ||
| 236 | // configured size. A prior maximized column retains its full-width buffer. | 236 | // configured size. A prior maximized column retains its full-width buffer. | ||
| 237 | - | func (m *Monitor) previewItem(w *Workspace, item stackItem, y int, dim, lit bool) []Placement { | 237 | + | func (m *Monitor) previewItem(w *Workspace, item stackItem, slot Rect, dim, lit bool) []Placement { |
| 238 | if item.kind == stackColumns { | 238 | if item.kind == stackColumns { | ||
| 239 | if !w.overviewMaximized() { | 239 | if !w.overviewMaximized() { | ||
| 240 | - | return w.previewRow(y, dim, lit) | 240 | + | return w.previewRow(slot, dim, lit) |
| 241 | } | 241 | } | ||
| 242 | // The hidden columns fill the screen as if the maximized column, | 242 | // The hidden columns fill the screen as if the maximized column, | ||
| 243 | // shown on its own card, were not there. In particular, an earlier | 243 | // shown on its own card, were not there. In particular, an earlier | ||
| @@ -260,20 +260,20 @@ func (m *Monitor) previewItem(w *Workspace, item stackItem, y int, dim, lit bool | |||||
| 260 | } | 260 | } | ||
| 261 | sel := rects[selected] | 261 | sel := rects[selected] | ||
| 262 | sel.X -= w.Usable.X | 262 | sel.X -= w.Usable.X | ||
| 263 | - | return w.previewRowTiles(y, dim, lit, tiles, w.Usable.W, sel) | 263 | + | return w.previewRowTiles(slot, dim, lit, tiles, w.Usable.W, sel) |
| 264 | } | 264 | } | ||
| 265 | g := w.gap() | 265 | g := w.gap() | ||
| 266 | if item.kind == stackFloat && item.id == w.cover() { | 266 | if item.kind == stackFloat && item.id == w.cover() { | ||
| 267 | // A fullscreen float shows at the size of a fullscreen column. | 267 | // A fullscreen float shows at the size of a fullscreen column. | ||
| 268 | r := Rect{X: g, Y: g, W: max(w.Usable.W-2*g, 0), H: max(w.Usable.H-2*g, 0)} | 268 | r := Rect{X: g, Y: g, W: max(w.Usable.W-2*g, 0), H: max(w.Usable.H-2*g, 0)} | ||
| 269 | - | return w.previewRowTiles(y, dim, lit, []Placement{{ID: item.id, Rect: r, Focused: true, Fullscreen: true}}, w.Usable.W, r) | 269 | + | return w.previewRowTiles(slot, dim, lit, []Placement{{ID: item.id, Rect: r, Focused: true, Fullscreen: true}}, w.Usable.W, r) |
| 270 | } | 270 | } | ||
| 271 | if item.kind == stackFloat { | 271 | if item.kind == stackFloat { | ||
| 272 | f := w.Floats[w.floatIndex(item.id)] | 272 | f := w.Floats[w.floatIndex(item.id)] | ||
| 273 | r := w.floatRect(f) | 273 | r := w.floatRect(f) | ||
| 274 | r.X -= w.Usable.X | 274 | r.X -= w.Usable.X | ||
| 275 | r.Y -= w.Usable.Y | 275 | r.Y -= w.Usable.Y | ||
| 276 | - | return w.previewRowTiles(y, dim, lit, []Placement{{ID: item.id, Rect: r, Floating: true, Focused: true}}, w.Usable.W, r) | 276 | + | return w.previewRowTiles(slot, dim, lit, []Placement{{ID: item.id, Rect: r, Floating: true, Focused: true}}, w.Usable.W, r) |
| 277 | } | 277 | } | ||
| 278 | i := w.columnOf(item.id) | 278 | i := w.columnOf(item.id) | ||
| 279 | if i < 0 { | 279 | if i < 0 { | ||
| @@ -288,7 +288,7 @@ func (m *Monitor) previewItem(w *Workspace, item stackItem, y int, dim, lit bool | |||||
| 288 | for j, t := range w.rowBuf { | 288 | for j, t := range w.rowBuf { | ||
| 289 | tiles = append(tiles, Placement{ID: c.Windows[j], Rect: t, Focused: j == c.Focus, Fullscreen: c.Windows[j] == w.cover()}) | 289 | tiles = append(tiles, Placement{ID: c.Windows[j], Rect: t, Focused: j == c.Focus, Fullscreen: c.Windows[j] == w.cover()}) | ||
| 290 | } | 290 | } | ||
| 291 | - | return w.previewRowTiles(y, dim, lit, tiles, w.Usable.W, r) | 291 | + | return w.previewRowTiles(slot, dim, lit, tiles, w.Usable.W, r) |
| 292 | } | 292 | } | ||
| 293 | 293 | ||||
| 294 | // hiddenColumnRects lays out the columns behind the maximized one with the | 294 | // hiddenColumnRects lays out the columns behind the maximized one with the | ||
| @@ -301,14 +301,15 @@ func (w *Workspace) hiddenColumnRects() []Rect { | |||||
| 301 | return slices.Insert(v.columnRectsFor(true), w.Focus, Rect{}) | 301 | return slices.Insert(v.columnRectsFor(true), w.Focus, Rect{}) | ||
| 302 | } | 302 | } | ||
| 303 | 303 | ||||
| 304 | - | // stackRow draws up to two cards on each side of the provisional front. | 304 | + | // stackRow draws up to two cards on each side of the provisional front, |
| 305 | - | func (m *Monitor) stackRow(w *Workspace, y int, dim, lit bool) []Placement { | 305 | + | // fanned along axis. |
| 306 | + | func (m *Monitor) stackRow(w *Workspace, axis layoutAxis, slot Rect, dim, lit bool) []Placement { | |||
| 306 | items := w.stack() | 307 | items := w.stack() | ||
| 307 | if len(items) == 0 { | 308 | if len(items) == 0 { | ||
| 308 | - | return w.previewRow(y, dim, lit) | 309 | + | return w.previewRow(slot, dim, lit) |
| 309 | } | 310 | } | ||
| 310 | if len(items) == 1 { | 311 | if len(items) == 1 { | ||
| 311 | - | return m.previewItem(w, items[0], y, dim, lit) | 312 | + | return m.previewItem(w, items[0], slot, dim, lit) |
| 312 | } | 313 | } | ||
| 313 | at := 0 | 314 | at := 0 | ||
| 314 | if w == m.Current() { | 315 | if w == m.Current() { | ||
| @@ -316,12 +317,17 @@ func (m *Monitor) stackRow(w *Workspace, y int, dim, lit bool) []Placement { | |||||
| 316 | } | 317 | } | ||
| 317 | cards := make([][]Placement, len(items)) | 318 | cards := make([][]Placement, len(items)) | ||
| 318 | step := w.peekStep() | 319 | step := w.peekStep() | ||
| 320 | + | // along is the offset of d steps on the overview axis. | |||
| 321 | + | along := func(d int) (int, int) { | |||
| 322 | + | var r Rect | |||
| 323 | + | axis.offset(&r, d*step) | |||
| 324 | + | return r.X, r.Y | |||
| 325 | + | } | |||
| 319 | for i, item := range items { | 326 | for i, item := range items { | ||
| 320 | - | cards[i] = m.previewItem(w, item, w.frontRowY(y), dim, lit) | 327 | + | cards[i] = m.previewItem(w, item, w.frontSlot(axis, slot), dim, lit) |
| 321 | } | 328 | } | ||
| 322 | 329 | ||||
| 323 | // fan's behind side advances through the linear stack. | 330 | // fan's behind side advances through the linear stack. | ||
| 324 | return fan(cards, at, 2, 2, false, | 331 | return fan(cards, at, 2, 2, false, | ||
| 325 | - | func(d int) (int, int) { return 0, -d * step }, | 332 | + | func(d int) (int, int) { return along(-d) }, along, dim, lit) |
| 326 | - | func(d int) (int, int) { return 0, d * step }, dim, lit) | |||
| 327 | } | 333 | } | ||