migrated to git.milar.in

This commit is contained in:
milarin 2023-04-24 11:41:38 +02:00
parent 67b65231fa
commit 2dbd0cc15e
19 changed files with 233 additions and 92 deletions

3
go.mod
View File

@ -4,7 +4,8 @@ go 1.18
require ( require (
git.tordarus.net/Tordarus/adverr v0.2.0 git.tordarus.net/Tordarus/adverr v0.2.0
git.tordarus.net/Tordarus/buf2d v1.1.4 git.tordarus.net/Tordarus/buf2d v1.1.6
git.tordarus.net/Tordarus/dstruct v0.0.3
github.com/gdamore/tcell v1.4.0 github.com/gdamore/tcell v1.4.0
github.com/mattn/go-runewidth v0.0.7 github.com/mattn/go-runewidth v0.0.7
) )

6
go.sum
View File

@ -1,7 +1,9 @@
git.tordarus.net/Tordarus/adverr v0.2.0 h1:kLYjR2/Vb2GHiSAMvAv+WPNaHR9BRphKanf8H/pCZdA= git.tordarus.net/Tordarus/adverr v0.2.0 h1:kLYjR2/Vb2GHiSAMvAv+WPNaHR9BRphKanf8H/pCZdA=
git.tordarus.net/Tordarus/adverr v0.2.0/go.mod h1:XRf0+7nhOkIEr0gi9DUG4RvV2KaOFB0fYPDaR1KLenw= git.tordarus.net/Tordarus/adverr v0.2.0/go.mod h1:XRf0+7nhOkIEr0gi9DUG4RvV2KaOFB0fYPDaR1KLenw=
git.tordarus.net/Tordarus/buf2d v1.1.4 h1:5R33Bq/no3uQIk7Tql6z55LI635DHeXQ7STNz0R7FQQ= git.tordarus.net/Tordarus/buf2d v1.1.6 h1:qBLHSRj0eDxaG/IOUncJpWr1xwEKD4SCZReLo5r5eJw=
git.tordarus.net/Tordarus/buf2d v1.1.4/go.mod h1:XXPpS8nQK0gUI0ki7ftV/qlprsGCRWFVSD4ybvDuUL8= git.tordarus.net/Tordarus/buf2d v1.1.6/go.mod h1:XXPpS8nQK0gUI0ki7ftV/qlprsGCRWFVSD4ybvDuUL8=
git.tordarus.net/Tordarus/dstruct v0.0.3 h1:cnUOM2rf96skIXGwBhSyzGMcT65ks8lFrOjpg6k23K8=
git.tordarus.net/Tordarus/dstruct v0.0.3/go.mod h1:RvLL2G4lUCGzwr8KaBGQRi3qlMG0WTgGhcVN6iyZZuw=
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=

View File

