huge refactor

This commit is contained in:
Timon Ringwald 2022-04-01 20:10:51 +02:00
parent 322b954149
commit d335211770
15 changed files with 306 additions and 72 deletions

View File

@ -1,13 +1,12 @@
package tui package tui
import ( import (
"git.tordarus.net/tordarus/buf2d"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
) )
func drawBuffer(scr tcell.Screen, buf *buf2d.Buffer) { func drawBuffer(scr tcell.Screen, buf *ViewBuffer) {
buf.Draw(func(x, y int, cn rune) { buf.ForEach(func(x, y int, rn Rune) {
scr.SetContent(x, y, cn, nil, tcell.StyleDefault) scr.SetContent(x, y, rn.Rn, nil, rn.Style)
}) })
scr.Show() scr.Show()
} }

View File

@ -1,7 +1,5 @@
package tui package tui
import "github.com/gdamore/tcell"
type Events interface { type Events interface {
OnKeyPressed(key tcell.Key) OnKeyPressed(event *KeyEvent) (consumed bool)
} }

16
go.mod Normal file
View File

@ -0,0 +1,16 @@
module git.tordarus.net/Tordarus/tui
go 1.18
require (
git.tordarus.net/Tordarus/buf2d v1.1.0
github.com/gdamore/tcell v1.4.0
)
require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/mattn/go-runewidth v0.0.7 // indirect
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect
golang.org/x/text v0.3.0 // indirect
)

14
go.sum Normal file
View File

@ -0,0 +1,14 @@
git.tordarus.net/Tordarus/buf2d v1.1.0 h1:rIZjD7yeX5XK2D1h75ET5Og0u/NQF3eVonnC5aaqVkQ=
git.tordarus.net/Tordarus/buf2d v1.1.0/go.mod h1:XXPpS8nQK0gUI0ki7ftV/qlprsGCRWFVSD4ybvDuUL8=
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/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
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/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=

60
rune.go Normal file
View File

