From 2dbd0cc15e9e9d643c75f538ff16af30f957279e Mon Sep 17 00:00:00 2001 From: milarin Date: Mon, 24 Apr 2023 11:41:38 +0200 Subject: [PATCH] migrated to git.milar.in --- go.mod | 3 +- go.sum | 6 +- {views => layouts}/layout_border.go | 2 +- {views => layouts}/layout_coord.go | 2 +- {views => layouts}/layout_flow.go | 2 +- layouts/layout_grid.go | 3 + {views => layouts}/layout_separator.go | 2 +- layouts/utils.go | 90 ++++++++++++++++++++++++++ modals/modal_info.go | 31 +++++++++ screen.go | 53 +++++++++++++-- tests/screen_test.go | 23 ++++--- tmpl_modal.go | 21 ++++++ viewtmpl.go => tmpl_view.go | 0 wrappertmpl.go => tmpl_wrapper.go | 0 utils.go | 4 +- view.go | 10 +++ views/layout_grid.go | 3 - views/utils.go | 64 ------------------ views/view_frame.go | 6 +- 19 files changed, 233 insertions(+), 92 deletions(-) rename {views => layouts}/layout_border.go (99%) rename {views => layouts}/layout_coord.go (98%) rename {views => layouts}/layout_flow.go (99%) create mode 100644 layouts/layout_grid.go rename {views => layouts}/layout_separator.go (99%) create mode 100644 layouts/utils.go create mode 100644 modals/modal_info.go create mode 100644 tmpl_modal.go rename viewtmpl.go => tmpl_view.go (100%) rename wrappertmpl.go => tmpl_wrapper.go (100%) delete mode 100644 views/layout_grid.go diff --git a/go.mod b/go.mod index 4ecd0b2..a6819a5 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.18 require ( 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/mattn/go-runewidth v0.0.7 ) diff --git a/go.sum b/go.sum index 59a3769..fb49202 100644 --- a/go.sum +++ b/go.sum @@ -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/go.mod h1:XRf0+7nhOkIEr0gi9DUG4RvV2KaOFB0fYPDaR1KLenw= -git.tordarus.net/Tordarus/buf2d v1.1.4 h1:5R33Bq/no3uQIk7Tql6z55LI635DHeXQ7STNz0R7FQQ= -git.tordarus.net/Tordarus/buf2d v1.1.4/go.mod h1:XXPpS8nQK0gUI0ki7ftV/qlprsGCRWFVSD4ybvDuUL8= +git.tordarus.net/Tordarus/buf2d v1.1.6 h1:qBLHSRj0eDxaG/IOUncJpWr1xwEKD4SCZReLo5r5eJw= +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/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= diff --git a/views/layout_border.go b/layouts/layout_border.go similarity index 99% rename from views/layout_border.go rename to layouts/layout_border.go index 916fba6..309eb71 100644 --- a/views/layout_border.go +++ b/layouts/layout_border.go @@ -1,4 +1,4 @@ -package views +package layouts import ( "git.tordarus.net/Tordarus/tui" diff --git a/views/layout_coord.go b/layouts/layout_coord.go similarity index 98% rename from views/layout_coord.go rename to layouts/layout_coord.go index 32c846a..6e0d852 100644 --- a/views/layout_coord.go +++ b/layouts/layout_coord.go @@ -1,4 +1,4 @@ -package views +package layouts import "git.tordarus.net/Tordarus/tui" diff --git a/views/layout_flow.go b/layouts/layout_flow.go similarity index 99% rename from views/layout_flow.go rename to layouts/layout_flow.go index 0e63416..0675526 100644 --- a/views/layout_flow.go +++ b/layouts/layout_flow.go @@ -1,4 +1,4 @@ -package views +package layouts import ( "git.tordarus.net/Tordarus/tui" diff --git a/layouts/layout_grid.go b/layouts/layout_grid.go new file mode 100644 index 0000000..a4c92d6 --- /dev/null +++ b/layouts/layout_grid.go @@ -0,0 +1,3 @@ +package layouts + +// TODO diff --git a/views/layout_separator.go b/layouts/layout_separator.go similarity index 99% rename from views/layout_separator.go rename to layouts/layout_separator.go index 19ff7b2..d4ea868 100644 --- a/views/layout_separator.go +++ b/layouts/layout_separator.go @@ -1,4 +1,4 @@ -package views +package layouts import ( "git.tordarus.net/Tordarus/tui" diff --git a/layouts/utils.go b/layouts/utils.go new file mode 100644 index 0000000..75197bd --- /dev/null +++ b/layouts/utils.go @@ -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 +} diff --git a/modals/modal_info.go b/modals/modal_info.go new file mode 100644 index 0000000..ee9a295 --- /dev/null +++ b/modals/modal_info.go @@ -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 +} diff --git a/screen.go b/screen.go index df3153e..e21f2ef 100644 --- a/screen.go +++ b/screen.go @@ -5,6 +5,7 @@ import ( "git.tordarus.net/Tordarus/adverr" "git.tordarus.net/Tordarus/buf2d" + "git.tordarus.net/Tordarus/dstruct" "github.com/gdamore/tcell" ) @@ -16,6 +17,9 @@ type Screen struct { stopCh chan error redrawCh chan struct{} + started bool + + modals *dstruct.Stack[View] // Root is the root view which is currently shown on screen Root View @@ -32,6 +36,7 @@ func NewScreen(root View) (*Screen, error) { scr: scr, stopCh: make(chan error, 1), redrawCh: make(chan struct{}, 1), + modals: dstruct.NewStack[View](), } s.KeyPressed = CloseOnCtrlC(s) @@ -49,9 +54,10 @@ func (s *Screen) Start() error { } defer s.scr.Fini() - //defer close(s.redrawCh) + defer close(s.redrawCh) s.scr.EnableMouse() + s.started = true return <-s.stopCh } @@ -64,6 +70,11 @@ func (s *Screen) StopWithError(err error) { } func (s *Screen) onKeyPressed(event *KeyEvent) { + if !s.modals.Empty() { + s.modals.Peek().OnKeyPressed(event) + return + } + if s.KeyPressed == nil || !s.KeyPressed(event) { s.Root.OnKeyPressed(event) } @@ -71,12 +82,20 @@ func (s *Screen) onKeyPressed(event *KeyEvent) { } func (s *Screen) onMouseEvent(event *MouseEvent) { - if s.MouseEvent == nil || !s.MouseEvent(event) { - s.Root.OnMouseEvent(event) - } 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 { @@ -89,7 +108,9 @@ func convertMouseEvent(original *tcell.EventMouse) *MouseEvent { } func (s *Screen) Redraw() { - s.redrawCh <- struct{}{} + if s.started { + s.redrawCh <- struct{}{} + } } func (s *Screen) eventloop() { @@ -128,8 +149,18 @@ func (s *Screen) drawloop() { s.buf.Fill(DefaultRune) } + // draw root view rw, rh := s.Root.Layout() 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) } } @@ -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() +} diff --git a/tests/screen_test.go b/tests/screen_test.go index 67a2c22..bfcc11d 100644 --- a/tests/screen_test.go +++ b/tests/screen_test.go @@ -10,6 +10,8 @@ import ( "testing" "git.tordarus.net/Tordarus/tui" + "git.tordarus.net/Tordarus/tui/layouts" + "git.tordarus.net/Tordarus/tui/modals" "git.tordarus.net/Tordarus/tui/views" "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))))) } - flowLayout := views.NewFlowLayout(tui.Vertical) + flowLayout := layouts.NewFlowLayout(tui.Vertical) flowLayout.AppendViews(textViews...) scrollView := views.NewScrollView(flowLayout) @@ -128,6 +130,9 @@ func TestMousePosition(t *testing.T) { return true } + modal := modals.NewInfoModal("Programm wird geschlossen") + screen.OpenModal(modal) + if err := screen.Start(); err != nil { fmt.Println(err) } @@ -153,7 +158,7 @@ func TestFlowLayout(t *testing.T) { growView2 := views.NewGrowView(nil) growView2.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow)) - flowLayout := views.NewFlowLayout(tui.Vertical) + flowLayout := layouts.NewFlowLayout(tui.Vertical) flowLayout.AppendViews(textView, growView, textView2) screen, err := tui.NewScreen(flowLayout) @@ -181,7 +186,7 @@ func TestSeparatorLayout(t *testing.T) { growView2 := views.NewGrowView(nil) growView2.SetStyle(tui.StyleDefault.Background(tcell.ColorYellow)) - separatorLayout := views.NewSeparatorLayout(tui.Vertical) + separatorLayout := layouts.NewSeparatorLayout(tui.Vertical) separatorLayout.AppendView(frameView, 1) separatorLayout.AppendView(growView, 1) separatorLayout.AppendView(textView2, 1) @@ -260,13 +265,13 @@ func TestBorderLayout(t *testing.T) { return false } - borderLayout := views.NewBorderLayout() + borderLayout := layouts.NewBorderLayout() borderLayout.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple)) - borderLayout.SetView(topView, views.Top) - borderLayout.SetView(bottomView, views.Bottom) - borderLayout.SetView(leftView, views.Left) - borderLayout.SetView(rightView, views.Right) - borderLayout.SetView(centerView, views.Center) + borderLayout.SetView(topView, layouts.Top) + borderLayout.SetView(bottomView, layouts.Bottom) + borderLayout.SetView(leftView, layouts.Left) + borderLayout.SetView(rightView, layouts.Right) + borderLayout.SetView(centerView, layouts.Center) screen, err := tui.NewScreen(borderLayout) if err != nil { diff --git a/tmpl_modal.go b/tmpl_modal.go new file mode 100644 index 0000000..f511b5f --- /dev/null +++ b/tmpl_modal.go @@ -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 +} diff --git a/viewtmpl.go b/tmpl_view.go similarity index 100% rename from viewtmpl.go rename to tmpl_view.go diff --git a/wrappertmpl.go b/tmpl_wrapper.go similarity index 100% rename from wrappertmpl.go rename to tmpl_wrapper.go diff --git a/utils.go b/utils.go index 1748145..5e3a4cd 100644 --- a/utils.go +++ b/utils.go @@ -127,8 +127,8 @@ func CloseOnKeyPressed(screen *Screen, key tcell.Key) func(event *KeyEvent) (con return func(event *KeyEvent) (consumed bool) { if event.Key() == key { screen.Stop() - return false + return true } - return true + return false } } diff --git a/view.go b/view.go index 7305d65..2cced41 100644 --- a/view.go +++ b/view.go @@ -38,3 +38,13 @@ type Wrapper interface { SetView(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) +} diff --git a/views/layout_grid.go b/views/layout_grid.go deleted file mode 100644 index 45abf24..0000000 --- a/views/layout_grid.go +++ /dev/null @@ -1,3 +0,0 @@ -package views - -// TODO diff --git a/views/utils.go b/views/utils.go index a18a257..048d28b 100644 --- a/views/utils.go +++ b/views/utils.go @@ -1,11 +1,5 @@ package views -import ( - "math" - - "git.tordarus.net/Tordarus/tui" -) - func min(x, y int) int { if x < y { return x @@ -30,61 +24,3 @@ func iff[T any](condition bool, trueValue, falseValue T) T { } 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 -} diff --git a/views/view_frame.go b/views/view_frame.go index e2b4661..34979a2 100644 --- a/views/view_frame.go +++ b/views/view_frame.go @@ -6,6 +6,8 @@ import "git.tordarus.net/Tordarus/tui" type FrameView struct { tui.WrapperTmpl Anchor tui.Anchor + + DontClearBuffer bool } var _ tui.Wrapper = &FrameView{} @@ -18,7 +20,9 @@ func NewFrameView(view tui.View) *FrameView { } func (g *FrameView) Draw(buf *tui.ViewBuffer) { - g.ViewTmpl.Draw(buf) + if !g.DontClearBuffer { + g.ViewTmpl.Draw(buf) + } w, h := g.View().Layout() w = iff(w >= 0, w, buf.Width())