@ -1,4 +1,4 @@
package views package layouts
import ( import (
"git.tordarus.net/Tordarus/tui" "git.tordarus.net/Tordarus/tui"

View File

@ -1,4 +1,4 @@
package views package layouts
import "git.tordarus.net/Tordarus/tui" import "git.tordarus.net/Tordarus/tui"

View File

@ -1,4 +1,4 @@
package views package layouts
import ( import (
"git.tordarus.net/Tordarus/tui" "git.tordarus.net/Tordarus/tui"

3
layouts/layout_grid.go Normal file
View File

@ -0,0 +1,3 @@
package layouts
// TODO

View File

@ -1,4 +1,4 @@
package views package layouts
import ( import (
"git.tordarus.net/Tordarus/tui" "git.tordarus.net/Tordarus/tui"

90
layouts/utils.go Normal file
View File

@ -0,0 +1,90 @@
package layouts
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 limit(v, minv, maxv int) int {
return min(max(v, minv), maxv)
}
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
}

31
modals/modal_info.go Normal file
View File

@ -0,0 +1,31 @@
package modals
import (
"git.tordarus.net/Tordarus/tui"
"git.tordarus.net/Tordarus/tui/views"
"github.com/gdamore/tcell"
)
type InfoModal struct {
*views.FrameView
}
func NewInfoModal(message string) *InfoModal {
tv := views.NewTextView(message)
bv := views.NewBorderView(views.NewMarginView(tv, 0, 1, 0, 1))
fv := views.NewFrameView(bv)
fv.DontClearBuffer = true
m := &InfoModal{
FrameView: fv,
}
m.KeyPressed = func(event *tui.KeyEvent) (consumed bool) {
if event.Key() == tcell.KeyEnter || event.Key() == tcell.KeyESC {
}
return true
}
return m
}

View File

@ -5,6 +5,7 @@ import (
"git.tordarus.net/Tordarus/adverr" "git.tordarus.net/Tordarus/adverr"
"git.tordarus.net/Tordarus/buf2d" "git.tordarus.net/Tordarus/buf2d"
"git.tordarus.net/Tordarus/dstruct"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
) )
@ -16,6 +17,9 @@ type Screen struct {
stopCh chan error stopCh chan error
redrawCh chan struct{} redrawCh chan struct{}
started bool
modals *dstruct.Stack[View]
// Root is the root view which is currently shown on screen // Root is the root view which is currently shown on screen
Root View Root View
@ -32,6 +36,7 @@ func NewScreen(root View) (*Screen, error) {
scr: scr, scr: scr,
stopCh: make(chan error, 1), stopCh: make(chan error, 1),
redrawCh: make(chan struct{}, 1), redrawCh: make(chan struct{}, 1),
modals: dstruct.NewStack[View](),
} }
s.KeyPressed = CloseOnCtrlC(s) s.KeyPressed = CloseOnCtrlC(s)
@ -49,9 +54,10 @@ func (s *Screen) Start() error {
} }
defer s.scr.Fini() defer s.scr.Fini()
//defer close(s.redrawCh) defer close(s.redrawCh)
s.scr.EnableMouse() s.scr.EnableMouse()
s.started = true
return <-s.stopCh return <-s.stopCh
} }
@ -64,6 +70,11 @@ func (s *Screen) StopWithError(err error) {
} }
func (s *Screen) onKeyPressed(event *KeyEvent) { func (s *Screen) onKeyPressed(event *KeyEvent) {
if !s.modals.Empty() {
s.modals.Peek().OnKeyPressed(event)
return
}
if s.KeyPressed == nil || !s.KeyPressed(event) { if s.KeyPressed == nil || !s.KeyPressed(event) {
s.Root.OnKeyPressed(event) s.Root.OnKeyPressed(event)
} }
@ -71,12 +82,20 @@ func (s *Screen) onKeyPressed(event *KeyEvent) {
} }
func (s *Screen) onMouseEvent(event *MouseEvent) { func (s *Screen) onMouseEvent(event *MouseEvent) {
if s.MouseEvent == nil || !s.MouseEvent(event) {
s.Root.OnMouseEvent(event)
}
if event.Button != MouseButtonNone { if event.Button != MouseButtonNone {
s.Redraw() defer s.Redraw()
} }
if !s.modals.Empty() {
s.modals.Peek().OnMouseEvent(event)
return
}
if s.MouseEvent != nil && s.MouseEvent(event) {
return
}
s.Root.OnMouseEvent(event)
} }
func convertMouseEvent(original *tcell.EventMouse) *MouseEvent { func convertMouseEvent(original *tcell.EventMouse) *MouseEvent {
@ -89,8 +108,10 @@ func convertMouseEvent(original *tcell.EventMouse) *MouseEvent {
} }
func (s *Screen) Redraw() { func (s *Screen) Redraw() {
if s.started {
s.redrawCh <- struct{}{} s.redrawCh <- struct{}{}
} }
}
func (s *Screen) eventloop() { func (s *Screen) eventloop() {
defer s.handlePanic("panicked while handling event") defer s.handlePanic("panicked while handling event")
@ -128,8 +149,18 @@ func (s *Screen) drawloop() {
s.buf.Fill(DefaultRune) s.buf.Fill(DefaultRune)
} }
// draw root view
rw, rh := s.Root.Layout() rw, rh := s.Root.Layout()
s.Root.Draw(truncateBuffer(s.buf, rw, rh)) s.Root.Draw(truncateBuffer(s.buf, rw, rh))
// draw modals
for i := 0; i < s.modals.Size(); i++ {
v := s.modals.PeekAt(i)
mw, mh := v.Layout()
v.Draw(s.buf.Sub(0, 0, iff(mw >= 0, mw, s.buf.Width()), iff(mh >= 0, mh, s.buf.Height())))
}
// draw buffer onto screen
drawBuffer(s.scr, s.buf) drawBuffer(s.scr, s.buf)
} }
} }
@ -143,3 +174,13 @@ func (s *Screen) handlePanic(msg string) {
} }
} }
} }
func (s *Screen) OpenModal(v View) {
s.modals.Push(v)
s.Redraw()
}
func (s *Screen) CloseModal() {
s.modals.Pop()
s.Redraw()
}

View File