@ -0,0 +1,60 @@
package tui
import "regexp"
type Rune struct {
Rn rune
Style Style
}
var DefaultRune = Rune{' ', StyleDefault}
// Text represents a string with rune-based styling
type Text struct {
str string
style []Style
}
// Txt returns a Text containing the given str
// using the default style
func Txt(str string) *Text {
styles := make([]Style, 0, len(str))
for range str {
styles = append(styles, StyleDefault)
}
return &Text{str: str, style: styles}
}
// Len returns the amount of runes in t
func (t *Text) Len() int {
return len(t.style)
}
// AppendString appends str with default styling to t
func (t *Text) AppendString(str string) {
t.AppendText(Txt(str))
}
// PrependString prepends str with default styling to t
func (t *Text) PrependString(str string) {
newTxt := Txt(str)
newTxt.AppendText(t)
*t = *newTxt
}
// AppendText appends txt to t
func (t *Text) AppendText(txt *Text) {
t.str += txt.str
t.style = append(t.style, txt.style...)
}
// Style applies the given style to the part of t between startIndex (inclusive) and endIndex (exclusive)
func (t *Text) Style(style Style, startIndex, endIndex int) {
for i := startIndex; i < endIndex; i++ {
t.style[i] = style
}
}
func (t *Text) StyleRegex(style Style, pattern *regexp.Regexp) {
// TODO
}

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"git.tordarus.net/tordarus/buf2d" "git.tordarus.net/Tordarus/buf2d"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
) )
@ -38,7 +38,7 @@ func (s *Screen) eventloop() {
go s.Redraw() go s.Redraw()
case *tcell.EventKey: case *tcell.EventKey:
go func() { go func() {
s.Root.OnKeyPressed(event.Key()) s.Root.OnKeyPressed(event)
s.Redraw() s.Redraw()
}() }()
default: default:
@ -53,6 +53,8 @@ func (s *Screen) Start() error {
if err != nil { if err != nil {
return err return err
} }
defer s.scr.Fini()
s.Redraw() s.Redraw()
return <-s.stopCh return <-s.stopCh
} }
@ -67,7 +69,7 @@ func (s *Screen) StopWithError(err error) {
func (s *Screen) Redraw() { func (s *Screen) Redraw() {
w, h := s.scr.Size() w, h := s.scr.Size()
buf := buf2d.NewBuffer(w, h) buf := buf2d.NewBuffer(w, h, DefaultRune)
s.Root.Draw(buf) s.Root.Draw(buf)
drawBuffer(s.scr, buf) drawBuffer(s.scr, buf)
} }

View File

@ -4,22 +4,28 @@ import (
"errors" "errors"
"fmt" "fmt"
"testing" "testing"
"tui"
"tui/views"
"git.tordarus.net/Tordarus/tui"
"git.tordarus.net/Tordarus/tui/views"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
) )
func TestScreen(t *testing.T) { func TestScreen(t *testing.T) {
eventView := views.NewEventView() textView := views.NewTextView("hello world")
eventView := views.NewEventView(textView)
screen, err := tui.NewScreen(eventView) screen, err := tui.NewScreen(eventView)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
eventView.KeyPressed = func(key tcell.Key) { eventView.KeyPressed = func(event *tui.KeyEvent) (consumed bool) {
screen.StopWithError(errors.New(fmt.Sprintf("%#v", key))) if event.Key() == tcell.KeyCtrlC {
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() err = screen.Start()

13
types.go Normal file
View File

@ -0,0 +1,13 @@
package tui
import (
"git.tordarus.net/Tordarus/buf2d"
"github.com/gdamore/tcell"
)
type ViewBuffer = buf2d.Buffer[Rune]
type KeyEvent = tcell.EventKey
type Style = tcell.Style
type Color = tcell.Color
var StyleDefault Style = tcell.StyleDefault

31
utils.go Normal file
View File

@ -0,0 +1,31 @@
package tui
import (
"strings"
)
// 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
func WriteString(b *ViewBuffer, str string, style Style, x, y int) {
dx := x
for _, r := range str {
if dx >= b.Width() {
return
}
b.Set(dx, y, Rune{r, style})
dx++
}
}
// 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
// All lines which do not fit vertically will be truncated as well
func WriteMultiLineString(b *ViewBuffer, str string, style Style, x, y int) {
lines := strings.Split(str, "\n")
for dy, line := range lines {
if dy >= b.Height() {
return
}
WriteString(b, line, style, x, y+dy)
}
}

42
view.go
View File

@ -1,18 +1,40 @@
package tui package tui
import ( // View defines the behavior of any element displayable on screen
"git.tordarus.net/tordarus/buf2d" // To define custom Views, it is recommended to add ViewTmpl
"github.com/gdamore/tcell" // as the promoted anonymous field for your custom View struct.
) // It implements the View interface with useful default behavior
type View interface { type View interface {
Events Events
SetForeground(color tcell.Color) SetForeground(color Color)
Foreground() tcell.Color Foreground() Color
SetBackground(color tcell.Color) SetBackground(color Color)
Background() tcell.Color Background() Color
Draw(*buf2d.Buffer) Style() Style
Draw(*ViewBuffer)
}
// 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 {
View
Children() []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
// as the promoted anonymous field for your custom Wrapper struct.
// It implements the Wrapper interface with useful default behavior
type Wrapper interface {
Group
SetView(View)
View() View
} }

View File

@ -1,6 +1,3 @@
package tui package tui
type ViewGroup interface { // TODO GroupTmpl
View
Children() []*View
}

View File

@ -1,22 +1,27 @@
package views package views
import ( import (
"tui" "git.tordarus.net/Tordarus/tui"
"github.com/gdamore/tcell"
) )
type EventView struct { type EventView struct {
tui.ViewTmpl tui.WrapperTmpl
KeyPressed func(key tcell.Key)
View tui.View
KeyPressed func(event *tui.KeyEvent) (consumed bool)
} }
func (v *EventView) OnKeyPressed(key tcell.Key) { func NewEventView(view tui.View) *EventView {
return &EventView{View: view}
}
func (v *EventView) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
if v.KeyPressed != nil { if v.KeyPressed != nil {
v.KeyPressed(key) return v.KeyPressed(event)
} }
return v.ViewTmpl.OnKeyPressed(event)
} }
func NewEventView() *EventView { func (v *EventView) Draw(buf *tui.ViewBuffer) {
return &EventView{} v.View.Draw(buf)
} }

View File

@ -1,9 +1,7 @@
package views package views
import ( import (
"tui" "git.tordarus.net/Tordarus/tui"
"git.tordarus.net/tordarus/buf2d"
) )
type TextView struct { type TextView struct {
@ -13,8 +11,8 @@ type TextView struct {
var _ tui.View = &TextView{} var _ tui.View = &TextView{}
func (v *TextView) Draw(buf *buf2d.Buffer) { func (v *TextView) Draw(buf *tui.ViewBuffer) {
buf.WriteMultiLineString(v.Text, 0, 0) tui.WriteMultiLineString(buf, v.Text, v.Style(), 0, 0)
} }
func NewTextView(text string) *TextView { func NewTextView(text string) *TextView {

View File

@ -1,45 +1,44 @@
package tui package tui
import ( import "github.com/gdamore/tcell"
"git.tordarus.net/tordarus/buf2d"
"github.com/gdamore/tcell"
)
type ViewTmpl struct { type ViewTmpl struct {
view View foreground *Color
background *Color
foreground tcell.Color
background tcell.Color
} }
var _ View = &ViewTmpl{} var _ View = &ViewTmpl{}
func NewViewTmpl(v View) *ViewTmpl { func (v *ViewTmpl) Draw(buf *ViewBuffer) {
return &ViewTmpl{ buf.Fill(DefaultRune)
view: v,
}
} }
func (v *ViewTmpl) Draw(buf *buf2d.Buffer) { func (v *ViewTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
buf.Fill(' ') return false
} }
func (v *ViewTmpl) OnKeyPressed(key tcell.Key) { func (v *ViewTmpl) Style() Style {
return StyleDefault.Background(v.Background()).Foreground(v.Foreground())
} }
func (v *ViewTmpl) Foreground() tcell.Color { func (v *ViewTmpl) Foreground() Color {
return v.foreground if v.foreground == nil {
return tcell.ColorDefault
}
return *v.foreground
} }
func (v *ViewTmpl) SetForeground(color tcell.Color) { func (v *ViewTmpl) SetForeground(color Color) {
v.foreground = color v.foreground = &color
} }
func (v *ViewTmpl) Background() tcell.Color { func (v *ViewTmpl) Background() Color {
return v.background if v.background == nil {
return tcell.ColorDefault
}
return *v.background
} }
func (v *ViewTmpl) SetBackground(color tcell.Color) { func (v *ViewTmpl) SetBackground(color Color) {
v.background = color v.background = &color
} }

74
wrappertmpl.go Normal file
View File

@ -0,0 +1,74 @@
package tui
import "github.com/gdamore/tcell"
type WrapperTmpl struct {
ViewTmpl
view View
}
var _ Wrapper = &WrapperTmpl{}
func (v *WrapperTmpl) Draw(buf *ViewBuffer) {
if v.view != nil {
v.view.Draw(buf)
} else {
v.ViewTmpl.Draw(buf)
}
}
func (v *WrapperTmpl) OnKeyPressed(event *KeyEvent) (consumed bool) {
if v.view != nil {
return v.view.OnKeyPressed(event)
}
return v.ViewTmpl.OnKeyPressed(event)
}
func (v *WrapperTmpl) Style() Style {
if v.view != nil {
return v.view.Style()
}
return v.ViewTmpl.Style()
}
func (v *WrapperTmpl) Foreground() Color {
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}
}
func (v *WrapperTmpl) View() View {
return v.view
}
func (v *WrapperTmpl) SetView(view View) {
v.view = view
}