Files
vif-log/internal/ui/layout.go
T
2026-08-06 10:18:22 -04:00

112 lines
2.4 KiB
Go

package ui
import "github.com/lixenwraith/terminal/tui"
// Pane identifies a region of the frame.
type Pane uint8
const (
PaneNone Pane = iota
PaneHeader
PaneList
PaneDetail
PaneStatus
PaneFooter
)
// Dir is the split axis of a container node.
type Dir uint8
const (
Vertical Dir = iota // children stacked top to bottom
Horizontal // children placed left to right
)
// Node is either a leaf bound to a Pane or a container. Layout is data:
// changing pane count or arrangement edits this tree only.
type Node struct {
Pane Pane
Dir Dir
Fixed int // size along the parent axis, 0 = share by Weight
Weight float64
Children []Node
}
// Layout is the root of the pane tree.
type Layout struct{ Root Node }
// DefaultLayout is the phase-1 layout: header, list|detail body, status, footer.
func DefaultLayout() Layout {
return Layout{Root: Node{Dir: Vertical, Children: []Node{
{Pane: PaneHeader, Fixed: 1},
{Dir: Horizontal, Weight: 1, Children: []Node{
{Pane: PaneList, Weight: 0.62},
{Pane: PaneDetail, Weight: 0.38},
}},
{Pane: PaneStatus, Fixed: 1},
{Pane: PaneFooter, Fixed: 1},
}}}
}
// Resolve assigns a region to every leaf pane.
func (l Layout) Resolve(r tui.Region) map[Pane]tui.Region {
out := make(map[Pane]tui.Region, 8)
place(l.Root, r, out)
return out
}
func place(n Node, r tui.Region, out map[Pane]tui.Region) {
if len(n.Children) == 0 {
if n.Pane != PaneNone {
out[n.Pane] = r
}
return
}
total := r.H
if n.Dir == Horizontal {
total = r.W
}
sizes := share(n.Children, total)
pos := 0
for i, c := range n.Children {
var sub tui.Region
if n.Dir == Horizontal {
sub = r.Sub(pos, 0, sizes[i], r.H)
} else {
sub = r.Sub(0, pos, r.W, sizes[i])
}
place(c, sub, out)
pos += sizes[i]
}
}
// share splits total between fixed and weighted children; the rounding
// remainder goes to the last weighted child.
func share(cs []Node, total int) []int {
sizes := make([]int, len(cs))
rest, sum := total, 0.0
for i, c := range cs {
if c.Fixed > 0 {
s := min(c.Fixed, max(rest, 0))
sizes[i], rest = s, rest-s
} else {
sum += c.Weight
}
}
if sum <= 0 {
sum = 1
}
lastW, acc := -1, 0
for i, c := range cs {
if c.Fixed > 0 {
continue
}
s := int(float64(rest) * c.Weight / sum)
sizes[i], acc, lastW = s, acc+s, i
}
if lastW >= 0 {
sizes[lastW] += rest - acc
}
return sizes
}