package layouts import ( "git.milar.in/milarin/tui" ) // BorderLayout ia a tui.Layout which places its children onto a given tui.Side type BorderLayout struct { tui.ViewTmpl views map[Slot]tui.View horizontalLayout *LayoutResult verticalLayout *LayoutResult viewDims map[Slot]tui.Dimension } var _ tui.Layout = &BorderLayout{} func NewBorderLayout() *BorderLayout { return &BorderLayout{ views: map[Slot]tui.View{}, viewDims: map[Slot]tui.Dimension{}, } } func (g *BorderLayout) Views() []tui.View { s := make([]tui.View, 0, len(g.views)) for _, view := range g.views { s = append(s, view) } return s } func (g *BorderLayout) SetView(v tui.View, slot Slot) { g.views[slot] = v } func (g *BorderLayout) View(slot Slot) tui.View { return g.views[slot] } func (g *BorderLayout) Draw(buf *tui.ViewBuffer) { g.ViewTmpl.Draw(buf) if g.verticalLayout == nil { g.Layout() } verticalLayout := g.verticalLayout if g.horizontalLayout == nil { g.Layout() } horizontalLayout := g.horizontalLayout remainingVerticalSpacePerView := (buf.Height() - verticalLayout.Sum.Height) if verticalLayout.VerticalNegativeCount > 0 { remainingVerticalSpacePerView /= verticalLayout.VerticalNegativeCount } remainingHorizontalSpacePerView := (buf.Width() - horizontalLayout.Sum.Width) if horizontalLayout.HorizontalNegativeCount > 0 { remainingHorizontalSpacePerView /= horizontalLayout.HorizontalNegativeCount } fitsVertically := buf.Height() >= verticalLayout.Sum.Height fitsHorizontally := buf.Width() >= horizontalLayout.Sum.Width var topHeight int var bottomHeight int var leftWidth int var rightWidth int if view, ok := g.views[Top]; ok { _, topHeight = view.Layout() if fitsVertically { topHeight = iff(topHeight < 0, remainingVerticalSpacePerView, topHeight) } else { topHeight = int(float64(buf.Height()) * float64(topHeight) / float64(verticalLayout.Sum.Height)) } g.viewDims[Top] = tui.D(0, 0, buf.Width(), topHeight) view.Draw(buf.Sub(0, 0, buf.Width(), topHeight)) } if view, ok := g.views[Bottom]; ok { _, bottomHeight = view.Layout() if fitsVertically { bottomHeight = iff(bottomHeight < 0, remainingVerticalSpacePerView, bottomHeight) } else { bottomHeight = int(float64(buf.Height()) * float64(bottomHeight) / float64(verticalLayout.Sum.Height)) } g.viewDims[Bottom] = tui.D(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight) view.Draw(buf.Sub(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight)) } if view, ok := g.views[Left]; ok { leftWidth, _ = view.Layout() if fitsHorizontally { leftWidth = iff(leftWidth < 0, remainingHorizontalSpacePerView, leftWidth) } else { leftWidth = int(float64(buf.Width()) * float64(leftWidth) / float64(horizontalLayout.Sum.Width)) } g.viewDims[Left] = tui.D(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight) view.Draw(buf.Sub(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight)) } if view, ok := g.views[Right]; ok { rightWidth, _ = view.Layout() if fitsHorizontally { rightWidth = iff(rightWidth < 0, remainingHorizontalSpacePerView, rightWidth) } else { rightWidth = int(float64(buf.Width()) * float64(rightWidth) / float64(horizontalLayout.Sum.Width)) } g.viewDims[Right] = tui.D(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight) view.Draw(buf.Sub(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight)) } if view, ok := g.views[Center]; ok { g.viewDims[Center] = tui.D(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight) view.Draw(buf.Sub(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight)) } g.verticalLayout = nil g.horizontalLayout = nil } func (g *BorderLayout) Layout() (prefWidth, prefHeight int) { g.verticalLayout = CalculateLayoutResult([]tui.View{g.View(Top), g.View(Center), g.View(Bottom)}) g.horizontalLayout = CalculateLayoutResult([]tui.View{g.View(Left), g.View(Center), g.View(Right)}) return -1, -1 } func (g *BorderLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) { if g.KeyPressed != nil { return g.KeyPressed(event) } for _, view := range g.Views() { if consumed := view.OnKeyPressed(event); consumed { return true } } return false } func (g *BorderLayout) OnMouseEvent(event *tui.MouseEvent) (consumed bool) { if g.MouseEvent != nil { return g.MouseEvent(event) } for slot, dim := range g.viewDims { if event.Position.In(dim) { g.views[slot].OnMouseEvent(event) return true } } return false } type Slot string const ( Top Slot = "top" Bottom Slot = "bottom" Left Slot = "left" Right Slot = "right" Center Slot = "center" )