1package core
2
3import "slices"
4
5func (w *Workspace) band(i int) int { return i / max(w.MaxColumns, 1) }
6func (w *Workspace) sameBand(a, b int) bool { return !w.policy().wraps || w.band(a) == w.band(b) }
7
8// cellX is the x of column i in its lane, from the usable area's left edge.
9func (w *Workspace) cellX(i int) int {
10 first, last := w.laneBounds(w.laneOf(i))
11 S, u, sum := w.laneStats(first, last)
12 x := w.gap()
13 for j := first; j < i; j++ {
14 x += w.laneWidth(j, first, last, S, u, sum) + w.gap()
15 }
16 return x
17}
18func (w *Workspace) cascadeBands() (n, spacing int) {
19 if len(w.Columns) == 0 || w.Usable.H <= 0 {
20 return 1, 0
21 }
22 return w.band(len(w.Columns)-1) + 1, w.Usable.H
23}
24
25// cellWidth is the width of column i in its lane: the columns of a band
26// (the whole row in fixed overflow) share the usable width by their cells,
27// auto columns sharing what the others leave, so a lone column fills it.
28func (w *Workspace) cellWidth(i int) int {
29 first, last := w.laneBounds(w.laneOf(i))
30 S, u, sum := w.laneStats(first, last)
31 return w.laneWidth(i, first, last, S, u, sum)
32}
33
34// laneStats measures the lane first..last: S is the sum of the cells of
35// its set columns, u the count of its auto columns and sum the total of
36// their weights (laneWeight).
37func (w *Workspace) laneStats(first, last int) (S, u, sum int) {
38 for i := first; i <= last; i++ {
39 if c := w.Columns[i].Cells; c > 0 {
40 S += c
41 } else {
42 u++
43 }
44 }
45 for i := first; i <= last; i++ {
46 sum += w.laneWeight(i, S, u)
47 }
48 return S, u, sum
49}
50
51// laneWeight is the share of column i in its lane, given its stats. With
52// room left for every auto column, they share the cells the set ones leave
53// and the set ones keep their cells; otherwise an auto column weighs one
54// cell.
55func (w *Workspace) laneWeight(i, S, u int) int {
56 k := max(w.MaxColumns, 1)
57 c := w.Columns[i].Cells
58 switch {
59 case u == 0:
60 return c
61 case k-S >= u:
62 if c > 0 {
63 return c * u
64 }
65 return k - S
66 case c > 0:
67 return c
68 }
69 return 1
70}
71
72// laneWidth is the width of column i of the lane first..last with the
73// stats of laneStats: the usable width after gaps, split by weight. The
74// remainder under one pixel per weight stays empty, as in equalShare.
75func (w *Workspace) laneWidth(i, first, last, S, u, sum int) int {
76 if sum <= 0 {
77 return 0
78 }
79 avail := max(w.Usable.W-w.gap()*(last-first+2), 0)
80 // floor(avail*weight/sum) without overflowing on a huge area.
81 weight := w.laneWeight(i, S, u)
82 return avail/sum*weight + avail%sum*weight/sum
83}
84
85// equalShare is the width of each of k columns sharing the usable width
86// after gaps; a remainder under k pixels stays empty.
87func (w *Workspace) equalShare(k int) int {
88 k = max(k, 1)
89 return max((w.Usable.W-w.gap()*(k+1))/k, 0)
90}
91func (w *Workspace) cellInBand(b int) int {
92 k := max(w.MaxColumns, 1)
93 return min(b*k+w.Focus%k, len(w.Columns)-1)
94}
95func (w *Workspace) focusBand(dir int) bool {
96 i := w.laneNeighbor(dir)
97 if i < 0 {
98 return false
99 }
100 w.unmaximize()
101 w.Focus = i
102 w.Columns[i].Focus = 0
103 if dir < 0 {
104 w.Columns[i].Focus = len(w.Columns[i].Windows) - 1
105 }
106 w.raiseColumns()
107 w.scroll()
108 return true
109}
110func (w *Workspace) cascadeRectsInto(dst []Rect, ignoreFullWidth bool) []Rect {
111 g := w.gap()
112 rects := slices.Grow(dst[:0], len(w.Columns))[:len(w.Columns)]
113 view := w.View + w.shiftPixels()
114 for lane := 0; ; lane++ {
115 first, last := w.laneBounds(lane)
116 if first > last {
117 break
118 }
119 S, u, sum := w.laneStats(first, last)
120 x := g
121 for i := first; i <= last; i++ {
122 cw := w.laneWidth(i, first, last, S, u, sum)
123 r := Rect{X: w.Usable.X + x, Y: w.Usable.Y + g + w.band(i)*w.Usable.H - view, W: cw, H: max(w.Usable.H-2*g, 0)}
124 if w.Columns[i].FullWidth && !ignoreFullWidth {
125 r.X = w.Usable.X + g
126 r.W = max(w.Usable.W-2*g, 0)
127 }
128 rects[i] = r
129 x += cw + g
130 }
131 }
132 return rects
133}
134
135func (w *Workspace) cascadePoints() []float64 {
136 n, spacing := w.cascadeBands()
137 points := make([]float64, n)
138 for i := range points {
139 points[i] = float64(i * spacing)
140 }
141 return points
142}
143
144func (w *Workspace) cascadeFocus(view int) int {
145 if len(w.Columns) == 0 {
146 return 0
147 }
148 b := min(max(view/max(w.Usable.H, 1), 0), w.band(len(w.Columns)-1))
149 return w.cellInBand(b)
150}
151
152// laneOf is the lane of column i in the overview: its band, or the only
153// lane of a layout that does not wrap.
154func (w *Workspace) laneOf(i int) int {
155 if w.policy().wraps {
156 return w.band(i)
157 }
158 return 0
159}
160
161// laneBounds is the range of columns of a lane, first > last when the lane
162// does not exist. A layout that does not wrap has the whole row as lane 0.
163func (w *Workspace) laneBounds(lane int) (first, last int) {
164 k := len(w.Columns)
165 if w.policy().wraps {
166 k = max(w.MaxColumns, 1)
167 }
168 first = lane * k
169 if lane < 0 || first >= len(w.Columns) {
170 return 0, -1
171 }
172 return first, min(first+k, len(w.Columns)) - 1
173}
174
175// leavesBand reports whether column i, where a left/right move from the
176// focused column goes, is past the workspace or in another cascade band:
177// the move then goes to the neighbor monitor first.
178func (w *Workspace) leavesBand(i int) bool {
179 return i < 0 || i >= len(w.Columns) || !w.sameBand(i, w.Focus)
180}
181
182// moveBand swaps the focused cascade column with the column in the band dir
183// away where focus-window-up/down goes; focus follows it. Past the first
184// or last band nothing moves.
185func (w *Workspace) moveBand(dir int) {
186 i := w.laneNeighbor(dir)
187 if i < 0 {
188 return
189 }
190 w.unmaximize()
191 w.Columns[w.Focus], w.Columns[i] = w.Columns[i], w.Columns[w.Focus]
192 w.Focus = i
193 w.scroll()
194}
195
196// laneNeighbor is the closest column to the focus in the lane d lanes away,
197// the focus's offset in its lane kept; -1 past the first or last lane.
198func (w *Workspace) laneNeighbor(d int) int {
199 lane := w.laneOf(w.Focus)
200 first, _ := w.laneBounds(lane)
201 nfirst, nlast := w.laneBounds(lane + d)
202 if nfirst > nlast {
203 return -1
204 }
205 return min(nfirst+w.Focus-first, nlast)
206}