Files
zyedidia.micro/internal/views/splits.go
Dmytro Maluka 628d9bb37b Fix split pane divider hovering over neighboring split pane (#3070)
Fix the following funny issue: if we open 3 vertical split panes (i.e.
with 2 vertical dividers between them) and drag the rightmost divider
to the left (for resizing the middle and the rightmost split panes), it
does not stop at the leftmost divider but jumps over it and then hovers
over the leftmost split pane. And likewise with horizontal split panes.
2024-03-13 21:02:11 +01:00

504 lines
10 KiB
Go

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 size <= 0 {
return false
}
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)
}