@ -10,6 +10,8 @@ import (
"testing" "testing"
"git.tordarus.net/Tordarus/tui" "git.tordarus.net/Tordarus/tui"
"git.tordarus.net/Tordarus/tui/layouts"
"git.tordarus.net/Tordarus/tui/modals"
"git.tordarus.net/Tordarus/tui/views" "git.tordarus.net/Tordarus/tui/views"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
) )
@ -38,7 +40,7 @@ func TestScrollView(t *testing.T) {
textViews[i].SetStyle(textViews[i].Style().Foreground(tcell.ColorBlack).Background(tcell.Color(rand.Intn(int(tcell.ColorYellowGreen))))) textViews[i].SetStyle(textViews[i].Style().Foreground(tcell.ColorBlack).Background(tcell.Color(rand.Intn(int(tcell.ColorYellowGreen)))))
} }
flowLayout := views.NewFlowLayout(tui.Vertical) flowLayout := layouts.NewFlowLayout(tui.Vertical)
flowLayout.AppendViews(textViews...) flowLayout.AppendViews(textViews...)
scrollView := views.NewScrollView(flowLayout) scrollView := views.NewScrollView(flowLayout)
@ -128,6 +130,9 @@ func TestMousePosition(t *testing.T) {
return true return true
} }
modal := modals.NewInfoModal("Programm wird geschlossen")
screen.OpenModal(modal)
if err := screen.Start(); err != nil { if err := screen.Start(); err != nil {
fmt.Println(err) fmt.Println(err)
} }
@ -153,7 +158,7 @@ func TestFlowLayout(t *testing.T) {
growView2 := views.NewGrowView(nil) growView2 := views.NewGrowView(nil)
growView2.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow)) growView2.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow))
flowLayout := views.NewFlowLayout(tui.Vertical) flowLayout := layouts.NewFlowLayout(tui.Vertical)
flowLayout.AppendViews(textView, growView, textView2) flowLayout.AppendViews(textView, growView, textView2)
screen, err := tui.NewScreen(flowLayout) screen, err := tui.NewScreen(flowLayout)
@ -181,7 +186,7 @@ func TestSeparatorLayout(t *testing.T) {
growView2 := views.NewGrowView(nil) growView2 := views.NewGrowView(nil)
growView2.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow)) growView2.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow))
separatorLayout := views.NewSeparatorLayout(tui.Vertical) separatorLayout := layouts.NewSeparatorLayout(tui.Vertical)
separatorLayout.AppendView(frameView, 1) separatorLayout.AppendView(frameView, 1)
separatorLayout.AppendView(growView, 1) separatorLayout.AppendView(growView, 1)
separatorLayout.AppendView(textView2, 1) separatorLayout.AppendView(textView2, 1)
@ -260,13 +265,13 @@ func TestBorderLayout(t *testing.T) {
return false return false
} }
borderLayout := views.NewBorderLayout() borderLayout := layouts.NewBorderLayout()
borderLayout.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple)) borderLayout.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
borderLayout.SetView(topView, views.Top) borderLayout.SetView(topView, layouts.Top)
borderLayout.SetView(bottomView, views.Bottom) borderLayout.SetView(bottomView, layouts.Bottom)
borderLayout.SetView(leftView, views.Left) borderLayout.SetView(leftView, layouts.Left)
borderLayout.SetView(rightView, views.Right) borderLayout.SetView(rightView, layouts.Right)
borderLayout.SetView(centerView, views.Center) borderLayout.SetView(centerView, layouts.Center)
screen, err := tui.NewScreen(borderLayout) screen, err := tui.NewScreen(borderLayout)
if err != nil { if err != nil {

21
tmpl_modal.go Normal file
View File

@ -0,0 +1,21 @@
package tui
type ModalTmpl[T any] struct {
WrapperTmpl
resultCh chan T
screen *Screen
}
var _ Modal[int] = &ModalTmpl[int]{}
func (m *ModalTmpl[T]) Open(s *Screen) <-chan T {
m.resultCh = make(chan T)
m.screen = s
return m.resultCh
}
func (m *ModalTmpl[T]) Close(result T) {
m.resultCh <- result
m.screen.CloseModal() // TODO which modal
}

View File

@ -127,8 +127,8 @@ func CloseOnKeyPressed(screen *Screen, key tcell.Key) func(event *KeyEvent) (con
return func(event *KeyEvent) (consumed bool) { return func(event *KeyEvent) (consumed bool) {
if event.Key() == key { if event.Key() == key {
screen.Stop() screen.Stop()
return false
}
return true return true
} }
return false
}
} }

10
view.go
View File

@ -38,3 +38,13 @@ type Wrapper interface {
SetView(View) SetView(View)
View() View View() View
} }
// Modal defines the behavior of a Wrapper which captures
// all screen space and all events when opened and can return results.
// It can be used to make dialogs, alert boxes and context menus
type Modal[T any] interface {
Wrapper
Open(s *Screen) <-chan T
Close(result T)
}

View File

@ -1,3 +0,0 @@
package views
// TODO

View File

@ -1,11 +1,5 @@
package views package views
import (
"math"
"git.tordarus.net/Tordarus/tui"
)
func min(x, y int) int { func min(x, y int) int {
if x < y { if x < y {
return x return x
@ -30,61 +24,3 @@ func iff[T any](condition bool, trueValue, falseValue T) T {
} }
return falseValue 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
}

View File

@ -6,6 +6,8 @@ import "git.tordarus.net/Tordarus/tui"
type FrameView struct { type FrameView struct {
tui.WrapperTmpl tui.WrapperTmpl
Anchor tui.Anchor Anchor tui.Anchor
DontClearBuffer bool
} }
var _ tui.Wrapper = &FrameView{} var _ tui.Wrapper = &FrameView{}
@ -18,7 +20,9 @@ func NewFrameView(view tui.View) *FrameView {
} }
func (g *FrameView) Draw(buf *tui.ViewBuffer) { func (g *FrameView) Draw(buf *tui.ViewBuffer) {
if !g.DontClearBuffer {
g.ViewTmpl.Draw(buf) g.ViewTmpl.Draw(buf)
}
w, h := g.View().Layout() w, h := g.View().Layout()
w = iff(w >= 0, w, buf.Width()) w = iff(w >= 0, w, buf.Width())