micro/internal/views/splits.go

501 lines
10 KiB
Go
Raw Normal View History

2019-01-04 22:40:56 +00:00
package views
import (
"fmt"
"strings"
)
type SplitType uint8
const (
STVert = 0
STHoriz = 1
STUndef = 2
)
var idcounter uint64
2019-01-09 21:55:00 +00:00
// NewID returns a new unique id
2019-01-04 22:40:56 +00:00
func NewID() uint64 {
idcounter++
return idcounter
}
2019-01-09 21:55:00 +00:00
// A View is a size and location of a split
2019-01-04 22:40:56 +00:00
type View struct {
X, Y int
W, H int
}
2019-01-09 21:55:00 +00:00
// 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)
2019-01-04 22:40:56 +00:00
type Node struct {
View
2019-01-05 21:27:04 +00:00
Kind SplitType
2019-01-04 22:40:56 +00:00
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
2019-01-09 21:55:00 +00:00
// Defines the proportion of the screen this node should take up if propScale is
// on
2019-01-06 00:41:40 +00:00
propW, propH float64
2019-01-09 21:55:00 +00:00
// 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
2019-01-04 22:40:56 +00:00
}
2019-01-09 21:55:00 +00:00
// 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
2019-01-04 22:40:56 +00:00
func (n *Node) ID() uint64 {
if n.IsLeaf() {
return n.id
}
return 0
}
2019-01-09 21:55:00 +00:00
// CanResize returns if this node can be resized
2019-01-04 22:40:56 +00:00
func (n *Node) CanResize() bool {
return n.canResize
}
2019-01-09 21:55:00 +00:00
// PropScale returns if this node is proportionally scaled
2019-01-04 22:40:56 +00:00
func (n *Node) PropScale() bool {
return n.propScale
}
2019-01-09 21:55:00 +00:00
// SetResize sets the resize flag
2019-01-04 22:40:56 +00:00
func (n *Node) SetResize(b bool) {
n.canResize = b
}
2019-01-09 21:55:00 +00:00
// SetPropScale sets the propScale flag
2019-01-04 22:40:56 +00:00
func (n *Node) SetPropScale(b bool) {
n.propScale = b
}
2019-01-09 21:55:00 +00:00
// Children returns this node's children
2019-01-05 21:27:04 +00:00
func (n *Node) Children() []*Node {
return n.children
}
2019-01-04 22:40:56 +00:00
2019-01-09 21:55:00 +00:00
// 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
2019-01-04 22:40:56 +00:00
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 {
2019-01-09 21:55:00 +00:00
if i < 0 || i >= len(n.children) {
2019-01-04 22:40:56 +00:00
return false
}
2019-01-09 21:55:00 +00:00
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]
}
2019-01-05 19:09:02 +00:00
toth := c1.H + c2.H
2019-01-04 22:40:56 +00:00
if size >= toth {
return false
}
2019-01-09 23:06:31 +00:00
c2.Y = c1.Y + size
2019-01-05 19:09:02 +00:00
c1.Resize(c1.W, size)
c2.Resize(c2.W, toth-size)
2019-01-09 21:55:00 +00:00
n.markSizes()
2019-01-09 23:06:31 +00:00
n.alignSizes(n.W, n.H)
2019-01-04 22:40:56 +00:00
return true
}
func (n *Node) hResizeSplit(i int, size int) bool {
2019-01-09 21:55:00 +00:00
if i < 0 || i >= len(n.children) {
2019-01-04 22:40:56 +00:00
return false
}
2019-01-09 21:55:00 +00:00
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]
}
2019-01-05 19:09:02 +00:00
totw := c1.W + c2.W
2019-01-04 22:40:56 +00:00
if size >= totw {
return false
}
2019-01-09 23:06:31 +00:00
c2.X = c1.X + size
2019-01-05 19:09:02 +00:00
c1.Resize(size, c1.H)
c2.Resize(totw-size, c2.H)
2019-01-09 21:55:00 +00:00
n.markSizes()
2019-01-09 23:06:31 +00:00
n.alignSizes(n.W, n.H)
2019-01-04 22:40:56 +00:00
return true
}
2019-01-09 21:55:00 +00:00
// ResizeSplit resizes a certain split to a given size
2019-01-04 22:40:56 +00:00
func (n *Node) ResizeSplit(size int) bool {
2019-01-09 23:06:31 +00:00
if len(n.parent.children) <= 1 {
// cannot resize a lone node
2019-01-09 21:55:00 +00:00
return false
}
2019-01-04 22:40:56 +00:00
ind := 0
for i, c := range n.parent.children {
if c.id == n.id {
ind = i
}
}
2019-01-05 21:27:04 +00:00
if n.parent.Kind == STVert {
2019-01-04 22:40:56 +00:00
return n.parent.vResizeSplit(ind, size)
}
return n.parent.hResizeSplit(ind, size)
}
2019-01-09 21:55:00 +00:00
// 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
}
}
2019-01-09 23:06:31 +00:00
n.alignSizes(totw, toth)
}
func (n *Node) alignSizes(totw, toth int) {
2019-01-09 21:55:00 +00:00
// 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()
}
2019-01-09 23:06:31 +00:00
}
func (n *Node) markResize() {
n.markSizes()
2019-01-09 21:55:00 +00:00
n.Resize(n.W, n.H)
}
// vsplits a vertical split and returns the id of the new split
2019-01-04 22:40:56 +00:00
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)
}
2019-01-09 21:55:00 +00:00
// hsplits a horizontal split
2019-01-04 22:40:56 +00:00
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)
}
2019-01-09 21:55:00 +00:00
// 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
}
}
2019-01-09 23:06:31 +00:00
n.markResize()
2019-01-09 21:55:00 +00:00
}
// hsplits a vertical split
2019-01-04 22:40:56 +00:00
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)
2019-01-09 23:06:31 +00:00
n.markResize()
2019-01-04 22:40:56 +00:00
return newid
} else {
2019-01-09 21:55:00 +00:00
nonrh, numr := n.getResizeInfo(true)
2019-01-04 22:40:56 +00:00
2019-01-09 21:55:00 +00:00
// size of resizable area
2019-01-04 22:40:56 +00:00
height := (n.H - nonrh) / (numr + 1)
newid := NewID()
hn := NewNode(STHoriz, n.X, 0, n.W, height, n, newid)
2019-01-09 21:55:00 +00:00
// insert the node into the correct slot
2019-01-04 22:40:56 +00:00
n.children = append(n.children, nil)
inspos := i
if right {
inspos++
}
copy(n.children[inspos+1:], n.children[inspos:])
n.children[inspos] = hn
2019-01-09 21:55:00 +00:00
n.applyNewSize(height, true)
2019-01-04 22:40:56 +00:00
return newid
}
}
2019-01-09 21:55:00 +00:00
// vsplits a horizontal split
2019-01-04 22:40:56 +00:00
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)
2019-01-09 23:06:31 +00:00
n.markResize()
2019-01-04 22:40:56 +00:00
return newid
} else {
2019-01-09 21:55:00 +00:00
nonrw, numr := n.getResizeInfo(false)
2019-01-04 22:40:56 +00:00
width := (n.W - nonrw) / (numr + 1)
newid := NewID()
vn := NewNode(STVert, 0, n.Y, width, n.H, n, newid)
2019-01-09 21:55:00 +00:00
// Inser the node into the correct slot
2019-01-04 22:40:56 +00:00
n.children = append(n.children, nil)
inspos := i
if right {
inspos++
}
copy(n.children[inspos+1:], n.children[inspos:])
n.children[inspos] = vn
2019-01-09 21:55:00 +00:00
n.applyNewSize(width, false)
2019-01-04 22:40:56 +00:00
return newid
}
}
2019-01-09 21:55:00 +00:00
// 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
2019-01-04 22:40:56 +00:00
func (n *Node) HSplit(bottom bool) uint64 {
if !n.IsLeaf() {
return 0
}
2019-01-05 21:27:04 +00:00
if n.Kind == STUndef {
n.Kind = STVert
2019-01-04 22:40:56 +00:00
}
2019-01-05 21:27:04 +00:00
if n.Kind == STVert {
2019-01-04 22:40:56 +00:00
return n.vHSplit(0, bottom)
}
return n.hHSplit(bottom)
}
2019-01-09 21:55:00 +00:00
// 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
2019-01-04 22:40:56 +00:00
func (n *Node) VSplit(right bool) uint64 {
if !n.IsLeaf() {
return 0
}
2019-01-05 21:27:04 +00:00
if n.Kind == STUndef {
n.Kind = STHoriz
2019-01-04 22:40:56 +00:00
}
2019-01-05 21:27:04 +00:00
if n.Kind == STVert {
2019-01-04 22:40:56 +00:00
return n.vVSplit(right)
}
return n.hVSplit(0, right)
}
2019-01-09 21:55:00 +00:00
// 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)
2019-01-09 23:06:31 +00:00
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
}
2019-01-09 21:55:00 +00:00
size := (n.W - nonrs) / numr
if h {
size = (n.H - nonrs) / numr
2019-01-04 22:40:56 +00:00
}
2019-01-09 21:55:00 +00:00
n.applyNewSize(size, h)
2019-01-04 22:40:56 +00:00
}
2019-01-09 21:55:00 +00:00
// Unsplit deletes this split and resizes everything
// else accordingly
2020-02-09 02:06:13 +00:00
func (n *Node) Unsplit() bool {
if !n.IsLeaf() || n.parent == nil {
return false
2019-01-05 19:09:02 +00:00
}
2019-01-09 21:55:00 +00:00
ind := 0
for i, c := range n.parent.children {
if c.id == n.id {
ind = i
2019-01-05 19:09:02 +00:00
}
}
2019-01-09 21:55:00 +00:00
if n.parent.Kind == STVert {
n.parent.unsplit(ind, true)
2019-01-09 23:06:31 +00:00
} else {
n.parent.unsplit(ind, false)
}
if n.parent.IsLeaf() {
2020-02-09 02:06:13 +00:00
return n.parent.Unsplit()
2019-01-05 19:09:02 +00:00
}
2020-02-09 02:06:13 +00:00
return true
2019-01-05 19:09:02 +00:00
}
2019-01-09 21:55:00 +00:00
// String returns the string form of the node and all children (used for debugging)
2019-01-04 22:40:56 +00:00
func (n *Node) String() string {
var strf func(n *Node, ident int) string
strf = func(n *Node, ident int) string {
marker := "|"
2019-01-05 21:27:04 +00:00
if n.Kind == STHoriz {
2019-01-04 22:40:56 +00:00
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)
}