star/bed/layout/layout.go

501 lines
11 KiB
Go
Raw Normal View History

2025-04-26 19:33:14 +08:00
package layout
// Layout represents the window layout.
type Layout interface {
isLayout()
Collect() map[int]Window
Replace(int) Layout
Resize(int, int, int, int) Layout
LeftMargin() int
TopMargin() int
Width() int
Height() int
SplitTop(int) Layout
SplitBottom(int) Layout
SplitLeft(int) Layout
SplitRight(int) Layout
Count() (int, int)
Activate(int) Layout
ActivateFirst() Layout
ActiveWindow() Window
Lookup(func(Window) bool) Window
Close() Layout
}
// Window holds the window index and it is active or not.
type Window struct {
Index int
Active bool
left int
top int
width int
height int
}
// NewLayout creates a new Layout from a window index.
func NewLayout(index int) Layout {
return Window{Index: index, Active: true}
}
func (Window) isLayout() {}
// Collect returns all the Window.
func (l Window) Collect() map[int]Window {
return map[int]Window{l.Index: l}
}
// Replace the active window with new window index.
func (l Window) Replace(index int) Layout {
if l.Active {
// revive:disable-next-line:modifies-value-receiver
l.Index = index
}
return l
}
// Resize recalculates the position.
func (l Window) Resize(left, top, width, height int) Layout {
// revive:disable-next-line:modifies-value-receiver
l.left, l.top, l.width, l.height = left, top, width, height
return l
}
// LeftMargin returns the left margin.
func (l Window) LeftMargin() int {
return l.left
}
// TopMargin returns the top margin.
func (l Window) TopMargin() int {
return l.top
}
// Width returns the width.
func (l Window) Width() int {
return l.width
}
// Height returns the height.
func (l Window) Height() int {
return l.height
}
// SplitTop splits the layout and opens a new window to the top.
func (l Window) SplitTop(index int) Layout {
if !l.Active {
return l
}
return Horizontal{
Top: Window{Index: index, Active: true},
Bottom: Window{Index: l.Index, Active: false},
}
}
// SplitBottom splits the layout and opens a new window to the bottom.
func (l Window) SplitBottom(index int) Layout {
if !l.Active {
return l
}
return Horizontal{
Top: Window{Index: l.Index, Active: false},
Bottom: Window{Index: index, Active: true},
}
}
// SplitLeft splits the layout and opens a new window to the left.
func (l Window) SplitLeft(index int) Layout {
if !l.Active {
return l
}
return Vertical{
Left: Window{Index: index, Active: true},
Right: Window{Index: l.Index, Active: false},
}
}
// SplitRight splits the layout and opens a new window to the right.
func (l Window) SplitRight(index int) Layout {
if !l.Active {
return l
}
return Vertical{
Left: Window{Index: l.Index, Active: false},
Right: Window{Index: index, Active: true},
}
}
// Count returns the width and height counts.
func (Window) Count() (int, int) {
return 1, 1
}
// Activate the specific window layout.
func (l Window) Activate(i int) Layout {
// revive:disable-next-line:modifies-value-receiver
l.Active = l.Index == i
return l
}
// ActivateFirst the first layout.
func (l Window) ActivateFirst() Layout {
// revive:disable-next-line:modifies-value-receiver
l.Active = true
return l
}
// ActiveWindow returns the active window.
func (l Window) ActiveWindow() Window {
if l.Active {
return l
}
return Window{Index: -1}
}
// Lookup search for the window meets the condition.
func (l Window) Lookup(cond func(Window) bool) Window {
if cond(l) {
return l
}
return Window{Index: -1}
}
// Close the active layout.
func (l Window) Close() Layout {
if l.Active {
panic("Active Window should not be closed")
}
return l
}
// Horizontal holds two layout horizontally.
type Horizontal struct {
Top Layout
Bottom Layout
left int
top int
width int
height int
}
func (Horizontal) isLayout() {}
// Collect returns all the Window.
func (l Horizontal) Collect() map[int]Window {
m := l.Top.Collect()
for i, l := range l.Bottom.Collect() {
m[i] = l
}
return m
}
// Replace the active window with new window index.
func (l Horizontal) Replace(index int) Layout {
return Horizontal{
Top: l.Top.Replace(index),
Bottom: l.Bottom.Replace(index),
left: l.left,
top: l.top,
width: l.width,
height: l.height,
}
}
// Resize recalculates the position.
func (l Horizontal) Resize(left, top, width, height int) Layout {
_, h1 := l.Top.Count()
_, h2 := l.Bottom.Count()
topHeight := height * h1 / (h1 + h2)
return Horizontal{
Top: l.Top.Resize(left, top, width, topHeight),
Bottom: l.Bottom.Resize(left, top+topHeight, width, height-topHeight),
left: left,
top: top,
width: width,
height: height,
}
}
// LeftMargin returns the left margin.
func (l Horizontal) LeftMargin() int {
return l.left
}
// TopMargin returns the top margin.
func (l Horizontal) TopMargin() int {
return l.top
}
// Width returns the width.
func (l Horizontal) Width() int {
return l.width
}
// Height returns the height.
func (l Horizontal) Height() int {
return l.height
}
// SplitTop splits the layout and opens a new window to the top.
func (l Horizontal) SplitTop(index int) Layout {
return Horizontal{
Top: l.Top.SplitTop(index),
Bottom: l.Bottom.SplitTop(index),
}
}
// SplitBottom splits the layout and opens a new window to the bottom.
func (l Horizontal) SplitBottom(index int) Layout {
return Horizontal{
Top: l.Top.SplitBottom(index),
Bottom: l.Bottom.SplitBottom(index),
}
}
// SplitLeft splits the layout and opens a new window to the left.
func (l Horizontal) SplitLeft(index int) Layout {
return Horizontal{
Top: l.Top.SplitLeft(index),
Bottom: l.Bottom.SplitLeft(index),
}
}
// SplitRight splits the layout and opens a new window to the right.
func (l Horizontal) SplitRight(index int) Layout {
return Horizontal{
Top: l.Top.SplitRight(index),
Bottom: l.Bottom.SplitRight(index),
}
}
// Count returns the width and height counts.
func (l Horizontal) Count() (int, int) {
w1, h1 := l.Top.Count()
w2, h2 := l.Bottom.Count()
return max(w1, w2), h1 + h2
}
// Activate the specific window layout.
func (l Horizontal) Activate(i int) Layout {
return Horizontal{
Top: l.Top.Activate(i),
Bottom: l.Bottom.Activate(i),
left: l.left,
top: l.top,
width: l.width,
height: l.height,
}
}
// ActivateFirst the first layout.
func (l Horizontal) ActivateFirst() Layout {
return Horizontal{
Top: l.Top.ActivateFirst(),
Bottom: l.Bottom,
left: l.left,
top: l.top,
width: l.width,
height: l.height,
}
}
// ActiveWindow returns the active window.
func (l Horizontal) ActiveWindow() Window {
if layout := l.Top.ActiveWindow(); layout.Index >= 0 {
return layout
}
return l.Bottom.ActiveWindow()
}
// Lookup search for the window meets the condition.
func (l Horizontal) Lookup(cond func(Window) bool) Window {
if layout := l.Top.Lookup(cond); layout.Index >= 0 {
return layout
}
return l.Bottom.Lookup(cond)
}
// Close the active layout.
func (l Horizontal) Close() Layout {
if m, ok := l.Top.(Window); ok {
if m.Active {
return l.Bottom.ActivateFirst()
}
}
if m, ok := l.Bottom.(Window); ok {
if m.Active {
return l.Top.ActivateFirst()
}
}
return Horizontal{
Top: l.Top.Close(),
Bottom: l.Bottom.Close(),
}
}
// Vertical holds two layout vertically.
type Vertical struct {
Left Layout
Right Layout
left int
top int
width int
height int
}
func (Vertical) isLayout() {}
// Collect returns all the Window.
func (l Vertical) Collect() map[int]Window {
m := l.Left.Collect()
for i, l := range l.Right.Collect() {
m[i] = l
}
return m
}
// Replace the active window with new window index.
func (l Vertical) Replace(index int) Layout {
return Vertical{
Left: l.Left.Replace(index),
Right: l.Right.Replace(index),
left: l.left,
top: l.top,
width: l.width,
height: l.height,
}
}
// Resize recalculates the position.
func (l Vertical) Resize(left, top, width, height int) Layout {
w1, _ := l.Left.Count()
w2, _ := l.Right.Count()
leftWidth := width * w1 / (w1 + w2)
return Vertical{
Left: l.Left.Resize(left, top, leftWidth, height),
Right: l.Right.Resize(
min(left+leftWidth+1, left+width), top,
max(width-leftWidth-1, 0), height),
left: left,
top: top,
width: width,
height: height,
}
}
// LeftMargin returns the left margin.
func (l Vertical) LeftMargin() int {
return l.left
}
// TopMargin returns the top margin.
func (l Vertical) TopMargin() int {
return l.top
}
// Width returns the width.
func (l Vertical) Width() int {
return l.width
}
// Height returns the height.
func (l Vertical) Height() int {
return l.height
}
// SplitTop splits the layout and opens a new window to the top.
func (l Vertical) SplitTop(index int) Layout {
return Vertical{
Left: l.Left.SplitTop(index),
Right: l.Right.SplitTop(index),
}
}
// SplitBottom splits the layout and opens a new window to the bottom.
func (l Vertical) SplitBottom(index int) Layout {
return Vertical{
Left: l.Left.SplitBottom(index),
Right: l.Right.SplitBottom(index),
}
}
// SplitLeft splits the layout and opens a new window to the left.
func (l Vertical) SplitLeft(index int) Layout {
return Vertical{
Left: l.Left.SplitLeft(index),
Right: l.Right.SplitLeft(index),
}
}
// SplitRight splits the layout and opens a new window to the right.
func (l Vertical) SplitRight(index int) Layout {
return Vertical{
Left: l.Left.SplitRight(index),
Right: l.Right.SplitRight(index),
}
}
// Count returns the width and height counts.
func (l Vertical) Count() (int, int) {
w1, h1 := l.Left.Count()
w2, h2 := l.Right.Count()
return w1 + w2, max(h1, h2)
}
// Activate the specific window layout.
func (l Vertical) Activate(i int) Layout {
return Vertical{
Left: l.Left.Activate(i),
Right: l.Right.Activate(i),
left: l.left,
top: l.top,
width: l.width,
height: l.height,
}
}
// ActivateFirst the first layout.
func (l Vertical) ActivateFirst() Layout {
return Vertical{
Left: l.Left.ActivateFirst(),
Right: l.Right,
left: l.left,
top: l.top,
width: l.width,
height: l.height,
}
}
// ActiveWindow returns the active window.
func (l Vertical) ActiveWindow() Window {
if layout := l.Left.ActiveWindow(); layout.Index >= 0 {
return layout
}
return l.Right.ActiveWindow()
}
// Lookup search for the window meets the condition.
func (l Vertical) Lookup(cond func(Window) bool) Window {
if layout := l.Left.Lookup(cond); layout.Index >= 0 {
return layout
}
return l.Right.Lookup(cond)
}
// Close the active layout.
func (l Vertical) Close() Layout {
if m, ok := l.Left.(Window); ok {
if m.Active {
return l.Right.ActivateFirst()
}
}
if m, ok := l.Right.(Window); ok {
if m.Active {
return l.Left.ActivateFirst()
}
}
return Vertical{
Left: l.Left.Close(),
Right: l.Right.Close(),
}
}