more views
This commit is contained in:
parent
d335211770
commit
dfa00f5fe3
@ -5,8 +5,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func drawBuffer(scr tcell.Screen, buf *ViewBuffer) {
|
func drawBuffer(scr tcell.Screen, buf *ViewBuffer) {
|
||||||
buf.ForEach(func(x, y int, rn Rune) {
|
// buf.ForEach(func(x, y int, rn Rune) {
|
||||||
scr.SetContent(x, y, rn.Rn, nil, rn.Style)
|
// scr.SetContent(x, y, rn.Rn, nil, rn.Style)
|
||||||
|
// })
|
||||||
|
|
||||||
|
buf.ForEachLine(func(y int, content []Rune) {
|
||||||
|
for x := 0; x < buf.Width(); x++ {
|
||||||
|
rn := content[x]
|
||||||
|
if rn.Rn >= '─' && rn.Rn <= '╿' {
|
||||||
|
scr.SetContent(x, y, rn.Rn, []rune{content[x+1].Rn}, rn.Style)
|
||||||
|
x++
|
||||||
|
} else {
|
||||||
|
scr.SetContent(x, y, rn.Rn, nil, rn.Style)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
scr.Show()
|
scr.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func truncateBuffer(buf *ViewBuffer, w, h int) *ViewBuffer {
|
||||||
|
if w < 0 {
|
||||||
|
w = buf.Width()
|
||||||
|
}
|
||||||
|
if h < 0 {
|
||||||
|
h = buf.Height()
|
||||||
|
}
|
||||||
|
return buf.Sub(0, 0, w, h)
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
type Events interface {
|
type Events interface {
|
||||||
|
|
||||||
|
// KeyPressed is called every time a key or key-combination is pressed.
|
||||||
|
// If KeyPressed returns true, the event will not be passed onto child views
|
||||||
OnKeyPressed(event *KeyEvent) (consumed bool)
|
OnKeyPressed(event *KeyEvent) (consumed bool)
|
||||||
}
|
}
|
||||||
|
4
go.mod
4
go.mod
@ -3,8 +3,9 @@ module git.tordarus.net/Tordarus/tui
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.tordarus.net/Tordarus/buf2d v1.1.0
|
git.tordarus.net/Tordarus/buf2d v1.1.1
|
||||||
github.com/gdamore/tcell v1.4.0
|
github.com/gdamore/tcell v1.4.0
|
||||||
|
golang.org/x/text v0.3.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -12,5 +13,4 @@ require (
|
|||||||
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.7 // indirect
|
github.com/mattn/go-runewidth v0.0.7 // indirect
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect
|
||||||
golang.org/x/text v0.3.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
7
go.sum
7
go.sum
@ -1,5 +1,5 @@
|
|||||||
git.tordarus.net/Tordarus/buf2d v1.1.0 h1:rIZjD7yeX5XK2D1h75ET5Og0u/NQF3eVonnC5aaqVkQ=
|
git.tordarus.net/Tordarus/buf2d v1.1.1 h1:rYvQ2YveqogCoKy5andQxuORPusWbUhpnqJhzVkTlRs=
|
||||||
git.tordarus.net/Tordarus/buf2d v1.1.0/go.mod h1:XXPpS8nQK0gUI0ki7ftV/qlprsGCRWFVSD4ybvDuUL8=
|
git.tordarus.net/Tordarus/buf2d v1.1.1/go.mod h1:XXPpS8nQK0gUI0ki7ftV/qlprsGCRWFVSD4ybvDuUL8=
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
||||||
@ -10,5 +10,6 @@ github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+tw
|
|||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
27
screen.go
27
screen.go
@ -10,8 +10,12 @@ import (
|
|||||||
|
|
||||||
type Screen struct {
|
type Screen struct {
|
||||||
scr tcell.Screen
|
scr tcell.Screen
|
||||||
|
buf *ViewBuffer
|
||||||
stopCh chan error
|
stopCh chan error
|
||||||
Root View
|
Root View
|
||||||
|
|
||||||
|
// KeyPressed is called every time a key or key-combination is pressed.
|
||||||
|
KeyPressed func(event *KeyEvent) (consumed bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScreen(root View) (*Screen, error) {
|
func NewScreen(root View) (*Screen, error) {
|
||||||
@ -37,10 +41,7 @@ func (s *Screen) eventloop() {
|
|||||||
case *tcell.EventResize:
|
case *tcell.EventResize:
|
||||||
go s.Redraw()
|
go s.Redraw()
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
go func() {
|
go s.onKeyPressed(event)
|
||||||
s.Root.OnKeyPressed(event)
|
|
||||||
s.Redraw()
|
|
||||||
}()
|
|
||||||
default:
|
default:
|
||||||
s.StopWithError(errors.New(fmt.Sprintf("%#v", event)))
|
s.StopWithError(errors.New(fmt.Sprintf("%#v", event)))
|
||||||
}
|
}
|
||||||
@ -67,9 +68,21 @@ func (s *Screen) StopWithError(err error) {
|
|||||||
s.stopCh <- err
|
s.stopCh <- err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Screen) onKeyPressed(event *KeyEvent) {
|
||||||
|
if s.KeyPressed == nil || !s.KeyPressed(event) {
|
||||||
|
s.Root.OnKeyPressed(event)
|
||||||
|
}
|
||||||
|
s.Redraw()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Screen) Redraw() {
|
func (s *Screen) Redraw() {
|
||||||
w, h := s.scr.Size()
|
w, h := s.scr.Size()
|
||||||
buf := buf2d.NewBuffer(w, h, DefaultRune)
|
|
||||||
s.Root.Draw(buf)
|
if s.buf == nil || s.buf.Width() != w || s.buf.Height() != h {
|
||||||
drawBuffer(s.scr, buf)
|
s.buf = buf2d.NewBuffer(w, h, DefaultRune)
|
||||||
|
}
|
||||||
|
|
||||||
|
rw, rh := s.Root.Layout()
|
||||||
|
s.Root.Draw(truncateBuffer(s.buf, rw, rh))
|
||||||
|
drawBuffer(s.scr, s.buf)
|
||||||
}
|
}
|
||||||
|
@ -10,21 +10,90 @@ import (
|
|||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestScreen(t *testing.T) {
|
func TestFlowGroup(t *testing.T) {
|
||||||
textView := views.NewTextView("hello world")
|
textView := views.NewTextView("hello world!")
|
||||||
eventView := views.NewEventView(textView)
|
textView.SetStyle(tui.StyleDefault.Background(tcell.ColorRed).Foreground(tcell.ColorBlack))
|
||||||
screen, err := tui.NewScreen(eventView)
|
|
||||||
|
marginView := views.NewMarginView(textView)
|
||||||
|
marginView.SetMargin(3, 1, 1, 0)
|
||||||
|
|
||||||
|
//borderView := views.NewBorderView(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))
|
||||||
|
|
||||||
|
flowGroup := views.NewFlowGroup(tui.Vertical)
|
||||||
|
flowGroup.AppendViews(marginView, growView, textView2)
|
||||||
|
|
||||||
|
constrainView := views.NewConstrainView(flowGroup)
|
||||||
|
constrainView.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
|
||||||
|
constrainView.Constrain(-1, -1)
|
||||||
|
|
||||||
|
screen, err := tui.NewScreen(constrainView)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
eventView.KeyPressed = func(event *tui.KeyEvent) (consumed bool) {
|
screen.KeyPressed = func(event *tui.KeyEvent) (consumed bool) {
|
||||||
|
textView.Text = event.When().String()
|
||||||
|
|
||||||
if event.Key() == tcell.KeyCtrlC {
|
if event.Key() == tcell.KeyCtrlC {
|
||||||
screen.StopWithError(errors.New(fmt.Sprintf("key: %#v | rune: %s", event.Key(), string(event.Rune()))))
|
screen.StopWithError(errors.New(fmt.Sprintf("key: %#v | rune: %s", event.Key(), string(event.Rune()))))
|
||||||
}
|
}
|
||||||
|
|
||||||
//textView.Text = event.When().String()
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
err = screen.Start()
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBorderGroup(t *testing.T) {
|
||||||
|
topView := views.NewConstrainView(nil)
|
||||||
|
topView.SetStyle(tui.StyleDefault.Background(tcell.ColorBlue))
|
||||||
|
topView.Constrain(10, 10)
|
||||||
|
|
||||||
|
bottomView := views.NewConstrainView(nil)
|
||||||
|
bottomView.SetStyle(tui.StyleDefault.Background(tcell.ColorRed))
|
||||||
|
bottomView.Constrain(10, 10)
|
||||||
|
|
||||||
|
leftView := views.NewConstrainView(nil)
|
||||||
|
leftView.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow))
|
||||||
|
leftView.Constrain(10, 10)
|
||||||
|
|
||||||
|
rightView := views.NewConstrainView(nil)
|
||||||
|
rightView.SetStyle(tui.StyleDefault.Background(tcell.ColorGreen))
|
||||||
|
rightView.Constrain(10, 10)
|
||||||
|
|
||||||
|
centerView := views.NewConstrainView(nil)
|
||||||
|
centerView.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
|
||||||
|
centerView.Constrain(10, 10)
|
||||||
|
|
||||||
|
borderGroup := views.NewBorderGroup()
|
||||||
|
borderGroup.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
|
||||||
|
borderGroup.SetView(topView, views.Top)
|
||||||
|
borderGroup.SetView(bottomView, views.Bottom)
|
||||||
|
borderGroup.SetView(leftView, views.Left)
|
||||||
|
borderGroup.SetView(rightView, views.Right)
|
||||||
|
borderGroup.SetView(centerView, views.Center)
|
||||||
|
|
||||||
|
screen, err := tui.NewScreen(borderGroup)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
screen.KeyPressed = func(event *tui.KeyEvent) (consumed bool) {
|
||||||
|
if event.Key() == tcell.KeyCtrlC {
|
||||||
|
screen.StopWithError(errors.New(fmt.Sprintf("key: %#v | rune: %s", event.Key(), string(event.Rune()))))
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
types.go
29
types.go
@ -11,3 +11,32 @@ type Style = tcell.Style
|
|||||||
type Color = tcell.Color
|
type Color = tcell.Color
|
||||||
|
|
||||||
var StyleDefault Style = tcell.StyleDefault
|
var StyleDefault Style = tcell.StyleDefault
|
||||||
|
|
||||||
|
type Point struct {
|
||||||
|
X, Y int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Size struct {
|
||||||
|
Width, Height int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dimension struct {
|
||||||
|
Point
|
||||||
|
Size
|
||||||
|
}
|
||||||
|
|
||||||
|
type Orientation uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Horizontal Orientation = iota
|
||||||
|
Vertical
|
||||||
|
)
|
||||||
|
|
||||||
|
type Side uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Top Side = iota
|
||||||
|
Bottom
|
||||||
|
Left
|
||||||
|
Right
|
||||||
|
)
|
||||||
|
66
utils.go
66
utils.go
@ -2,30 +2,88 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/text/width"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteString writes a whole string to the buffer at position (x,y)
|
// WriteString writes a whole string to the buffer at position (x,y)
|
||||||
// no word wrap is applied at all. If the string does not fit, it will be truncated
|
// no word wrap is applied at all. If the string does not fit, it will be truncated
|
||||||
func WriteString(b *ViewBuffer, str string, style Style, x, y int) {
|
func WriteString(b *ViewBuffer, str string, style Style, x, y int) (width int) {
|
||||||
dx := x
|
dx := x
|
||||||
for _, r := range str {
|
for _, r := range str {
|
||||||
if dx >= b.Width() {
|
if dx >= b.Width() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Set(dx, y, Rune{r, style})
|
b.Set(dx, y, Rune{r, style})
|
||||||
dx++
|
dx += runeWidth(r)
|
||||||
}
|
}
|
||||||
|
return dx - x
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMultiLineString writes a multi-line string to the buffer at position (x,y)
|
// WriteMultiLineString writes a multi-line string to the buffer at position (x,y)
|
||||||
// no word wrap is applied at all. If a line does not fit horizontally, it will be truncated
|
// no word wrap is applied at all. If a line does not fit horizontally, it will be truncated
|
||||||
// All lines which do not fit vertically will be truncated as well
|
// All lines which do not fit vertically will be truncated as well
|
||||||
func WriteMultiLineString(b *ViewBuffer, str string, style Style, x, y int) {
|
func WriteMultiLineString(b *ViewBuffer, str string, style Style, x, y int) (maxLineWidth, lineCount int) {
|
||||||
lines := strings.Split(str, "\n")
|
lines := strings.Split(str, "\n")
|
||||||
for dy, line := range lines {
|
for dy, line := range lines {
|
||||||
if dy >= b.Height() {
|
if dy >= b.Height() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteString(b, line, style, x, y+dy)
|
lineWidth := WriteString(b, line, style, x, y+dy)
|
||||||
|
maxLineWidth = max(maxLineWidth, lineWidth)
|
||||||
|
}
|
||||||
|
return maxLineWidth, len(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeasureString measures how much horizontal space str consumes when drawn to a buffer
|
||||||
|
func MeasureString(str string) (width int) {
|
||||||
|
dx := 0
|
||||||
|
for _, r := range str {
|
||||||
|
dx += runeWidth(r)
|
||||||
|
}
|
||||||
|
return dx
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeasureString measures how much horizontal and vertical space str consumes when drawn to a buffer
|
||||||
|
func MeasureMultiLineString(str string) (maxLineWidth, lineCount int) {
|
||||||
|
lines := strings.Split(str, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
lineWidth := MeasureString(line)
|
||||||
|
maxLineWidth = max(maxLineWidth, lineWidth)
|
||||||
|
}
|
||||||
|
return maxLineWidth, len(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runeWidth(r rune) int {
|
||||||
|
//fmt.Println(r, width.LookupRune(r).Kind())
|
||||||
|
switch width.LookupRune(r).Kind() {
|
||||||
|
case width.EastAsianFullwidth:
|
||||||
|
fallthrough
|
||||||
|
case width.EastAsianWide:
|
||||||
|
return 2
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func min(x, y int) int {
|
||||||
|
if x < y {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(x, y int) int {
|
||||||
|
if x > y {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func iff[T any](condition bool, trueValue, falseValue T) T {
|
||||||
|
if condition {
|
||||||
|
return trueValue
|
||||||
|
}
|
||||||
|
return falseValue
|
||||||
|
}
|
||||||
|
19
view.go
19
view.go
@ -1,34 +1,27 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
// View defines the behavior of any element displayable on screen
|
// View defines the behavior of any element displayable on screen.
|
||||||
// To define custom Views, it is recommended to add ViewTmpl
|
// To define custom Views, it is recommended to add ViewTmpl
|
||||||
// as the promoted anonymous field for your custom View struct.
|
// as the promoted anonymous field for your custom View struct.
|
||||||
// It implements the View interface with useful default behavior
|
// It implements the View interface with useful default behavior
|
||||||
type View interface {
|
type View interface {
|
||||||
Events
|
Events
|
||||||
|
|
||||||
SetForeground(color Color)
|
SetStyle(s Style)
|
||||||
Foreground() Color
|
|
||||||
|
|
||||||
SetBackground(color Color)
|
|
||||||
Background() Color
|
|
||||||
|
|
||||||
Style() Style
|
Style() Style
|
||||||
|
|
||||||
Draw(*ViewBuffer)
|
Layout() (prefWidth, prefHeight int)
|
||||||
|
Draw(buf *ViewBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group defines the behavior of a View which can hold multiple sub views
|
// Group defines the behavior of a View which can hold multiple sub views
|
||||||
// To define custom Groups, it is recommended to add GroupTmpl
|
|
||||||
// as the promoted anonymous field for your custom Wrapper struct.
|
|
||||||
// It implements the Group interface with useful default behavior
|
|
||||||
type Group interface {
|
type Group interface {
|
||||||
View
|
View
|
||||||
|
|
||||||
Children() []View
|
Views() []View
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper defines the behavior of a GroupView which can hold exactly one sub view
|
// Wrapper defines the behavior of a GroupView which can hold exactly one sub view.
|
||||||
// To define custom Wrappers, it is recommended to add WrapperTmpl
|
// To define custom Wrappers, it is recommended to add WrapperTmpl
|
||||||
// as the promoted anonymous field for your custom Wrapper struct.
|
// as the promoted anonymous field for your custom Wrapper struct.
|
||||||
// It implements the Wrapper interface with useful default behavior
|
// It implements the Wrapper interface with useful default behavior
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package tui
|
|
||||||
|
|
||||||
// TODO GroupTmpl
|
|
149
views/bordergroup.go
Normal file
149
views/bordergroup.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.tordarus.net/Tordarus/tui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BorderGroup ia a tui.Group which places its children in a linear layout
|
||||||
|
type BorderGroup struct {
|
||||||
|
tui.ViewTmpl
|
||||||
|
views map[Slot]tui.View
|
||||||
|
horizontalLayout *LayoutResult
|
||||||
|
verticalLayout *LayoutResult
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tui.Group = &BorderGroup{}
|
||||||
|
|
||||||
|
func NewBorderGroup() *BorderGroup {
|
||||||
|
return &BorderGroup{
|
||||||
|
views: map[Slot]tui.View{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *BorderGroup) Views() []tui.View {
|
||||||
|
s := make([]tui.View, 0, len(g.views))
|
||||||
|
for _, view := range g.views {
|
||||||
|
s = append(s, view)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *BorderGroup) SetView(v tui.View, slot Slot) {
|
||||||
|
g.views[slot] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *BorderGroup) View(slot Slot) tui.View {
|
||||||
|
return g.views[slot]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *BorderGroup) 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
view.Draw(buf.Sub(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
if view, ok := g.views[Center]; ok {
|
||||||
|
view.Draw(buf.Sub(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
g.verticalLayout = nil
|
||||||
|
g.horizontalLayout = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *BorderGroup) 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 *BorderGroup) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
||||||
|
for _, view := range g.Views() {
|
||||||
|
if view.OnKeyPressed(event) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Slot uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Top Slot = iota
|
||||||
|
Bottom
|
||||||
|
Left
|
||||||
|
Right
|
||||||
|
Center
|
||||||
|
)
|
93
views/borderview.go
Normal file
93
views/borderview.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import "git.tordarus.net/Tordarus/tui"
|
||||||
|
|
||||||
|
// BorderView is a tui.Wrapper which draws an ASCII border around its view.
|
||||||
|
// Be aware that box drawing characters must share the same tui.Style with the next character.
|
||||||
|
// This can lead to color artifacts when using BorderView with colored styles.
|
||||||
|
type BorderView struct {
|
||||||
|
tui.WrapperTmpl
|
||||||
|
Border BorderBox
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tui.View = &BorderView{}
|
||||||
|
|
||||||
|
func NewBorderView(view tui.View) *BorderView {
|
||||||
|
v := new(BorderView)
|
||||||
|
v.SetView(view)
|
||||||
|
v.Border = ThinBorder()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *BorderView) Draw(buf *tui.ViewBuffer) {
|
||||||
|
g.View().Draw(buf.Sub(1, 1, buf.Width()-2, buf.Height()-2))
|
||||||
|
|
||||||
|
buf.Set(0, 0, tui.Rune{Rn: g.Border.TopLeft, Style: g.Style()})
|
||||||
|
buf.Set(buf.Width()-1, 0, tui.Rune{Rn: g.Border.TopRight, Style: g.Style()})
|
||||||
|
|
||||||
|
buf.Set(0, buf.Height()-1, tui.Rune{Rn: g.Border.BottomLeft, Style: g.Style()})
|
||||||
|
buf.Set(buf.Width()-1, buf.Height()-1, tui.Rune{Rn: g.Border.BottomRight, Style: g.Style()})
|
||||||
|
|
||||||
|
for x := 1; x < buf.Width()-1; x++ {
|
||||||
|
buf.Set(x, 0, tui.Rune{Rn: g.Border.Horizontal, Style: g.Style()})
|
||||||
|
buf.Set(x, buf.Height()-1, tui.Rune{Rn: g.Border.Horizontal, Style: g.Style()})
|
||||||
|
}
|
||||||
|
|
||||||
|
for y := 1; y < buf.Height()-1; y++ {
|
||||||
|
buf.Set(0, y, tui.Rune{Rn: g.Border.Vertical, Style: g.Style()})
|
||||||
|
buf.Set(buf.Width()-1, y, tui.Rune{Rn: g.Border.Vertical, Style: g.Style()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BorderView) Layout() (prefWidth, prefHeight int) {
|
||||||
|
w, h := v.View().Layout()
|
||||||
|
w = iff(w > 0, w+2, w)
|
||||||
|
h = iff(h > 0, h+2, h)
|
||||||
|
return w, h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BorderView) Style() tui.Style {
|
||||||
|
return v.ViewTmpl.Style()
|
||||||
|
}
|
||||||
|
|
||||||
|
type BorderBox struct {
|
||||||
|
TopLeft rune
|
||||||
|
TopRight rune
|
||||||
|
BottomLeft rune
|
||||||
|
BottomRight rune
|
||||||
|
Horizontal rune
|
||||||
|
Vertical rune
|
||||||
|
}
|
||||||
|
|
||||||
|
func ThickBorder() BorderBox {
|
||||||
|
return BorderBox{
|
||||||
|
TopLeft: '┏',
|
||||||
|
TopRight: '┓',
|
||||||
|
BottomLeft: '┗',
|
||||||
|
BottomRight: '┛',
|
||||||
|
Horizontal: '━',
|
||||||
|
Vertical: '┃',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ThinBorder() BorderBox {
|
||||||
|
return BorderBox{
|
||||||
|
TopLeft: '┌',
|
||||||
|
TopRight: '┐',
|
||||||
|
BottomLeft: '└',
|
||||||
|
BottomRight: '┘',
|
||||||
|
Horizontal: '─',
|
||||||
|
Vertical: '│',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoubleBorder() BorderBox {
|
||||||
|
return BorderBox{
|
||||||
|
TopLeft: '╔',
|
||||||
|
TopRight: '╗',
|
||||||
|
BottomLeft: '╚',
|
||||||
|
BottomRight: '╝',
|
||||||
|
Horizontal: '═',
|
||||||
|
Vertical: '║',
|
||||||
|
}
|
||||||
|
}
|
29
views/constrainview.go
Normal file
29
views/constrainview.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.tordarus.net/Tordarus/tui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConstrainView is a tui.Wrapper which constrains the dimensions of its View
|
||||||
|
type ConstrainView struct {
|
||||||
|
tui.WrapperTmpl
|
||||||
|
MaxWidth int
|
||||||
|
MaxHeight int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tui.View = &ConstrainView{}
|
||||||
|
|
||||||
|
func NewConstrainView(view tui.View) *ConstrainView {
|
||||||
|
v := new(ConstrainView)
|
||||||
|
v.SetView(view)
|
||||||
|
v.Constrain(-1, -1)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ConstrainView) Constrain(maxWidth, maxHeight int) {
|
||||||
|
v.MaxWidth, v.MaxHeight = maxWidth, maxHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ConstrainView) Layout() (prefWidth, prefHeight int) {
|
||||||
|
return v.MaxWidth, v.MaxHeight
|
||||||
|
}
|
49
views/coordgroup.go
Normal file
49
views/coordgroup.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import "git.tordarus.net/Tordarus/tui"
|
||||||
|
|
||||||
|
// CoordGroup is a tui.Group which places its children on predefined coordinates
|
||||||
|
type CoordGroup struct {
|
||||||
|
tui.ViewTmpl
|
||||||
|
views map[tui.View]tui.Dimension
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tui.Group = &CoordGroup{}
|
||||||
|
|
||||||
|
func NewCoordGroup() *CoordGroup {
|
||||||
|
return &CoordGroup{
|
||||||
|
views: map[tui.View]tui.Dimension{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CoordGroup) Views() []tui.View {
|
||||||
|
s := make([]tui.View, 0, len(g.views))
|
||||||
|
for v := range g.views {
|
||||||
|
s = append(s, v)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetView places v at the given coordinates with the given dimensions.
|
||||||
|
// v will be added to g's children if not already added before
|
||||||
|
func (g *CoordGroup) SetView(v tui.View, x, y, width, height int) {
|
||||||
|
g.views[v] = tui.Dimension{Point: tui.Point{X: x, Y: y}, Size: tui.Size{Width: width, Height: height}}
|
||||||
|
}
|
||||||
|
func (g *CoordGroup) Draw(buf *tui.ViewBuffer) {
|
||||||
|
for v, d := range g.views {
|
||||||
|
v.Draw(buf.Sub(d.X, d.Y, d.Width, d.Height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *CoordGroup) Layout() (prefWidth, prefHeight int) {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CoordGroup) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
||||||
|
for _, view := range g.Views() {
|
||||||
|
if view.OnKeyPressed(event) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
package views
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.tordarus.net/Tordarus/tui"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventView struct {
|
|
||||||
tui.WrapperTmpl
|
|
||||||
|
|
||||||
View tui.View
|
|
||||||
KeyPressed func(event *tui.KeyEvent) (consumed bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEventView(view tui.View) *EventView {
|
|
||||||
return &EventView{View: view}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *EventView) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
|
||||||
if v.KeyPressed != nil {
|
|
||||||
return v.KeyPressed(event)
|
|
||||||
}
|
|
||||||
return v.ViewTmpl.OnKeyPressed(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *EventView) Draw(buf *tui.ViewBuffer) {
|
|
||||||
v.View.Draw(buf)
|
|
||||||
}
|
|
114
views/flowgroup.go
Normal file
114
views/flowgroup.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.tordarus.net/Tordarus/tui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlowGroup ia a tui.Group which places its children in a linear layout
|
||||||
|
type FlowGroup struct {
|
||||||
|
tui.ViewTmpl
|
||||||
|
views []tui.View
|
||||||
|
lastLayoutPhase *LayoutResult
|
||||||
|
|
||||||
|
// Orientation defines in which direction the children will be placed
|
||||||
|
Orientation tui.Orientation
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tui.Group = &FlowGroup{}
|
||||||
|
|
||||||
|
func NewFlowGroup(orientation tui.Orientation) *FlowGroup {
|
||||||
|
return &FlowGroup{
|
||||||
|
views: make([]tui.View, 0),
|
||||||
|
Orientation: orientation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FlowGroup) Views() []tui.View {
|
||||||
|
return g.views[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FlowGroup) AppendViews(v ...tui.View) {
|
||||||
|
g.views = append(g.views, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FlowGroup) PrependViews(v ...tui.View) {
|
||||||
|
g.views = append(v, g.views...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FlowGroup) InsertView(v tui.View, index int) {
|
||||||
|
g.views = append(g.views[:index], append([]tui.View{v}, g.views[index:]...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FlowGroup) Draw(buf *tui.ViewBuffer) {
|
||||||
|
g.ViewTmpl.Draw(buf)
|
||||||
|
|
||||||
|
if g.lastLayoutPhase == nil {
|
||||||
|
g.Layout()
|
||||||
|
}
|
||||||
|
layout := g.lastLayoutPhase
|
||||||
|
|
||||||
|
if g.Orientation == tui.Horizontal {
|
||||||
|
remainingSpacePerView := buf.Width() - layout.Sum.Width
|
||||||
|
if layout.HorizontalNegativeCount > 0 {
|
||||||
|
remainingSpacePerView /= layout.HorizontalNegativeCount
|
||||||
|
}
|
||||||
|
|
||||||
|
x := 0
|
||||||
|
for _, view := range g.views {
|
||||||
|
size := layout.Sizes[view]
|
||||||
|
|
||||||
|
size.Height = iff(size.Height < 0, buf.Height(), size.Height)
|
||||||
|
if size.Width < 0 {
|
||||||
|
size.Width = iff(layout.Sum.Width > buf.Width(), 0, remainingSpacePerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.Draw(buf.Sub(x, 0, size.Width, size.Height))
|
||||||
|
x += size.Width
|
||||||
|
}
|
||||||
|
} else if g.Orientation == tui.Vertical {
|
||||||
|
remainingSpacePerView := buf.Height() - layout.Sum.Height
|
||||||
|
if layout.VerticalNegativeCount > 0 {
|
||||||
|
remainingSpacePerView /= layout.VerticalNegativeCount
|
||||||
|
}
|
||||||
|
|
||||||
|
y := 0
|
||||||
|
for _, view := range g.views {
|
||||||
|
size := layout.Sizes[view]
|
||||||
|
|
||||||
|
size.Width = iff(size.Width < 0, buf.Width(), size.Width)
|
||||||
|
if size.Height < 0 {
|
||||||
|
size.Height = iff(layout.Sum.Height > buf.Height(), 0, remainingSpacePerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.Draw(buf.Sub(0, y, size.Width, size.Height))
|
||||||
|
y += size.Height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.lastLayoutPhase = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FlowGroup) Layout() (prefWidth, prefHeight int) {
|
||||||
|
layout := CalculateLayoutResult(g.Views())
|
||||||
|
g.lastLayoutPhase = layout
|
||||||
|
|
||||||
|
if g.Orientation == tui.Horizontal {
|
||||||
|
prefWidth = iff(layout.HorizontalNegativeCount == 0, layout.Sum.Width, -1)
|
||||||
|
prefHeight = iff(layout.VerticalNegativeCount == 0, layout.Max.Height, -1)
|
||||||
|
} else if g.Orientation == tui.Vertical {
|
||||||
|
prefWidth = iff(layout.HorizontalNegativeCount == 0, layout.Max.Width, -1)
|
||||||
|
prefHeight = iff(layout.VerticalNegativeCount == 0, layout.Sum.Width, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.Pref = tui.Size{Width: prefWidth, Height: prefHeight}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FlowGroup) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
||||||
|
for _, view := range g.Views() {
|
||||||
|
if view.OnKeyPressed(event) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
20
views/growview.go
Normal file
20
views/growview.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.tordarus.net/Tordarus/tui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GrowView is a tui.View which always demands all available space
|
||||||
|
type GrowView struct {
|
||||||
|
tui.ViewTmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tui.View = &GrowView{}
|
||||||
|
|
||||||
|
func NewGrowView() *GrowView {
|
||||||
|
return &GrowView{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *GrowView) Layout() (prefWidth, prefHeight int) {
|
||||||
|
return -1, -1
|
||||||
|
}
|
48
views/marginview.go
Normal file
48
views/marginview.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import "git.tordarus.net/Tordarus/tui"
|
||||||
|
|
||||||
|
// MarginView is a tui.Wrapper which applies margin around its view
|
||||||
|
type MarginView struct {
|
||||||
|
tui.WrapperTmpl
|
||||||
|
Margin map[tui.Side]int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tui.View = &MarginView{}
|
||||||
|
|
||||||
|
func NewMarginView(view tui.View) *MarginView {
|
||||||
|
v := new(MarginView)
|
||||||
|
v.SetView(view)
|
||||||
|
v.SetMargin(0, 0, 0, 0)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *MarginView) Draw(buf *tui.ViewBuffer) {
|
||||||
|
x := g.Margin[tui.Left]
|
||||||
|
y := g.Margin[tui.Top]
|
||||||
|
w := buf.Width() - x - g.Margin[tui.Right]
|
||||||
|
h := buf.Height() - y - g.Margin[tui.Bottom]
|
||||||
|
|
||||||
|
g.ViewTmpl.Draw(buf)
|
||||||
|
g.View().Draw(buf.Sub(x, y, w, h))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *MarginView) Layout() (prefWidth, prefHeight int) {
|
||||||
|
w, h := v.View().Layout()
|
||||||
|
w = iff(w > 0, w+v.Margin[tui.Left]+v.Margin[tui.Right], w)
|
||||||
|
h = iff(h > 0, h+v.Margin[tui.Top]+v.Margin[tui.Bottom], h)
|
||||||
|
return w, h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *MarginView) Style() tui.Style {
|
||||||
|
return v.ViewTmpl.Style()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *MarginView) SetMargin(top, right, bottom, left int) {
|
||||||
|
v.Margin = map[tui.Side]int{
|
||||||
|
tui.Top: top,
|
||||||
|
tui.Right: right,
|
||||||
|
tui.Bottom: bottom,
|
||||||
|
tui.Left: left,
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"git.tordarus.net/Tordarus/tui"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TextView is a tui.View which prints text
|
||||||
type TextView struct {
|
type TextView struct {
|
||||||
tui.ViewTmpl
|
tui.ViewTmpl
|
||||||
Text string
|
Text string
|
||||||
@ -20,3 +21,7 @@ func NewTextView(text string) *TextView {
|
|||||||
Text: text,
|
Text: text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *TextView) Layout() (prefWidth, prefHeight int) {
|
||||||
|
return tui.MeasureMultiLineString(v.Text)
|
||||||
|
}
|
||||||
|
86
views/utils.go
Normal file
86
views/utils.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"git.tordarus.net/Tordarus/tui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func min(x, y int) int {
|
||||||
|
if x < y {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(x, y int) int {
|
||||||
|
if x > y {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func iff[T any](condition bool, trueValue, falseValue T) T {
|
||||||
|
if condition {
|
||||||
|
return trueValue
|
||||||
|
}
|
||||||
|
return falseValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type LayoutResult struct {
|
||||||
|
Sizes map[tui.View]tui.Size
|
||||||
|
|
||||||
|
Sum tui.Size
|
||||||
|
Min tui.Size
|
||||||
|
Max tui.Size
|
||||||
|
Pref tui.Size
|
||||||
|
|
||||||
|
Count int
|
||||||
|
|
||||||
|
VerticalNegativeCount int
|
||||||
|
HorizontalNegativeCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalculateLayoutResult(views []tui.View) *LayoutResult {
|
||||||
|
result := &LayoutResult{
|
||||||
|
Sizes: map[tui.View]tui.Size{},
|
||||||
|
|
||||||
|
Sum: tui.Size{Width: 0, Height: 0},
|
||||||
|
Min: tui.Size{Width: math.MaxInt, Height: math.MaxInt},
|
||||||
|
Max: tui.Size{Width: -1, Height: -1},
|
||||||
|
|
||||||
|
Count: 0,
|
||||||
|
|
||||||
|
VerticalNegativeCount: 0,
|
||||||
|
HorizontalNegativeCount: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, view := range views {
|
||||||
|
if view == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Count++
|
||||||
|
|
||||||
|
width, height := view.Layout()
|
||||||
|
result.Sizes[view] = tui.Size{Width: width, Height: height}
|
||||||
|
|
||||||
|
if width > 0 {
|
||||||
|
result.Sum.Width += width
|
||||||
|
result.Min.Width = min(result.Min.Width, width)
|
||||||
|
result.Max.Width = max(result.Max.Width, width)
|
||||||
|
} else if width < 0 {
|
||||||
|
result.HorizontalNegativeCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if height > 0 {
|
||||||
|
result.Sum.Height += height
|
||||||
|
result.Min.Height = min(result.Min.Height, height)
|
||||||
|
result.Max.Height = max(result.Max.Height, height)
|
||||||
|
} else if height < 0 {
|
||||||
|
result.VerticalNegativeCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
40
viewtmpl.go
40
viewtmpl.go
@ -1,44 +1,30 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import "github.com/gdamore/tcell"
|
|
||||||
|
|
||||||
type ViewTmpl struct {
|
type ViewTmpl struct {
|
||||||
foreground *Color
|
style *Style
|
||||||
background *Color
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ View = &ViewTmpl{}
|
var _ View = &ViewTmpl{}
|
||||||
|
|
||||||
func (v *ViewTmpl) Draw(buf *ViewBuffer) {
|
func (v *ViewTmpl) Draw(buf *ViewBuffer) {
|
||||||
buf.Fill(DefaultRune)
|
buf.Fill(Rune{' ', v.Style()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ViewTmpl) Layout() (prefWidth, prefHeight int) {
|
||||||
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ViewTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
|
func (v *ViewTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *ViewTmpl) SetStyle(s Style) {
|
||||||
|
v.style = &s
|
||||||
|
}
|
||||||
|
|
||||||
func (v *ViewTmpl) Style() Style {
|
func (v *ViewTmpl) Style() Style {
|
||||||
return StyleDefault.Background(v.Background()).Foreground(v.Foreground())
|
if v.style == nil {
|
||||||
}
|
return StyleDefault
|
||||||
|
|
||||||
func (v *ViewTmpl) Foreground() Color {
|
|
||||||
if v.foreground == nil {
|
|
||||||
return tcell.ColorDefault
|
|
||||||
}
|
}
|
||||||
return *v.foreground
|
return *v.style
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ViewTmpl) SetForeground(color Color) {
|
|
||||||
v.foreground = &color
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ViewTmpl) Background() Color {
|
|
||||||
if v.background == nil {
|
|
||||||
return tcell.ColorDefault
|
|
||||||
}
|
|
||||||
return *v.background
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ViewTmpl) SetBackground(color Color) {
|
|
||||||
v.background = &color
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import "github.com/gdamore/tcell"
|
|
||||||
|
|
||||||
type WrapperTmpl struct {
|
type WrapperTmpl struct {
|
||||||
ViewTmpl
|
ViewTmpl
|
||||||
view View
|
view View
|
||||||
@ -17,6 +15,13 @@ func (v *WrapperTmpl) Draw(buf *ViewBuffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *WrapperTmpl) Layout() (prefWidth, prefHeight int) {
|
||||||
|
if v.view != nil {
|
||||||
|
return v.view.Layout()
|
||||||
|
}
|
||||||
|
return v.ViewTmpl.Layout()
|
||||||
|
}
|
||||||
|
|
||||||
func (v *WrapperTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
|
func (v *WrapperTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
|
||||||
if v.view != nil {
|
if v.view != nil {
|
||||||
return v.view.OnKeyPressed(event)
|
return v.view.OnKeyPressed(event)
|
||||||
@ -24,6 +29,14 @@ func (v *WrapperTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
|
|||||||
return v.ViewTmpl.OnKeyPressed(event)
|
return v.ViewTmpl.OnKeyPressed(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *WrapperTmpl) SetStyle(s Style) {
|
||||||
|
if v.view != nil {
|
||||||
|
v.view.SetStyle(s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.ViewTmpl.SetStyle(s)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *WrapperTmpl) Style() Style {
|
func (v *WrapperTmpl) Style() Style {
|
||||||
if v.view != nil {
|
if v.view != nil {
|
||||||
return v.view.Style()
|
return v.view.Style()
|
||||||
@ -31,37 +44,7 @@ func (v *WrapperTmpl) Style() Style {
|
|||||||
return v.ViewTmpl.Style()
|
return v.ViewTmpl.Style()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *WrapperTmpl) Foreground() Color {
|
func (v *WrapperTmpl) Views() []View {
|
||||||
if v.view != nil {
|
|
||||||
return v.view.Foreground()
|
|
||||||
}
|
|
||||||
return v.ViewTmpl.Foreground()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *WrapperTmpl) SetForeground(color Color) {
|
|
||||||
if v.view != nil {
|
|
||||||
v.view.SetForeground(color)
|
|
||||||
} else {
|
|
||||||
v.ViewTmpl.SetForeground(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *WrapperTmpl) Background() Color {
|
|
||||||
if v.background == nil {
|
|
||||||
return tcell.ColorDefault
|
|
||||||
}
|
|
||||||
return *v.background
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *WrapperTmpl) SetBackground(color Color) {
|
|
||||||
if v.view != nil {
|
|
||||||
v.view.SetBackground(color)
|
|
||||||
} else {
|
|
||||||
v.ViewTmpl.SetBackground(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *WrapperTmpl) Children() []View {
|
|
||||||
return []View{v.view}
|
return []View{v.view}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user