more views

This commit is contained in:
Timon Ringwald 2022-04-02 15:09:52 +02:00
parent dfa00f5fe3
commit b797dc0b2c
10 changed files with 257 additions and 15 deletions

View File

@ -55,6 +55,46 @@ func TestFlowGroup(t *testing.T) {
fmt.Println(err) fmt.Println(err)
} }
func TestSeparatorGroup(t *testing.T) {
textView := views.NewTextView("hello world!")
textView.SetStyle(tui.StyleDefault.Background(tcell.ColorRed).Foreground(tcell.ColorBlack))
frameView := views.NewFrameView(textView)
textView2 := views.NewTextView("Hi!")
textView2.SetStyle(tui.StyleDefault.Background(tcell.ColorBlue).Foreground(tcell.ColorYellow))
growView := views.NewGrowView()
growView.SetStyle(tui.StyleDefault.Background(tcell.ColorGreen))
growView2 := views.NewGrowView()
growView2.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow))
separatorGroup := views.NewSeparatorGroup(tui.Vertical)
separatorGroup.AppendView(frameView, 1)
separatorGroup.AppendView(growView, 1)
separatorGroup.AppendView(textView2, 1)
screen, err := tui.NewScreen(separatorGroup)
if err != nil {
t.Error(err)
return
}
screen.KeyPressed = func(event *tui.KeyEvent) (consumed bool) {
textView.Text = event.When().String()
if event.Key() == tcell.KeyCtrlC {
screen.StopWithError(errors.New(fmt.Sprintf("key: %#v | rune: %s", event.Key(), string(event.Rune()))))
}
return true
}
err = screen.Start()
fmt.Println(err)
}
func TestBorderGroup(t *testing.T) { func TestBorderGroup(t *testing.T) {
topView := views.NewConstrainView(nil) topView := views.NewConstrainView(nil)
topView.SetStyle(tui.StyleDefault.Background(tcell.ColorBlue)) topView.SetStyle(tui.StyleDefault.Background(tcell.ColorBlue))

View File

@ -35,8 +35,22 @@ const (
type Side uint8 type Side uint8
const ( const (
Top Side = iota SideTop Side = iota
Bottom SideBottom
Left SideLeft
Right SideRight
)
type Anchor uint8
const (
AnchorTopLeft Anchor = iota
AnchorTop
AnchorTopRight
AnchorLeft
AnchorCenter
AnchorRight
AnchorBottomLeft
AnchorBottom
AnchorBottomRight
) )

View File

@ -87,3 +87,28 @@ func iff[T any](condition bool, trueValue, falseValue T) T {
} }
return falseValue return falseValue
} }
func ConstrainBufferToAnchor(buf *ViewBuffer, anchor Anchor, width, height int) *ViewBuffer {
switch anchor {
default:
fallthrough
case AnchorTopLeft:
return buf.Sub(0, 0, width, height)
case AnchorTop:
return buf.Sub(buf.Width()/2-width/2, 0, width, height)
case AnchorTopRight:
return buf.Sub(buf.Width()-width, 0, width, height)
case AnchorLeft:
return buf.Sub(0, buf.Height()/2-height/2, width, height)
case AnchorCenter:
return buf.Sub(buf.Width()/2-width/2, buf.Height()/2-height/2, width, height)
case AnchorRight:
return buf.Sub(buf.Width()-width, buf.Height()/2-height/2, width, height)
case AnchorBottomLeft:
return buf.Sub(0, buf.Height()-height, width, height)
case AnchorBottom:
return buf.Sub(buf.Width()/2-width/2, buf.Height()-height, width, height)
case AnchorBottomRight:
return buf.Sub(buf.Width()-width, buf.Height()-height, width, height)
}
}

View File

@ -10,7 +10,14 @@ type View interface {
SetStyle(s Style) SetStyle(s Style)
Style() Style Style() Style
// Layout is usually called by the parent view to ask the view's preferred size.
// If the parent view does not care about its preferred size, it might not be called at all.
// Negative values indicate as much space as possible.
Layout() (prefWidth, prefHeight int) Layout() (prefWidth, prefHeight int)
// Draw is called for each view when it should print itself onto the screen.
// The parent view has full control of the ViewBuffer size
// and may or may not use the values returned from Layout() to set the size.
Draw(buf *ViewBuffer) Draw(buf *ViewBuffer)
} }

