]> git.lizzy.rs Git - micro.git/blob - internal/views/splits.go
Merge branch 'john-batch-master'
[micro.git] / internal / views / splits.go
1 package views
2
3 import (
4         "fmt"
5         "strings"
6 )
7
8 type SplitType uint8
9
10 const (
11         STVert  = 0
12         STHoriz = 1
13         STUndef = 2
14 )
15
16 var idcounter uint64
17
18 // NewID returns a new unique id
19 func NewID() uint64 {
20         idcounter++
21         return idcounter
22 }
23
24 // A View is a size and location of a split
25 type View struct {
26         X, Y int
27         W, H int
28 }
29
30 // A Node describes a split in the tree
31 // If a node is a leaf node then it corresponds to a buffer that is being
32 // displayed otherwise it has a number of children of the opposite type
33 // (vertical splits have horizontal children and vice versa)
34 type Node struct {
35         View
36
37         Kind SplitType
38
39         parent   *Node
40         children []*Node
41
42         // Nodes can be marked as non resizable if they shouldn't be rescaled
43         // when the terminal window is resized or when a new split is added
44         // Only the splits on the edges of the screen can be marked as non resizable
45         canResize bool
46         // A node may also be marked with proportional scaling. This means that when
47         // the window is resized the split maintains its proportions
48         propScale bool
49
50         // Defines the proportion of the screen this node should take up if propScale is
51         // on
52         propW, propH float64
53         // The id is unique for each leaf node and provides a way to keep track of a split
54         // The id cannot be 0
55         id uint64
56 }
57
58 // NewNode returns a new node with the given specifications
59 func NewNode(Kind SplitType, x, y, w, h int, parent *Node, id uint64) *Node {
60         n := new(Node)
61         n.Kind = Kind
62         n.canResize = true
63         n.propScale = true
64         n.X, n.Y, n.W, n.H = x, y, w, h
65         n.children = make([]*Node, 0)
66         n.parent = parent
67         n.id = id
68         if parent != nil {
69                 n.propW, n.propH = float64(w)/float64(parent.W), float64(h)/float64(parent.H)
70         } else {
71                 n.propW, n.propH = 1, 1
72         }
73
74         return n
75 }
76
77 // NewRoot returns an empty Node with a size and location
78 // The type of the node will be determined by the first action on the node
79 // In other words, a lone split is neither horizontal nor vertical, it only
80 // becomes one or the other after a vsplit or hsplit is made
81 func NewRoot(x, y, w, h int) *Node {
82         n1 := NewNode(STUndef, x, y, w, h, nil, NewID())
83
84         return n1
85 }
86
87 // IsLeaf returns if this node is a leaf node
88 func (n *Node) IsLeaf() bool {
89         return len(n.children) == 0
90 }
91
92 // ID returns this node's id or 0 if it is not viewable
93 func (n *Node) ID() uint64 {
94         if n.IsLeaf() {
95                 return n.id
96         }
97         return 0
98 }
99
100 // CanResize returns if this node can be resized
101 func (n *Node) CanResize() bool {
102         return n.canResize
103 }
104
105 // PropScale returns if this node is proportionally scaled
106 func (n *Node) PropScale() bool {
107         return n.propScale
108 }
109
110 // SetResize sets the resize flag
111 func (n *Node) SetResize(b bool) {
112         n.canResize = b
113 }
114
115 // SetPropScale sets the propScale flag
116 func (n *Node) SetPropScale(b bool) {
117         n.propScale = b
118 }
119
120 // Children returns this node's children
121 func (n *Node) Children() []*Node {
122         return n.children
123 }
124
125 // GetNode returns the node with the given id in the tree of children
126 // that this node has access to or nil if the node with that id cannot be found
127 func (n *Node) GetNode(id uint64) *Node {
128         if n.id == id && n.IsLeaf() {
129                 return n
130         }
131         for _, c := range n.children {
132                 if c.id == id && c.IsLeaf() {
133                         return c
134                 }
135                 gc := c.GetNode(id)
136                 if gc != nil {
137                         return gc
138                 }
139         }
140         return nil
141 }
142
143 func (n *Node) vResizeSplit(i int, size int) bool {
144         if i < 0 || i >= len(n.children) {
145                 return false
146         }
147         var c1, c2 *Node
148         if i == len(n.children)-1 {
149                 c1, c2 = n.children[i-1], n.children[i]
150         } else {
151                 c1, c2 = n.children[i], n.children[i+1]
152         }
153         toth := c1.H + c2.H
154         if size >= toth {
155                 return false
156         }
157         c2.Y = c1.Y + size
158         c1.Resize(c1.W, size)
159         c2.Resize(c2.W, toth-size)
160         n.markSizes()
161         n.alignSizes(n.W, n.H)
162         return true
163 }
164 func (n *Node) hResizeSplit(i int, size int) bool {
165         if i < 0 || i >= len(n.children) {
166                 return false
167         }
168         var c1, c2 *Node
169         if i == len(n.children)-1 {
170                 c1, c2 = n.children[i-1], n.children[i]
171         } else {
172                 c1, c2 = n.children[i], n.children[i+1]
173         }
174         totw := c1.W + c2.W
175         if size >= totw {
176                 return false
177         }
178         c2.X = c1.X + size
179         c1.Resize(size, c1.H)
180         c2.Resize(totw-size, c2.H)
181         n.markSizes()
182         n.alignSizes(n.W, n.H)
183         return true
184 }
185
186 // ResizeSplit resizes a certain split to a given size
187 func (n *Node) ResizeSplit(size int) bool {
188         if len(n.parent.children) <= 1 {
189                 // cannot resize a lone node
190                 return false
191         }
192         ind := 0
193         for i, c := range n.parent.children {
194                 if c.id == n.id {
195                         ind = i
196                 }
197         }
198         if n.parent.Kind == STVert {
199                 return n.parent.vResizeSplit(ind, size)
200         }
201         return n.parent.hResizeSplit(ind, size)
202 }
203
204 // Resize sets this node's size and resizes all children accordlingly
205 func (n *Node) Resize(w, h int) {
206         n.W, n.H = w, h
207
208         if n.IsLeaf() {
209                 return
210         }
211
212         x, y := n.X, n.Y
213         totw, toth := 0, 0
214         for _, c := range n.children {
215                 cW := int(float64(w) * c.propW)
216                 cH := int(float64(h) * c.propH)
217
218                 c.X, c.Y = x, y
219                 c.Resize(cW, cH)
220                 if n.Kind == STHoriz {
221                         x += cW
222                         totw += cW
223                 } else {
224                         y += cH
225                         toth += cH
226                 }
227         }
228
229         n.alignSizes(totw, toth)
230 }
231
232 func (n *Node) alignSizes(totw, toth int) {
233         // Make sure that there are no off-by-one problems with the rounding
234         // of the sizes by making the final split fill the screen
235         if n.Kind == STVert && toth != n.H {
236                 last := n.children[len(n.children)-1]
237                 last.Resize(last.W, last.H+n.H-toth)
238         } else if n.Kind == STHoriz && totw != n.W {
239                 last := n.children[len(n.children)-1]
240                 last.Resize(last.W+n.W-totw, last.H)
241         }
242 }
243
244 // Resets all proportions for children
245 func (n *Node) markSizes() {
246         for _, c := range n.children {
247                 c.propW = float64(c.W) / float64(n.W)
248                 c.propH = float64(c.H) / float64(n.H)
249                 c.markSizes()
250         }
251 }
252
253 func (n *Node) markResize() {
254         n.markSizes()
255         n.Resize(n.W, n.H)
256 }
257
258 // vsplits a vertical split and returns the id of the new split
259 func (n *Node) vVSplit(right bool) uint64 {
260         ind := 0
261         for i, c := range n.parent.children {
262                 if c.id == n.id {
263                         ind = i
264                 }
265         }
266         return n.parent.hVSplit(ind, right)
267 }
268
269 // hsplits a horizontal split
270 func (n *Node) hHSplit(bottom bool) uint64 {
271         ind := 0
272         for i, c := range n.parent.children {
273                 if c.id == n.id {
274                         ind = i
275                 }
276         }
277         return n.parent.vHSplit(ind, bottom)
278 }
279
280 // Returns the size of the non-resizable area and the number of resizable
281 // splits
282 func (n *Node) getResizeInfo(h bool) (int, int) {
283         numr := 0
284         numnr := 0
285         nonr := 0
286         for _, c := range n.children {
287                 if !c.CanResize() {
288                         if h {
289                                 nonr += c.H
290                         } else {
291                                 nonr += c.W
292                         }
293                         numnr++
294                 } else {
295                         numr++
296                 }
297         }
298
299         // if there are no resizable splits make them all resizable
300         if numr == 0 {
301                 numr = numnr
302         }
303
304         return nonr, numr
305 }
306
307 func (n *Node) applyNewSize(size int, h bool) {
308         a := n.X
309         if h {
310                 a = n.Y
311         }
312         for _, c := range n.children {
313                 if h {
314                         c.Y = a
315                 } else {
316                         c.X = a
317                 }
318                 if c.CanResize() {
319                         if h {
320                                 c.Resize(c.W, size)
321                         } else {
322                                 c.Resize(size, c.H)
323                         }
324                 }
325                 if h {
326                         a += c.H
327                 } else {
328                         a += c.H
329                 }
330         }
331         n.markResize()
332 }
333
334 // hsplits a vertical split
335 func (n *Node) vHSplit(i int, right bool) uint64 {
336         if n.IsLeaf() {
337                 newid := NewID()
338                 hn1 := NewNode(STHoriz, n.X, n.Y, n.W, n.H/2, n, n.id)
339                 hn2 := NewNode(STHoriz, n.X, n.Y+hn1.H, n.W, n.H/2, n, newid)
340                 if !right {
341                         hn1.id, hn2.id = hn2.id, hn1.id
342                 }
343
344                 n.children = append(n.children, hn1, hn2)
345                 n.markResize()
346                 return newid
347         } else {
348                 nonrh, numr := n.getResizeInfo(true)
349
350                 // size of resizable area
351                 height := (n.H - nonrh) / (numr + 1)
352
353                 newid := NewID()
354                 hn := NewNode(STHoriz, n.X, 0, n.W, height, n, newid)
355
356                 // insert the node into the correct slot
357                 n.children = append(n.children, nil)
358                 inspos := i
359                 if right {
360                         inspos++
361                 }
362                 copy(n.children[inspos+1:], n.children[inspos:])
363                 n.children[inspos] = hn
364
365                 n.applyNewSize(height, true)
366                 return newid
367         }
368 }
369
370 // vsplits a horizontal split
371 func (n *Node) hVSplit(i int, right bool) uint64 {
372         if n.IsLeaf() {
373                 newid := NewID()
374                 vn1 := NewNode(STVert, n.X, n.Y, n.W/2, n.H, n, n.id)
375                 vn2 := NewNode(STVert, n.X+vn1.W, n.Y, n.W/2, n.H, n, newid)
376                 if !right {
377                         vn1.id, vn2.id = vn2.id, vn1.id
378                 }
379
380                 n.children = append(n.children, vn1, vn2)
381                 n.markResize()
382                 return newid
383         } else {
384                 nonrw, numr := n.getResizeInfo(false)
385
386                 width := (n.W - nonrw) / (numr + 1)
387
388                 newid := NewID()
389                 vn := NewNode(STVert, 0, n.Y, width, n.H, n, newid)
390
391                 // Inser the node into the correct slot
392                 n.children = append(n.children, nil)
393                 inspos := i
394                 if right {
395                         inspos++
396                 }
397                 copy(n.children[inspos+1:], n.children[inspos:])
398                 n.children[inspos] = vn
399
400                 n.applyNewSize(width, false)
401                 return newid
402         }
403 }
404
405 // HSplit creates a horizontal split and returns the id of the new split
406 // bottom specifies if the new split should be created on the top or bottom
407 // of the current split
408 func (n *Node) HSplit(bottom bool) uint64 {
409         if !n.IsLeaf() {
410                 return 0
411         }
412         if n.Kind == STUndef {
413                 n.Kind = STVert
414         }
415         if n.Kind == STVert {
416                 return n.vHSplit(0, bottom)
417         }
418         return n.hHSplit(bottom)
419 }
420
421 // VSplit creates a vertical split and returns the id of the new split
422 // right specifies if the new split should be created on the right or left
423 // of the current split
424 func (n *Node) VSplit(right bool) uint64 {
425         if !n.IsLeaf() {
426                 return 0
427         }
428         if n.Kind == STUndef {
429                 n.Kind = STHoriz
430         }
431         if n.Kind == STVert {
432                 return n.vVSplit(right)
433         }
434         return n.hVSplit(0, right)
435 }
436
437 // unsplits the child of a split
438 func (n *Node) unsplit(i int, h bool) {
439         copy(n.children[i:], n.children[i+1:])
440         n.children[len(n.children)-1] = nil
441         n.children = n.children[:len(n.children)-1]
442
443         nonrs, numr := n.getResizeInfo(h)
444         if numr == 0 {
445                 // This means that this was the last child
446                 // The parent will get cleaned up in the next iteration and
447                 // will resolve all sizing issues with its parent
448                 return
449         }
450         size := (n.W - nonrs) / numr
451         if h {
452                 size = (n.H - nonrs) / numr
453         }
454         n.applyNewSize(size, h)
455 }
456
457 // Unsplit deletes this split and resizes everything
458 // else accordingly
459 func (n *Node) Unsplit() bool {
460         if !n.IsLeaf() || n.parent == nil {
461                 return false
462         }
463         ind := 0
464         for i, c := range n.parent.children {
465                 if c.id == n.id {
466                         ind = i
467                 }
468         }
469         if n.parent.Kind == STVert {
470                 n.parent.unsplit(ind, true)
471         } else {
472                 n.parent.unsplit(ind, false)
473         }
474
475         if n.parent.IsLeaf() {
476                 return n.parent.Unsplit()
477         }
478         return true
479 }
480
481 // String returns the string form of the node and all children (used for debugging)
482 func (n *Node) String() string {
483         var strf func(n *Node, ident int) string
484         strf = func(n *Node, ident int) string {
485                 marker := "|"
486                 if n.Kind == STHoriz {
487                         marker = "-"
488                 }
489                 str := fmt.Sprint(strings.Repeat("\t", ident), marker, n.View, n.id)
490                 if n.IsLeaf() {
491                         str += "🍁"
492                 }
493                 str += "\n"
494                 for _, c := range n.children {
495                         str += strf(c, ident+1)
496                 }
497                 return str
498         }
499         return strf(n, 0)
500 }