package views import ( "fmt" "strings" ) type SplitType uint8 const ( STVert = 0 STHoriz = 1 STUndef = 2 ) var idcounter uint64 // NewID returns a new unique id func NewID() uint64 { idcounter++ return idcounter } // A View is a size and location of a split type View struct { X, Y int W, H int } // A Node describes a split in the tree // If a node is a leaf node then it corresponds to a buffer that is being // displayed otherwise it has a number of children of the opposite type // (vertical splits have horizontal children and vice versa) type Node struct { View Kind SplitType parent *Node children []*Node // Nodes can be marked as non resizable if they shouldn't be rescaled // when the terminal window is resized or when a new split is added // Only the splits on the edges of the screen can be marked as non resizable canResize bool // A node may also be marked with proportional scaling. This means that when // the window is resized the split maintains its proportions propScale bool // Defines the proportion of the screen this node should take up if propScale is // on propW, propH float64 // The id is unique for each leaf node and provides a way to keep track of a split // The id cannot be 0 id uint64 } // NewNode returns a new node with the given specifications func NewNode(Kind SplitType, x, y, w, h int, parent *Node, id uint64) *Node { n := new(Node) n.Kind = Kind n.canResize = true n.propScale = true n.X, n.Y, n.W, n.H = x, y, w, h n.children = make([]*Node, 0) n.parent = parent n.id = id if parent != nil { n.propW, n.propH = float64(w)/float64(parent.W), float64(h)/float64(parent.H) } else { n.propW, n.propH = 1, 1 } return n } // NewRoot returns an empty Node with a size and location // The type of the node will be determined by the first action on the node // In other words, a lone split is neither horizontal nor vertical, it only // becomes one or the other after a vsplit or hsplit is made func NewRoot(x, y, w, h int) *Node { n1 := NewNode(STUndef, x, y, w, h, nil, NewID()) return n1 } // IsLeaf returns if this node is a leaf node func (n *Node) IsLeaf() bool { return len(n.children) == 0 } // ID returns this node's id or 0 if it is not viewable func (n *Node) ID() uint64 { if n.IsLeaf() { return n.id } return 0 } // CanResize returns if this node can be resized func (n *Node) CanResize() bool { return n.canResize } // PropScale returns if this node is proportionally scaled func (n *Node) PropScale() bool { return n.propScale } // SetResize sets the resize flag func (n *Node) SetResize(b bool) { n.canResize = b } // SetPropScale sets the propScale flag func (n *Node) SetPropScale(b bool) { n.propScale = b } // Children returns this node's children func (n *Node) Children() []*Node { return n.children } // GetNode returns the node with the given id in the tree of children // that this node has access to or nil if the node with that id cannot be found func (n *Node) GetNode(id uint64) *Node { if n.id == id && n.IsLeaf() { return n } for _, c := range n.children { if c.id == id && c.IsLeaf() { return c } gc := c.GetNode(id) if gc != nil { return gc } } return nil } func (n *Node) vResizeSplit(i int, size int) bool { if i < 0 || i >= len(n.children) { return false } var c1, c2 *Node if i == len(n.children)-1 { c1, c2 = n.children[i-1], n.children[i] } else { c1, c2 = n.children[i], n.children[i+1] } toth := c1.H + c2.H if size >= toth { return false } c2.Y = c1.Y + size c1.Resize(c1.W, size) c2.Resize(c2.W, toth-size) n.markSizes() n.alignSizes(n.W, n.H) return true } func (n *Node) hResizeSplit(i int, size int) bool { if i < 0 || i >= len(n.children) { return false } var c1, c2 *Node if i == len(n.children)-1 { c1, c2 = n.children[i-1], n.children[i] } else { c1, c2 = n.children[i], n.children[i+1] } totw := c1.W + c2.W if size >= totw { return false } c2.X = c1.X + size c1.Resize(size, c1.H) c2.Resize(totw-size, c2.H) n.markSizes() n.alignSizes(n.W, n.H) return true } // ResizeSplit resizes a certain split to a given size func (n *Node) ResizeSplit(size int) bool { if len(n.parent.children) <= 1 { // cannot resize a lone node return false } ind := 0 for i, c := range n.parent.children { if c.id == n.id { ind = i } } if n.parent.Kind == STVert { return n.parent.vResizeSplit(ind, size) } return n.parent.hResizeSplit(ind, size) } // Resize sets this node's size and resizes all children accordlingly func (n *Node) Resize(w, h int) { n.W, n.H = w, h if n.IsLeaf() { return } x, y := n.X, n.Y totw, toth := 0, 0 for _, c := range n.children { cW := int(float64(w) * c.propW) cH := int(float64(h) * c.propH) c.X, c.Y = x, y c.Resize(cW, cH) if n.Kind == STHoriz { x += cW totw += cW } else { y += cH toth += cH } } n.alignSizes(totw, toth) } func (n *Node) alignSizes(totw, toth int) { // Make sure that there are no off-by-one problems with the rounding // of the sizes by making the final split fill the screen if n.Kind == STVert && toth != n.H { last := n.children[len(n.children)-1] last.Resize(last.W, last.H+n.H-toth) } else if n.Kind == STHoriz && totw != n.W { last := n.children[len(n.children)-1] last.Resize(last.W+n.W-totw, last.H) } } // Resets all proportions for children func (n *Node) markSizes() { for _, c := range n.children { c.propW = float64(c.W) / float64(n.W) c.propH = float64(c.H) / float64(n.H) c.markSizes() } } func (n *Node) markResize() { n.markSizes() n.Resize(n.W, n.H) } // vsplits a vertical split and returns the id of the new split func (n *Node) vVSplit(right bool) uint64 { ind := 0 for i, c := range n.parent.children { if c.id == n.id { ind = i } } return n.parent.hVSplit(ind, right) } // hsplits a horizontal split func (n *Node) hHSplit(bottom bool) uint64 { ind := 0 for i, c := range n.parent.children { if c.id == n.id { ind = i } } return n.parent.vHSplit(ind, bottom) } // Returns the size of the non-resizable area and the number of resizable // splits func (n *Node) getResizeInfo(h bool) (int, int) { numr := 0 numnr := 0 nonr := 0 for _, c := range n.children { if !c.CanResize() { if h { nonr += c.H } else { nonr += c.W } numnr++ } else { numr++ } } // if there are no resizable splits make them all resizable if numr == 0 { numr = numnr } return nonr, numr } func (n *Node) applyNewSize(size int, h bool) { a := n.X if h { a = n.Y } for _, c := range n.children { if h { c.Y = a } else { c.X = a } if c.CanResize() { if h { c.Resize(c.W, size) } else { c.Resize(size, c.H) } } if h { a += c.H } else { a += c.H } } n.markResize() } // hsplits a vertical split func (n *Node) vHSplit(i int, right bool) uint64 { if n.IsLeaf() { newid := NewID() hn1 := NewNode(STHoriz, n.X, n.Y, n.W, n.H/2, n, n.id) hn2 := NewNode(STHoriz, n.X, n.Y+hn1.H, n.W, n.H/2, n, newid) if !right { hn1.id, hn2.id = hn2.id, hn1.id } n.children = append(n.children, hn1, hn2) n.markResize() return newid } else { nonrh, numr := n.getResizeInfo(true) // size of resizable area height := (n.H - nonrh) / (numr + 1) newid := NewID() hn := NewNode(STHoriz, n.X, 0, n.W, height, n, newid) // insert the node into the correct slot n.children = append(n.children, nil) inspos := i if right { inspos++ } copy(n.children[inspos+1:], n.children[inspos:]) n.children[inspos] = hn n.applyNewSize(height, true) return newid } } // vsplits a horizontal split func (n *Node) hVSplit(i int, right bool) uint64 { if n.IsLeaf() { newid := NewID() vn1 := NewNode(STVert, n.X, n.Y, n.W/2, n.H, n, n.id) vn2 := NewNode(STVert, n.X+vn1.W, n.Y, n.W/2, n.H, n, newid) if !right { vn1.id, vn2.id = vn2.id, vn1.id } n.children = append(n.children, vn1, vn2) n.markResize() return newid } else { nonrw, numr := n.getResizeInfo(false) width := (n.W - nonrw) / (numr + 1) newid := NewID() vn := NewNode(STVert, 0, n.Y, width, n.H, n, newid) // Inser the node into the correct slot n.children = append(n.children, nil) inspos := i if right { inspos++ } copy(n.children[inspos+1:], n.children[inspos:]) n.children[inspos] = vn n.applyNewSize(width, false) return newid } } // HSplit creates a horizontal split and returns the id of the new split // bottom specifies if the new split should be created on the top or bottom // of the current split func (n *Node) HSplit(bottom bool) uint64 { if !n.IsLeaf() { return 0 } if n.Kind == STUndef { n.Kind = STVert } if n.Kind == STVert { return n.vHSplit(0, bottom) } return n.hHSplit(bottom) } // VSplit creates a vertical split and returns the id of the new split // right specifies if the new split should be created on the right or left // of the current split func (n *Node) VSplit(right bool) uint64 { if !n.IsLeaf() { return 0 } if n.Kind == STUndef { n.Kind = STHoriz } if n.Kind == STVert { return n.vVSplit(right) } return n.hVSplit(0, right) } // unsplits the child of a split func (n *Node) unsplit(i int, h bool) { copy(n.children[i:], n.children[i+1:]) n.children[len(n.children)-1] = nil n.children = n.children[:len(n.children)-1] nonrs, numr := n.getResizeInfo(h) if numr == 0 { // This means that this was the last child // The parent will get cleaned up in the next iteration and // will resolve all sizing issues with its parent return } size := (n.W - nonrs) / numr if h { size = (n.H - nonrs) / numr } n.applyNewSize(size, h) } // Unsplit deletes this split and resizes everything // else accordingly func (n *Node) Unsplit() bool { if !n.IsLeaf() || n.parent == nil { return false } ind := 0 for i, c := range n.parent.children { if c.id == n.id { ind = i } } if n.parent.Kind == STVert { n.parent.unsplit(ind, true) } else { n.parent.unsplit(ind, false) } if n.parent.IsLeaf() { return n.parent.Unsplit() } return true } // String returns the string form of the node and all children (used for debugging) func (n *Node) String() string { var strf func(n *Node, ident int) string strf = func(n *Node, ident int) string { marker := "|" if n.Kind == STHoriz { marker = "-" } str := fmt.Sprint(strings.Repeat("\t", ident), marker, n.View, n.id) if n.IsLeaf() { str += "🍁" } str += "\n" for _, c := range n.children { str += strf(c, ident+1) } return str } return strf(n, 0) }