View File

@ -4,7 +4,7 @@ import (
"git.tordarus.net/Tordarus/tui" "git.tordarus.net/Tordarus/tui"
) )
// BorderGroup ia a tui.Group which places its children in a linear layout // BorderGroup ia a tui.Group which places its children onto a given tui.Side
type BorderGroup struct { type BorderGroup struct {
tui.ViewTmpl tui.ViewTmpl
views map[Slot]tui.View views map[Slot]tui.View

View File

@ -39,6 +39,15 @@ func (g *FlowGroup) InsertView(v tui.View, index int) {
g.views = append(g.views[:index], append([]tui.View{v}, g.views[index:]...)...) g.views = append(g.views[:index], append([]tui.View{v}, g.views[index:]...)...)
} }
func (g *FlowGroup) RemoveView(v tui.View) {
for index, view := range g.Views() {
if view == v {
g.views = append(g.views[:index], g.views[index:]...)
return
}
}
}
func (g *FlowGroup) Draw(buf *tui.ViewBuffer) { func (g *FlowGroup) Draw(buf *tui.ViewBuffer) {
g.ViewTmpl.Draw(buf) g.ViewTmpl.Draw(buf)

35
views/frameview.go Normal file
View File

@ -0,0 +1,35 @@
package views
import "git.tordarus.net/Tordarus/tui"
// FrameView is a tui.Wrapper which draws its view preferably with preferred size on its tui.Anchor point
type FrameView struct {
tui.WrapperTmpl
Anchor tui.Anchor
}
var _ tui.View = &FrameView{}
func NewFrameView(view tui.View) *FrameView {
v := new(FrameView)
v.SetView(view)
v.Anchor = tui.AnchorCenter
return v
}
func (g *FrameView) Draw(buf *tui.ViewBuffer) {
g.ViewTmpl.Draw(buf)
w, h := g.View().Layout()
w = iff(w >= 0, w, buf.Width())
h = iff(h >= 0, h, buf.Height())
g.View().Draw(tui.ConstrainBufferToAnchor(buf, g.Anchor, w, h))
}
func (v *FrameView) Layout() (prefWidth, prefHeight int) {
return -1, -1
}
func (v *FrameView) Style() tui.Style {
return v.ViewTmpl.Style()
}

View File

@ -18,10 +18,10 @@ func NewMarginView(view tui.View) *MarginView {
} }
func (g *MarginView) Draw(buf *tui.ViewBuffer) { func (g *MarginView) Draw(buf *tui.ViewBuffer) {
x := g.Margin[tui.Left] x := g.Margin[tui.SideLeft]
y := g.Margin[tui.Top] y := g.Margin[tui.SideTop]
w := buf.Width() - x - g.Margin[tui.Right] w := buf.Width() - x - g.Margin[tui.SideRight]
h := buf.Height() - y - g.Margin[tui.Bottom] h := buf.Height() - y - g.Margin[tui.SideBottom]
g.ViewTmpl.Draw(buf) g.ViewTmpl.Draw(buf)
g.View().Draw(buf.Sub(x, y, w, h)) g.View().Draw(buf.Sub(x, y, w, h))
@ -29,8 +29,8 @@ func (g *MarginView) Draw(buf *tui.ViewBuffer) {
func (v *MarginView) Layout() (prefWidth, prefHeight int) { func (v *MarginView) Layout() (prefWidth, prefHeight int) {
w, h := v.View().Layout() w, h := v.View().Layout()
w = iff(w > 0, w+v.Margin[tui.Left]+v.Margin[tui.Right], w) w = iff(w > 0, w+v.Margin[tui.SideLeft]+v.Margin[tui.SideRight], w)
h = iff(h > 0, h+v.Margin[tui.Top]+v.Margin[tui.Bottom], h) h = iff(h > 0, h+v.Margin[tui.SideTop]+v.Margin[tui.SideBottom], h)
return w, h return w, h
} }
@ -40,9 +40,9 @@ func (v *MarginView) Style() tui.Style {
func (v *MarginView) SetMargin(top, right, bottom, left int) { func (v *MarginView) SetMargin(top, right, bottom, left int) {
v.Margin = map[tui.Side]int{ v.Margin = map[tui.Side]int{
tui.Top: top, tui.SideTop: top,
tui.Right: right, tui.SideRight: right,
tui.Bottom: bottom, tui.SideBottom: bottom,
tui.Left: left, tui.SideLeft: left,
} }
} }

111
views/separatorgroup.go Normal file
View File

@ -0,0 +1,111 @@
package views
import (
"git.tordarus.net/Tordarus/tui"
)
// SeperatorGroup ia a tui.Group which separates
type SeperatorGroup struct {
tui.ViewTmpl
views []tui.View
gravity map[tui.View]int
gravitySum int
Orientation tui.Orientation
}
var _ tui.Group = &SeperatorGroup{}
func NewSeparatorGroup(orientation tui.Orientation) *SeperatorGroup {
return &SeperatorGroup{
views: make([]tui.View, 0),
gravity: map[tui.View]int{},
Orientation: orientation,
}
}
func (g *SeperatorGroup) Views() []tui.View {
return g.views[:]
}
func (g *SeperatorGroup) AppendView(v tui.View, gravity int) {
g.views = append(g.views, v)
g.gravitySum += gravity
g.gravity[v] = gravity
}
func (g *SeperatorGroup) PrependView(v tui.View, gravity int) {
g.views = append([]tui.View{v}, g.views...)
g.gravitySum += gravity
g.gravity[v] = gravity
}
func (g *SeperatorGroup) InsertView(v tui.View, index int, gravity int) {
g.views = append(g.views[:index], append([]tui.View{v}, g.views[index:]...)...)
g.gravitySum += gravity
g.gravity[v] = gravity
}
func (g *SeperatorGroup) SetGravity(v tui.View, gravity int) {
for _, view := range g.Views() {
if view == v {
g.gravitySum += gravity - g.gravity[v]
g.gravity[v] = gravity
return
}
}
}
func (g *SeperatorGroup) RemoveView(v tui.View) {
for index, view := range g.Views() {
if view == v {
g.views = append(g.views[:index], g.views[index:]...)
g.gravitySum -= g.gravity[v]
delete(g.gravity, v)
return
}
}
}
func (g *SeperatorGroup) View(slot Slot) tui.View {
return g.views[slot]
}
func (g *SeperatorGroup) Draw(buf *tui.ViewBuffer) {
g.ViewTmpl.Draw(buf)
if g.Orientation == tui.Horizontal {
x := 0
for _, v := range g.Views() {
viewGravity := g.gravity[v]
percentage := float64(viewGravity) / float64(g.gravitySum)
width := int(percentage * float64(buf.Width()))
v.Draw(buf.Sub(x, 0, width, buf.Height()))
x += width
}
} else if g.Orientation == tui.Vertical {
y := 0
for _, v := range g.Views() {
viewGravity := g.gravity[v]
percentage := float64(viewGravity) / float64(g.gravitySum)
height := int(percentage * float64(buf.Height()))
v.Draw(buf.Sub(0, y, buf.Width(), height))
y += height
}
}
}
func (g *SeperatorGroup) Layout() (prefWidth, prefHeight int) {
return -1, -1
}
func (g *SeperatorGroup) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
for _, view := range g.Views() {
if view.OnKeyPressed(event) {
return true
}
}
return false
}

View File

@ -13,6 +13,7 @@ type TextView struct {
var _ tui.View = &TextView{} var _ tui.View = &TextView{}
func (v *TextView) Draw(buf *tui.ViewBuffer) { func (v *TextView) Draw(buf *tui.ViewBuffer) {
v.ViewTmpl.Draw(buf)
tui.WriteMultiLineString(buf, v.Text, v.Style(), 0, 0) tui.WriteMultiLineString(buf, v.Text, v.Style(), 0, 0)
} }