mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-10 06:40:24 +09:00
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.
504 lines
10 KiB
Go
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)
|
|
}
|