fix(overview): separate drawn neighbor envelopes and lock swipe axes
- commit
- 6ee8ac0488b81f20f045e694cb7b51fcbda4f2db
- author
- brice · · 2026-10-06 14:32 UTC
- parent
- aee8623
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +53 -6
M internal/core/overview.go +53 -6
| @@ -490,9 +490,8 @@ func (m *Monitor) OverviewPick(id WindowID) { | |||
| 490 | 490 | m.closeOverview() | |
| 491 | 491 | } | |
| 492 | 492 | ||
| 493 | - | // overviewRows centers the selected row with its neighbors above and below. | |
| 494 | - | // The numbered/named boundary gets extra spacing and an inactive horizontal | |
| 495 | - | // rule. Row widths, stash piles and preview scales remain unchanged. | |
| 493 | + | // overviewRows places neighboring workspaces along the current policy axis. | |
| 494 | + | // Named boundaries reserve extra spacing and a perpendicular divider. | |
| 496 | 495 | func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) { | |
| 497 | 496 | cur := m.Current() | |
| 498 | 497 | u := cur.overviewArea() | |
| @@ -552,6 +551,12 @@ func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) { | |||
| 552 | 551 | func (m *Monitor) overviewLayout(result []Placement) []Placement { | |
| 553 | 552 | cur := m.Current() | |
| 554 | 553 | rows, _ := m.overviewRows() | |
| 554 | + | type group struct { | |
| 555 | + | w *Workspace | |
| 556 | + | start, end int | |
| 557 | + | } | |
| 558 | + | var groups [3]group | |
| 559 | + | count := 0 | |
| 555 | 560 | for w := range m.all() { | |
| 556 | 561 | ry, shown := rows[w] | |
| 557 | 562 | if shown { | |
| @@ -559,6 +564,8 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement { | |||
| 559 | 564 | start := len(result) | |
| 560 | 565 | result = append(result, m.stackRow(w, ry.Y, w != cur, at < 0)...) | |
| 561 | 566 | result = append(result, w.pile(ry.Y, w != cur, at)...) | |
| 567 | + | groups[count] = group{w, start, len(result)} | |
| 568 | + | count++ | |
| 562 | 569 | for i := start; i < len(result); i++ { | |
| 563 | 570 | if !result[i].Hidden { | |
| 564 | 571 | result[i].Rect.X += ry.X | |
| @@ -575,6 +582,46 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement { | |||
| 575 | 582 | result = w.appendHidden(result, false) | |
| 576 | 583 | } | |
| 577 | 584 | } | |
| 585 | + | if cur.policy().workspace == horizontalAxis { | |
| 586 | + | left, right := cur.overviewArea().X+cur.overviewArea().W, cur.overviewArea().X | |
| 587 | + | for _, g := range groups[:count] { | |
| 588 | + | if g.w != cur { | |
| 589 | + | continue | |
| 590 | + | } | |
| 591 | + | for _, p := range result[g.start:g.end] { | |
| 592 | + | if !p.Hidden { | |
| 593 | + | left = min(left, p.Rect.X) | |
| 594 | + | right = max(right, p.Rect.X+p.Rect.W) | |
| 595 | + | } | |
| 596 | + | } | |
| 597 | + | } | |
| 598 | + | gap := max(1, cur.overviewArea().H*3/100) | |
| 599 | + | for _, g := range groups[:count] { | |
| 600 | + | w, origin := g.w, rows[g.w] | |
| 601 | + | if w == cur { | |
| 602 | + | continue | |
| 603 | + | } | |
| 604 | + | lo, hi := int(^uint(0)>>1), -int(^uint(0)>>1) | |
| 605 | + | for _, p := range result[g.start:g.end] { | |
| 606 | + | if !p.Hidden { | |
| 607 | + | lo = min(lo, p.Rect.X) | |
| 608 | + | hi = max(hi, p.Rect.X+p.Rect.W) | |
| 609 | + | } | |
| 610 | + | } | |
| 611 | + | if lo > hi { | |
| 612 | + | continue | |
| 613 | + | } | |
| 614 | + | dx := right + gap - lo | |
| 615 | + | if origin.X < 0 { | |
| 616 | + | dx = left - gap - hi | |
| 617 | + | } | |
| 618 | + | for i := g.start; i < g.end; i++ { | |
| 619 | + | if !result[i].Hidden { | |
| 620 | + | result[i].Rect.X += dx | |
| 621 | + | } | |
| 622 | + | } | |
| 623 | + | } | |
| 624 | + | } | |
| 578 | 625 | return result | |
| 579 | 626 | } | |
| 580 | 627 | ||
| @@ -757,15 +804,13 @@ func (w *Workspace) rowHeight() int { | |||
| 757 | 804 | return h | |
| 758 | 805 | } | |
| 759 | 806 | ||
| 760 | - | // previewTiles lays tiles out unscrolled, relative to the usable area's | |
| 761 | - | // corner. Fixed overflow uses its on-screen geometry; scroll overflow uses | |
| 762 | - | // an unscrolled row. span is the row's width; sel is the focused column. | |
| 763 | 807 | // overviewPreview describes unscaled content independently of its presentation. | |
| 764 | 808 | type overviewPreview struct { | |
| 765 | 809 | bounds Rect | |
| 766 | 810 | selected Rect | |
| 767 | 811 | } | |
| 768 | 812 | ||
| 813 | + | // previewTiles lays out unscrolled tiles relative to the usable origin. | |
| 769 | 814 | func (w *Workspace) previewTiles() ([]Placement, overviewPreview) { | |
| 770 | 815 | return w.previewTilesInto(make([]Placement, 0, w.columnWindows())) | |
| 771 | 816 | } | |
| @@ -1068,6 +1113,8 @@ func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) { | |||
| 1068 | 1113 | axis := m.Current().policy().workspace | |
| 1069 | 1114 | m.OverviewMove(0, step) | |
| 1070 | 1115 | if axis != m.Current().policy().workspace { | |
| 1116 | + | m.ov.scrollX, m.ov.scrollY = 0, 0 | |
| 1117 | + | m.ov.scrolled = finger | |
| 1071 | 1118 | return true | |
| 1072 | 1119 | } | |
| 1073 | 1120 | m.ov.scrollY -= float64(step) * overviewScrollStep | |
| @@ -490,9 +490,8 @@ func (m *Monitor) OverviewPick(id WindowID) { | |||||
| 490 | m.closeOverview() | 490 | m.closeOverview() | ||
| 491 | } | 491 | } | ||
| 492 | 492 | ||||
| 493 | - | // overviewRows centers the selected row with its neighbors above and below. | 493 | + | // overviewRows places neighboring workspaces along the current policy axis. |
| 494 | - | // The numbered/named boundary gets extra spacing and an inactive horizontal | 494 | + | // Named boundaries reserve extra spacing and a perpendicular divider. |
| 495 | - | // rule. Row widths, stash piles and preview scales remain unchanged. | |||
| 496 | func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) { | 495 | func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) { | ||
| 497 | cur := m.Current() | 496 | cur := m.Current() | ||
| 498 | u := cur.overviewArea() | 497 | u := cur.overviewArea() | ||
| @@ -552,6 +551,12 @@ func (m *Monitor) overviewRows() (map[*Workspace]Rect, []ports.Separator) { | |||||
| 552 | func (m *Monitor) overviewLayout(result []Placement) []Placement { | 551 | func (m *Monitor) overviewLayout(result []Placement) []Placement { | ||
| 553 | cur := m.Current() | 552 | cur := m.Current() | ||
| 554 | rows, _ := m.overviewRows() | 553 | rows, _ := m.overviewRows() | ||
| 554 | + | type group struct { | |||
| 555 | + | w *Workspace | |||
| 556 | + | start, end int | |||
| 557 | + | } | |||
| 558 | + | var groups [3]group | |||
| 559 | + | count := 0 | |||
| 555 | for w := range m.all() { | 560 | for w := range m.all() { | ||
| 556 | ry, shown := rows[w] | 561 | ry, shown := rows[w] | ||
| 557 | if shown { | 562 | if shown { | ||
| @@ -559,6 +564,8 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement { | |||||
| 559 | start := len(result) | 564 | start := len(result) | ||
| 560 | result = append(result, m.stackRow(w, ry.Y, w != cur, at < 0)...) | 565 | result = append(result, m.stackRow(w, ry.Y, w != cur, at < 0)...) | ||
| 561 | result = append(result, w.pile(ry.Y, w != cur, at)...) | 566 | result = append(result, w.pile(ry.Y, w != cur, at)...) | ||
| 567 | + | groups[count] = group{w, start, len(result)} | |||
| 568 | + | count++ | |||
| 562 | for i := start; i < len(result); i++ { | 569 | for i := start; i < len(result); i++ { | ||
| 563 | if !result[i].Hidden { | 570 | if !result[i].Hidden { | ||
| 564 | result[i].Rect.X += ry.X | 571 | result[i].Rect.X += ry.X | ||
| @@ -575,6 +582,46 @@ func (m *Monitor) overviewLayout(result []Placement) []Placement { | |||||
| 575 | result = w.appendHidden(result, false) | 582 | result = w.appendHidden(result, false) | ||
| 576 | } | 583 | } | ||
| 577 | } | 584 | } | ||
| 585 | + | if cur.policy().workspace == horizontalAxis { | |||
| 586 | + | left, right := cur.overviewArea().X+cur.overviewArea().W, cur.overviewArea().X | |||
| 587 | + | for _, g := range groups[:count] { | |||
| 588 | + | if g.w != cur { | |||
| 589 | + | continue | |||
| 590 | + | } | |||
| 591 | + | for _, p := range result[g.start:g.end] { | |||
| 592 | + | if !p.Hidden { | |||
| 593 | + | left = min(left, p.Rect.X) | |||
| 594 | + | right = max(right, p.Rect.X+p.Rect.W) | |||
| 595 | + | } | |||
| 596 | + | } | |||
| 597 | + | } | |||
| 598 | + | gap := max(1, cur.overviewArea().H*3/100) | |||
| 599 | + | for _, g := range groups[:count] { | |||
| 600 | + | w, origin := g.w, rows[g.w] | |||
| 601 | + | if w == cur { | |||
| 602 | + | continue | |||
| 603 | + | } | |||
| 604 | + | lo, hi := int(^uint(0)>>1), -int(^uint(0)>>1) | |||
| 605 | + | for _, p := range result[g.start:g.end] { | |||
| 606 | + | if !p.Hidden { | |||
| 607 | + | lo = min(lo, p.Rect.X) | |||
| 608 | + | hi = max(hi, p.Rect.X+p.Rect.W) | |||
| 609 | + | } | |||
| 610 | + | } | |||
| 611 | + | if lo > hi { | |||
| 612 | + | continue | |||
| 613 | + | } | |||
| 614 | + | dx := right + gap - lo | |||
| 615 | + | if origin.X < 0 { | |||
| 616 | + | dx = left - gap - hi | |||
| 617 | + | } | |||
| 618 | + | for i := g.start; i < g.end; i++ { | |||
| 619 | + | if !result[i].Hidden { | |||
| 620 | + | result[i].Rect.X += dx | |||
| 621 | + | } | |||
| 622 | + | } | |||
| 623 | + | } | |||
| 624 | + | } | |||
| 578 | return result | 625 | return result | ||
| 579 | } | 626 | } | ||
| 580 | 627 | ||||
| @@ -757,15 +804,13 @@ func (w *Workspace) rowHeight() int { | |||||
| 757 | return h | 804 | return h | ||
| 758 | } | 805 | } | ||
| 759 | 806 | ||||
| 760 | - | // previewTiles lays tiles out unscrolled, relative to the usable area's | |||
| 761 | - | // corner. Fixed overflow uses its on-screen geometry; scroll overflow uses | |||
| 762 | - | // an unscrolled row. span is the row's width; sel is the focused column. | |||
| 763 | // overviewPreview describes unscaled content independently of its presentation. | 807 | // overviewPreview describes unscaled content independently of its presentation. | ||
| 764 | type overviewPreview struct { | 808 | type overviewPreview struct { | ||
| 765 | bounds Rect | 809 | bounds Rect | ||
| 766 | selected Rect | 810 | selected Rect | ||
| 767 | } | 811 | } | ||
| 768 | 812 | ||||
| 813 | + | // previewTiles lays out unscrolled tiles relative to the usable origin. | |||
| 769 | func (w *Workspace) previewTiles() ([]Placement, overviewPreview) { | 814 | func (w *Workspace) previewTiles() ([]Placement, overviewPreview) { | ||
| 770 | return w.previewTilesInto(make([]Placement, 0, w.columnWindows())) | 815 | return w.previewTilesInto(make([]Placement, 0, w.columnWindows())) | ||
| 771 | } | 816 | } | ||
| @@ -1068,6 +1113,8 @@ func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) { | |||||
| 1068 | axis := m.Current().policy().workspace | 1113 | axis := m.Current().policy().workspace | ||
| 1069 | m.OverviewMove(0, step) | 1114 | m.OverviewMove(0, step) | ||
| 1070 | if axis != m.Current().policy().workspace { | 1115 | if axis != m.Current().policy().workspace { | ||
| 1116 | + | m.ov.scrollX, m.ov.scrollY = 0, 0 | |||
| 1117 | + | m.ov.scrolled = finger | |||
| 1071 | return true | 1118 | return true | ||
| 1072 | } | 1119 | } | ||
| 1073 | m.ov.scrollY -= float64(step) * overviewScrollStep | 1120 | m.ov.scrollY -= float64(step) * overviewScrollStep | ||