fix(layout): normalize cascade covers on restore and reload
- commit
- 7609c0ef7955e4ddaa50a6e56f98ebce6de387de
- author
- brice · · 2026-10-06 08:12 UTC
- parent
- 8c17b30
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +72 -19
M internal/core/cascade.go +14 -7
| @@ -4,6 +4,15 @@ import "slices" | |||
| 4 | 4 | ||
| 5 | 5 | func (w *Workspace) band(i int) int { return i / max(w.MaxColumns, 1) } | |
| 6 | 6 | func (w *Workspace) sameBand(a, b int) bool { return !w.policy().wraps || w.band(a) == w.band(b) } | |
| 7 | + | func (w *Workspace) cellX(i int) int { | |
| 8 | + | return w.gap() + (i%max(w.MaxColumns, 1))*(w.cellWidth()+w.gap()) | |
| 9 | + | } | |
| 10 | + | func (w *Workspace) cascadeBands() (n, spacing int) { | |
| 11 | + | if len(w.Columns) == 0 || w.Usable.H <= 0 { | |
| 12 | + | return 1, 0 | |
| 13 | + | } | |
| 14 | + | return w.band(len(w.Columns)-1) + 1, w.Usable.H | |
| 15 | + | } | |
| 7 | 16 | func (w *Workspace) cellWidth() int { | |
| 8 | 17 | k := max(w.MaxColumns, 1) | |
| 9 | 18 | return max((w.Usable.W-w.gap()*(k+1))/k, 0) | |
| @@ -37,12 +46,12 @@ func (w *Workspace) bandNeighbor(dir int) int { | |||
| 37 | 46 | } | |
| 38 | 47 | ||
| 39 | 48 | func (w *Workspace) cascadeRectsInto(dst []Rect, ignoreFullWidth bool) []Rect { | |
| 40 | - | k, g := max(w.MaxColumns, 1), w.gap() | |
| 49 | + | g := w.gap() | |
| 41 | 50 | width := w.cellWidth() | |
| 42 | 51 | rects := slices.Grow(dst[:0], len(w.Columns))[:len(w.Columns)] | |
| 43 | 52 | view := w.View + w.shiftPixels() | |
| 44 | 53 | for i := range w.Columns { | |
| 45 | - | r := Rect{X: w.Usable.X + g + (i%k)*(width+g), Y: w.Usable.Y + g + w.band(i)*w.Usable.H - view, W: width, H: max(w.Usable.H-2*g, 0)} | |
| 54 | + | r := Rect{X: w.Usable.X + w.cellX(i), Y: w.Usable.Y + g + w.band(i)*w.Usable.H - view, W: width, H: max(w.Usable.H-2*g, 0)} | |
| 46 | 55 | if w.Columns[i].FullWidth && !ignoreFullWidth { | |
| 47 | 56 | r.X = w.Usable.X + g | |
| 48 | 57 | r.W = max(w.Usable.W-2*g, 0) | |
| @@ -53,12 +62,10 @@ func (w *Workspace) cascadeRectsInto(dst []Rect, ignoreFullWidth bool) []Rect { | |||
| 53 | 62 | } | |
| 54 | 63 | ||
| 55 | 64 | func (w *Workspace) cascadePoints() []float64 { | |
| 56 | - | if len(w.Columns) == 0 || w.Usable.H <= 0 { | |
| 57 | - | return []float64{0} | |
| 58 | - | } | |
| 59 | - | points := make([]float64, w.band(len(w.Columns)-1)+1) | |
| 65 | + | n, spacing := w.cascadeBands() | |
| 66 | + | points := make([]float64, n) | |
| 60 | 67 | for i := range points { | |
| 61 | - | points[i] = float64(i * w.Usable.H) | |
| 68 | + | points[i] = float64(i * spacing) | |
| 62 | 69 | } | |
| 63 | 70 | return points | |
| 64 | 71 | } | |
| @@ -4,6 +4,15 @@ import "slices" | |||||
| 4 | 4 | ||||
| 5 | func (w *Workspace) band(i int) int { return i / max(w.MaxColumns, 1) } | 5 | func (w *Workspace) band(i int) int { return i / max(w.MaxColumns, 1) } | ||
| 6 | func (w *Workspace) sameBand(a, b int) bool { return !w.policy().wraps || w.band(a) == w.band(b) } | 6 | func (w *Workspace) sameBand(a, b int) bool { return !w.policy().wraps || w.band(a) == w.band(b) } | ||
| 7 | + | func (w *Workspace) cellX(i int) int { | |||
| 8 | + | return w.gap() + (i%max(w.MaxColumns, 1))*(w.cellWidth()+w.gap()) | |||
| 9 | + | } | |||
| 10 | + | func (w *Workspace) cascadeBands() (n, spacing int) { | |||
| 11 | + | if len(w.Columns) == 0 || w.Usable.H <= 0 { | |||
| 12 | + | return 1, 0 | |||
| 13 | + | } | |||
| 14 | + | return w.band(len(w.Columns)-1) + 1, w.Usable.H | |||
| 15 | + | } | |||
| 7 | func (w *Workspace) cellWidth() int { | 16 | func (w *Workspace) cellWidth() int { | ||
| 8 | k := max(w.MaxColumns, 1) | 17 | k := max(w.MaxColumns, 1) | ||
| 9 | return max((w.Usable.W-w.gap()*(k+1))/k, 0) | 18 | return max((w.Usable.W-w.gap()*(k+1))/k, 0) | ||
| @@ -37,12 +46,12 @@ func (w *Workspace) bandNeighbor(dir int) int { | |||||
| 37 | } | 46 | } | ||
| 38 | 47 | ||||
| 39 | func (w *Workspace) cascadeRectsInto(dst []Rect, ignoreFullWidth bool) []Rect { | 48 | func (w *Workspace) cascadeRectsInto(dst []Rect, ignoreFullWidth bool) []Rect { | ||
| 40 | - | k, g := max(w.MaxColumns, 1), w.gap() | 49 | + | g := w.gap() |
| 41 | width := w.cellWidth() | 50 | width := w.cellWidth() | ||
| 42 | rects := slices.Grow(dst[:0], len(w.Columns))[:len(w.Columns)] | 51 | rects := slices.Grow(dst[:0], len(w.Columns))[:len(w.Columns)] | ||
| 43 | view := w.View + w.shiftPixels() | 52 | view := w.View + w.shiftPixels() | ||
| 44 | for i := range w.Columns { | 53 | for i := range w.Columns { | ||
| 45 | - | r := Rect{X: w.Usable.X + g + (i%k)*(width+g), Y: w.Usable.Y + g + w.band(i)*w.Usable.H - view, W: width, H: max(w.Usable.H-2*g, 0)} | 54 | + | r := Rect{X: w.Usable.X + w.cellX(i), Y: w.Usable.Y + g + w.band(i)*w.Usable.H - view, W: width, H: max(w.Usable.H-2*g, 0)} |
| 46 | if w.Columns[i].FullWidth && !ignoreFullWidth { | 55 | if w.Columns[i].FullWidth && !ignoreFullWidth { | ||
| 47 | r.X = w.Usable.X + g | 56 | r.X = w.Usable.X + g | ||
| 48 | r.W = max(w.Usable.W-2*g, 0) | 57 | r.W = max(w.Usable.W-2*g, 0) | ||
| @@ -53,12 +62,10 @@ func (w *Workspace) cascadeRectsInto(dst []Rect, ignoreFullWidth bool) []Rect { | |||||
| 53 | } | 62 | } | ||
| 54 | 63 | ||||
| 55 | func (w *Workspace) cascadePoints() []float64 { | 64 | func (w *Workspace) cascadePoints() []float64 { | ||
| 56 | - | if len(w.Columns) == 0 || w.Usable.H <= 0 { | 65 | + | n, spacing := w.cascadeBands() |
| 57 | - | return []float64{0} | 66 | + | points := make([]float64, n) |
| 58 | - | } | |||
| 59 | - | points := make([]float64, w.band(len(w.Columns)-1)+1) | |||
| 60 | for i := range points { | 67 | for i := range points { | ||
| 61 | - | points[i] = float64(i * w.Usable.H) | 68 | + | points[i] = float64(i * spacing) |
| 62 | } | 69 | } | ||
| 63 | return points | 70 | return points | ||
| 64 | } | 71 | } | ||
M internal/core/cascade_test.go +43 -0
| @@ -117,6 +117,49 @@ func TestCascadeClearsMaximizationOnBandChanges(t *testing.T) { | |||
| 117 | 117 | } | |
| 118 | 118 | } | |
| 119 | 119 | ||
| 120 | + | func TestCascadeReloadAndStashRestoreClearMaximization(t *testing.T) { | |
| 121 | + | w := cascadeWorkspace(3) | |
| 122 | + | w.FocusID(1) | |
| 123 | + | w.ToggleFullWidth() | |
| 124 | + | w.restore(4, &origPlace{stacked: []WindowID{2}, col: 1}) | |
| 125 | + | for _, c := range w.Columns { | |
| 126 | + | if c.FullWidth { | |
| 127 | + | t.Fatal("restore retained maximization") | |
| 128 | + | } | |
| 129 | + | } | |
| 130 | + | m := NewMonitor() | |
| 131 | + | m.SetOutput(900, 600) | |
| 132 | + | for i := WindowID(1); i <= 3; i++ { | |
| 133 | + | m.AddWindow(i) | |
| 134 | + | } | |
| 135 | + | m.Current().FocusID(1) | |
| 136 | + | m.Current().ToggleFullWidth() | |
| 137 | + | m.Current().FocusID(3) | |
| 138 | + | m.SetOverflow(OverflowCascade) | |
| 139 | + | for _, c := range m.Current().Columns { | |
| 140 | + | if c.FullWidth { | |
| 141 | + | t.Fatal("reload retained maximization") | |
| 142 | + | } | |
| 143 | + | } | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | func TestCascadeGestureValidityDoesNotAllocate(t *testing.T) { | |
| 147 | + | m := NewMonitor() | |
| 148 | + | m.SetOutput(900, 600) | |
| 149 | + | m.SetOverflow(OverflowCascade) | |
| 150 | + | for i := WindowID(1); i <= 7; i++ { | |
| 151 | + | m.AddWindow(i) | |
| 152 | + | } | |
| 153 | + | g := &swipeGesture{ws: m.Current(), points: m.Current().snapPoints(), horizontal: false, opens: m.overviewOpens} | |
| 154 | + | if n := testing.AllocsPerRun(100, func() { | |
| 155 | + | if g.columnsChanged(m) { | |
| 156 | + | t.Fatal("unchanged gesture invalidated") | |
| 157 | + | } | |
| 158 | + | }); n != 0 { | |
| 159 | + | t.Fatalf("allocations=%v", n) | |
| 160 | + | } | |
| 161 | + | } | |
| 162 | + | ||
| 120 | 163 | func TestCascadeGeometryReusesBuffer(t *testing.T) { | |
| 121 | 164 | w := cascadeWorkspace(7) | |
| 122 | 165 | buf := w.columnRectsInto(nil, false) | |
| @@ -117,6 +117,49 @@ func TestCascadeClearsMaximizationOnBandChanges(t *testing.T) { | |||||
| 117 | } | 117 | } | ||
| 118 | } | 118 | } | ||
| 119 | 119 | ||||
| 120 | + | func TestCascadeReloadAndStashRestoreClearMaximization(t *testing.T) { | |||
| 121 | + | w := cascadeWorkspace(3) | |||
| 122 | + | w.FocusID(1) | |||
| 123 | + | w.ToggleFullWidth() | |||
| 124 | + | w.restore(4, &origPlace{stacked: []WindowID{2}, col: 1}) | |||
| 125 | + | for _, c := range w.Columns { | |||
| 126 | + | if c.FullWidth { | |||
| 127 | + | t.Fatal("restore retained maximization") | |||
| 128 | + | } | |||
| 129 | + | } | |||
| 130 | + | m := NewMonitor() | |||
| 131 | + | m.SetOutput(900, 600) | |||
| 132 | + | for i := WindowID(1); i <= 3; i++ { | |||
| 133 | + | m.AddWindow(i) | |||
| 134 | + | } | |||
| 135 | + | m.Current().FocusID(1) | |||
| 136 | + | m.Current().ToggleFullWidth() | |||
| 137 | + | m.Current().FocusID(3) | |||
| 138 | + | m.SetOverflow(OverflowCascade) | |||
| 139 | + | for _, c := range m.Current().Columns { | |||
| 140 | + | if c.FullWidth { | |||
| 141 | + | t.Fatal("reload retained maximization") | |||
| 142 | + | } | |||
| 143 | + | } | |||
| 144 | + | } | |||
| 145 | + | ||||
| 146 | + | func TestCascadeGestureValidityDoesNotAllocate(t *testing.T) { | |||
| 147 | + | m := NewMonitor() | |||
| 148 | + | m.SetOutput(900, 600) | |||
| 149 | + | m.SetOverflow(OverflowCascade) | |||
| 150 | + | for i := WindowID(1); i <= 7; i++ { | |||
| 151 | + | m.AddWindow(i) | |||
| 152 | + | } | |||
| 153 | + | g := &swipeGesture{ws: m.Current(), points: m.Current().snapPoints(), horizontal: false, opens: m.overviewOpens} | |||
| 154 | + | if n := testing.AllocsPerRun(100, func() { | |||
| 155 | + | if g.columnsChanged(m) { | |||
| 156 | + | t.Fatal("unchanged gesture invalidated") | |||
| 157 | + | } | |||
| 158 | + | }); n != 0 { | |||
| 159 | + | t.Fatalf("allocations=%v", n) | |||
| 160 | + | } | |||
| 161 | + | } | |||
| 162 | + | ||||
| 120 | func TestCascadeGeometryReusesBuffer(t *testing.T) { | 163 | func TestCascadeGeometryReusesBuffer(t *testing.T) { | ||
| 121 | w := cascadeWorkspace(7) | 164 | w := cascadeWorkspace(7) | ||
| 122 | buf := w.columnRectsInto(nil, false) | 165 | buf := w.columnRectsInto(nil, false) | ||
M internal/core/gesture.go +7 -10
| @@ -102,15 +102,12 @@ func (g *swipeGesture) columnsChanged(m *Monitor) bool { | |||
| 102 | 102 | return true | |
| 103 | 103 | } | |
| 104 | 104 | if g.ws.policy().wraps { | |
| 105 | - | n := 1 | |
| 106 | - | if len(g.ws.Columns) > 0 && g.ws.Usable.H > 0 { | |
| 107 | - | n = g.ws.band(len(g.ws.Columns)-1) + 1 | |
| 108 | - | } | |
| 105 | + | n, spacing := g.ws.cascadeBands() | |
| 109 | 106 | if len(g.points) != n { | |
| 110 | 107 | return true | |
| 111 | 108 | } | |
| 112 | 109 | for i, p := range g.points { | |
| 113 | - | if p != float64(i*g.ws.Usable.H) { | |
| 110 | + | if p != float64(i*spacing) { | |
| 114 | 111 | return true | |
| 115 | 112 | } | |
| 116 | 113 | } | |
| @@ -424,10 +421,6 @@ func (c *Core) swipeEnd(e ports.SwipeEnd) (shown bool) { | |||
| 424 | 421 | if w.onFloat() || w.fullscreen != 0 || w.bandNeighbor(step) < 0 { | |
| 425 | 422 | return false | |
| 426 | 423 | } | |
| 427 | - | a = ActionFocusWindowDown | |
| 428 | - | if step < 0 { | |
| 429 | - | a = ActionFocusWindowUp | |
| 430 | - | } | |
| 431 | 424 | } | |
| 432 | 425 | // Focus actions act on the focused screen: the swipe's, unless the | |
| 433 | 426 | // pointer took the focus to another output meanwhile. | |
| @@ -440,7 +433,11 @@ func (c *Core) swipeEnd(e ports.SwipeEnd) (shown bool) { | |||
| 440 | 433 | before := c.cur().mon.Current() | |
| 441 | 434 | shots := c.snapshot(now) | |
| 442 | 435 | c.keyboard.takeBack() | |
| 443 | - | c.applyAction(a) | |
| 436 | + | if p.wraps && !workspace { | |
| 437 | + | w.focusBand(step) | |
| 438 | + | } else { | |
| 439 | + | c.applyAction(a) | |
| 440 | + | } | |
| 444 | 441 | c.transition(shots, now) | |
| 445 | 442 | return c.cur().mon.Current() != before | |
| 446 | 443 | } | |
| @@ -102,15 +102,12 @@ func (g *swipeGesture) columnsChanged(m *Monitor) bool { | |||||
| 102 | return true | 102 | return true | ||
| 103 | } | 103 | } | ||
| 104 | if g.ws.policy().wraps { | 104 | if g.ws.policy().wraps { | ||
| 105 | - | n := 1 | 105 | + | n, spacing := g.ws.cascadeBands() |
| 106 | - | if len(g.ws.Columns) > 0 && g.ws.Usable.H > 0 { | |||
| 107 | - | n = g.ws.band(len(g.ws.Columns)-1) + 1 | |||
| 108 | - | } | |||
| 109 | if len(g.points) != n { | 106 | if len(g.points) != n { | ||
| 110 | return true | 107 | return true | ||
| 111 | } | 108 | } | ||
| 112 | for i, p := range g.points { | 109 | for i, p := range g.points { | ||
| 113 | - | if p != float64(i*g.ws.Usable.H) { | 110 | + | if p != float64(i*spacing) { |
| 114 | return true | 111 | return true | ||
| 115 | } | 112 | } | ||
| 116 | } | 113 | } | ||
| @@ -424,10 +421,6 @@ func (c *Core) swipeEnd(e ports.SwipeEnd) (shown bool) { | |||||
| 424 | if w.onFloat() || w.fullscreen != 0 || w.bandNeighbor(step) < 0 { | 421 | if w.onFloat() || w.fullscreen != 0 || w.bandNeighbor(step) < 0 { | ||
| 425 | return false | 422 | return false | ||
| 426 | } | 423 | } | ||
| 427 | - | a = ActionFocusWindowDown | |||
| 428 | - | if step < 0 { | |||
| 429 | - | a = ActionFocusWindowUp | |||
| 430 | - | } | |||
| 431 | } | 424 | } | ||
| 432 | // Focus actions act on the focused screen: the swipe's, unless the | 425 | // Focus actions act on the focused screen: the swipe's, unless the | ||
| 433 | // pointer took the focus to another output meanwhile. | 426 | // pointer took the focus to another output meanwhile. | ||
| @@ -440,7 +433,11 @@ func (c *Core) swipeEnd(e ports.SwipeEnd) (shown bool) { | |||||
| 440 | before := c.cur().mon.Current() | 433 | before := c.cur().mon.Current() | ||
| 441 | shots := c.snapshot(now) | 434 | shots := c.snapshot(now) | ||
| 442 | c.keyboard.takeBack() | 435 | c.keyboard.takeBack() | ||
| 443 | - | c.applyAction(a) | 436 | + | if p.wraps && !workspace { |
| 437 | + | w.focusBand(step) | |||
| 438 | + | } else { | |||
| 439 | + | c.applyAction(a) | |||
| 440 | + | } | |||
| 444 | c.transition(shots, now) | 441 | c.transition(shots, now) | ||
| 445 | return c.cur().mon.Current() != before | 442 | return c.cur().mon.Current() != before | ||
| 446 | } | 443 | } | ||
M internal/core/monitor.go +4 -0
| @@ -510,10 +510,14 @@ func (m *Monitor) applyNamed() { | |||
| 510 | 510 | } | |
| 511 | 511 | m.each(func(w *Workspace) { | |
| 512 | 512 | s := specs[w.Name] | |
| 513 | + | before := w.Overflow | |
| 513 | 514 | w.Overflow = m.template.Overflow | |
| 514 | 515 | if s.Overflow != "" { | |
| 515 | 516 | w.Overflow = s.Overflow | |
| 516 | 517 | } | |
| 518 | + | if before != w.Overflow && w.policy().equalCells { | |
| 519 | + | w.unmaximize() | |
| 520 | + | } | |
| 517 | 521 | w.SetMaxColumns(m.template.MaxColumns) | |
| 518 | 522 | if s.MaxColumns > 0 { | |
| 519 | 523 | w.SetMaxColumns(s.MaxColumns) | |
| @@ -510,10 +510,14 @@ func (m *Monitor) applyNamed() { | |||||
| 510 | } | 510 | } | ||
| 511 | m.each(func(w *Workspace) { | 511 | m.each(func(w *Workspace) { | ||
| 512 | s := specs[w.Name] | 512 | s := specs[w.Name] | ||
| 513 | + | before := w.Overflow | |||
| 513 | w.Overflow = m.template.Overflow | 514 | w.Overflow = m.template.Overflow | ||
| 514 | if s.Overflow != "" { | 515 | if s.Overflow != "" { | ||
| 515 | w.Overflow = s.Overflow | 516 | w.Overflow = s.Overflow | ||
| 516 | } | 517 | } | ||
| 518 | + | if before != w.Overflow && w.policy().equalCells { | |||
| 519 | + | w.unmaximize() | |||
| 520 | + | } | |||
| 517 | w.SetMaxColumns(m.template.MaxColumns) | 521 | w.SetMaxColumns(m.template.MaxColumns) | ||
| 518 | if s.MaxColumns > 0 { | 522 | if s.MaxColumns > 0 { | ||
| 519 | w.SetMaxColumns(s.MaxColumns) | 523 | w.SetMaxColumns(s.MaxColumns) | ||
M internal/core/overview.go +1 -2
| @@ -749,9 +749,8 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||
| 749 | 749 | for i, c := range w.Columns { | |
| 750 | 750 | r := Rect{X: w.columnX(i) - w.Usable.X, Y: g, W: w.columnWidth(i), H: h} | |
| 751 | 751 | if w.policy().wraps { | |
| 752 | - | k := max(w.MaxColumns, 1) | |
| 753 | 752 | width := w.cellWidth() | |
| 754 | - | r.X = w.band(i)*w.Usable.W + g + (i%k)*(width+g) | |
| 753 | + | r.X = w.band(i)*w.Usable.W + w.cellX(i) | |
| 755 | 754 | r.W = width | |
| 756 | 755 | } else if w.Overflow == OverflowFixed { | |
| 757 | 756 | r = rects[i] | |
| @@ -749,9 +749,8 @@ func (w *Workspace) previewTilesInto(tiles []Placement) ([]Placement, int, Rect) | |||||
| 749 | for i, c := range w.Columns { | 749 | for i, c := range w.Columns { | ||
| 750 | r := Rect{X: w.columnX(i) - w.Usable.X, Y: g, W: w.columnWidth(i), H: h} | 750 | r := Rect{X: w.columnX(i) - w.Usable.X, Y: g, W: w.columnWidth(i), H: h} | ||
| 751 | if w.policy().wraps { | 751 | if w.policy().wraps { | ||
| 752 | - | k := max(w.MaxColumns, 1) | |||
| 753 | width := w.cellWidth() | 752 | width := w.cellWidth() | ||
| 754 | - | r.X = w.band(i)*w.Usable.W + g + (i%k)*(width+g) | 753 | + | r.X = w.band(i)*w.Usable.W + w.cellX(i) |
| 755 | r.W = width | 754 | r.W = width | ||
| 756 | } else if w.Overflow == OverflowFixed { | 755 | } else if w.Overflow == OverflowFixed { | ||
| 757 | r = rects[i] | 756 | r = rects[i] | ||
M internal/core/stash.go +3 -0
| @@ -132,6 +132,9 @@ func (w *Workspace) restore(id WindowID, back *origPlace) { | |||
| 132 | 132 | } | |
| 133 | 133 | c.Windows = slices.Insert(c.Windows, row, id) | |
| 134 | 134 | c.Focus = row | |
| 135 | + | if w.policy().equalCells && w.Focus != i { | |
| 136 | + | w.unmaximize() | |
| 137 | + | } | |
| 135 | 138 | w.Focus = i | |
| 136 | 139 | w.scroll() | |
| 137 | 140 | return | |
| @@ -132,6 +132,9 @@ func (w *Workspace) restore(id WindowID, back *origPlace) { | |||||
| 132 | } | 132 | } | ||
| 133 | c.Windows = slices.Insert(c.Windows, row, id) | 133 | c.Windows = slices.Insert(c.Windows, row, id) | ||
| 134 | c.Focus = row | 134 | c.Focus = row | ||
| 135 | + | if w.policy().equalCells && w.Focus != i { | |||
| 136 | + | w.unmaximize() | |||
| 137 | + | } | |||
| 135 | w.Focus = i | 138 | w.Focus = i | ||
| 136 | w.scroll() | 139 | w.scroll() | ||
| 137 | return | 140 | return | ||