501 lines
11 KiB
Go
501 lines
11 KiB
Go
|
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(),
|
||
|
}
|
||
|
}
|