Compare commits
No commits in common. "main" and "v0.0.13" have entirely different histories.
@ -1,36 +1,30 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.milar.in/milarin/slices"
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawBuffer(scr tcell.Screen, buf *ViewBuffer) {
|
func drawBuffer(scr tcell.Screen, buf *ViewBuffer) {
|
||||||
|
// buf.ForEach(func(x, y int, rn Rune) {
|
||||||
|
// scr.SetContent(x, y, rn.Rn, nil, rn.Style)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// TODO use runewidth.RuneWidth(rn)?
|
||||||
buf.ForEachLine(func(y int, content []Rune) {
|
buf.ForEachLine(func(y int, content []Rune) {
|
||||||
for x := 0; x < buf.Width(); x++ {
|
for x := 0; x < buf.Width(); x++ {
|
||||||
rn := content[x]
|
rn := content[x]
|
||||||
|
if rn.Rn >= '─' && rn.Rn <= '╿' {
|
||||||
extraRuneCount := runewidth.RuneWidth(rn.Rn) - 1
|
scr.SetContent(x, y, rn.Rn, []rune{content[x+1].Rn}, rn.Style)
|
||||||
var extraRunes []Rune
|
x++
|
||||||
if x+1+extraRuneCount < len(content) {
|
|
||||||
extraRunes = content[x+1 : x+1+extraRuneCount]
|
|
||||||
} else {
|
} else {
|
||||||
extraRunes = content[x+1 : x+1]
|
scr.SetContent(x, y, rn.Rn, nil, rn.Style)
|
||||||
}
|
}
|
||||||
|
|
||||||
scr.SetContent(x, y, rn.Rn, slices.Map(extraRunes, getRune), rn.Style)
|
|
||||||
x += extraRuneCount
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
scr.Show()
|
scr.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRune(rn Rune) rune {
|
|
||||||
return rn.Rn
|
|
||||||
}
|
|
||||||
|
|
||||||
func truncateBuffer(buf *ViewBuffer, w, h int) *ViewBuffer {
|
func truncateBuffer(buf *ViewBuffer, w, h int) *ViewBuffer {
|
||||||
if w < 0 {
|
if w < 0 {
|
||||||
w = buf.Width()
|
w = buf.Width()
|
||||||
|
9
go.mod
9
go.mod
@ -1,13 +1,10 @@
|
|||||||
module git.milar.in/milarin/tui
|
module git.tordarus.net/Tordarus/tui
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.milar.in/milarin/adverr v1.1.0
|
git.tordarus.net/Tordarus/adverr v0.2.0
|
||||||
git.milar.in/milarin/buf2d v1.1.7
|
git.tordarus.net/Tordarus/buf2d v1.1.4
|
||||||
git.milar.in/milarin/ds v0.0.2
|
|
||||||
git.milar.in/milarin/gmath v0.0.3
|
|
||||||
git.milar.in/milarin/slices v0.0.8
|
|
||||||
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
|
||||||
)
|
)
|
||||||
|
14
go.sum
14
go.sum
@ -1,13 +1,7 @@
|
|||||||
git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0=
|
git.tordarus.net/Tordarus/adverr v0.2.0 h1:kLYjR2/Vb2GHiSAMvAv+WPNaHR9BRphKanf8H/pCZdA=
|
||||||
git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E=
|
git.tordarus.net/Tordarus/adverr v0.2.0/go.mod h1:XRf0+7nhOkIEr0gi9DUG4RvV2KaOFB0fYPDaR1KLenw=
|
||||||
git.milar.in/milarin/buf2d v1.1.7 h1:c+YEM4jthzaLmifx9PfP1Gy4ozQxh9+0menyShj0qU0=
|
git.tordarus.net/Tordarus/buf2d v1.1.4 h1:5R33Bq/no3uQIk7Tql6z55LI635DHeXQ7STNz0R7FQQ=
|
||||||
git.milar.in/milarin/buf2d v1.1.7/go.mod h1:yiJgXMuUXTQ/Dzc/N3iIMa4riyL5y1aQgZOZfzNIWHo=
|
git.tordarus.net/Tordarus/buf2d v1.1.4/go.mod h1:XXPpS8nQK0gUI0ki7ftV/qlprsGCRWFVSD4ybvDuUL8=
|
||||||
git.milar.in/milarin/ds v0.0.2 h1:vCA3mDxZUNfvHpzrdz7SeBUKiPn74NTopo915IUG7I0=
|
|
||||||
git.milar.in/milarin/ds v0.0.2/go.mod h1:HJK7QERcRvV9j7xzEocrKUtW+1q4JB1Ly4Bj54chfwI=
|
|
||||||
git.milar.in/milarin/gmath v0.0.3 h1:ii6rKNItS55O/wtIFhD1cTN2BMwDZjTBmiOocKURvxM=
|
|
||||||
git.milar.in/milarin/gmath v0.0.3/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE=
|
|
||||||
git.milar.in/milarin/slices v0.0.8 h1:qN9TE3tkArdTixMKSnwvNPcApwAjxpLVwA5a9k1rm2s=
|
|
||||||
git.milar.in/milarin/slices v0.0.8/go.mod h1:qMhdtMnfWswc1rHpwgNw33lB84aNEkdBn5BDiYA+G3k=
|
|
||||||
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=
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package layouts
|
|
||||||
|
|
||||||
// TODO
|
|
@ -1,73 +0,0 @@
|
|||||||
package layouts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"git.milar.in/milarin/gmath"
|
|
||||||
"git.milar.in/milarin/tui"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 = gmath.Min(result.Min.Width, width)
|
|
||||||
result.Max.Width = gmath.Max(result.Max.Width, width)
|
|
||||||
} else if width < 0 {
|
|
||||||
result.HorizontalNegativeCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
if height > 0 {
|
|
||||||
result.Sum.Height += height
|
|
||||||
result.Min.Height = gmath.Min(result.Min.Height, height)
|
|
||||||
result.Max.Height = gmath.Max(result.Max.Height, height)
|
|
||||||
} else if height < 0 {
|
|
||||||
result.VerticalNegativeCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package modals
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.milar.in/milarin/tui/views"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
93
screen.go
93
screen.go
@ -3,9 +3,8 @@ package tui
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.milar.in/milarin/adverr"
|
"git.tordarus.net/Tordarus/adverr"
|
||||||
"git.milar.in/milarin/buf2d"
|
"git.tordarus.net/Tordarus/buf2d"
|
||||||
"git.milar.in/milarin/ds"
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,19 +16,9 @@ type Screen struct {
|
|||||||
|
|
||||||
stopCh chan error
|
stopCh chan error
|
||||||
redrawCh chan struct{}
|
redrawCh chan struct{}
|
||||||
started bool
|
|
||||||
|
|
||||||
modals ds.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
|
||||||
|
|
||||||
// Some unicode characters need more bytes than one. For these characters,
|
|
||||||
// an additional last column will be provided in the internal view buffer.
|
|
||||||
// That way, these characters can also be shown on the right most column.
|
|
||||||
//
|
|
||||||
// You should enable this flag if you have missing unicode characters in the last column.
|
|
||||||
UnicodeSupport bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScreen(root View) (*Screen, error) {
|
func NewScreen(root View) (*Screen, error) {
|
||||||
@ -43,11 +32,13 @@ 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: ds.NewArrayStack[View](),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.KeyPressed = CloseOnCtrlC(s)
|
s.KeyPressed = CloseOnCtrlC(s)
|
||||||
|
|
||||||
|
go s.eventloop()
|
||||||
|
go s.drawloop()
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,14 +49,9 @@ 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()
|
||||||
|
|
||||||
go s.eventloop()
|
|
||||||
go s.drawloop()
|
|
||||||
|
|
||||||
s.started = true
|
|
||||||
return <-s.stopCh
|
return <-s.stopCh
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,11 +64,6 @@ 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)
|
||||||
}
|
}
|
||||||
@ -90,20 +71,12 @@ 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 {
|
||||||
defer s.Redraw()
|
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 {
|
||||||
@ -116,9 +89,7 @@ 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() {
|
||||||
@ -149,39 +120,17 @@ func (s *Screen) drawloop() {
|
|||||||
defer s.handlePanic("panicked while redrawing")
|
defer s.handlePanic("panicked while redrawing")
|
||||||
|
|
||||||
for range s.redrawCh {
|
for range s.redrawCh {
|
||||||
s.prepareViewBuffer()
|
w, h := s.scr.Size()
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Screen) prepareViewBuffer() {
|
|
||||||
w, h := s.scr.Size()
|
|
||||||
|
|
||||||
if s.UnicodeSupport {
|
|
||||||
if s.buf == nil || s.buf.Width() != w-1 || s.buf.Height() != h {
|
|
||||||
s.buf = buf2d.NewBuffer(w, h, DefaultRune).Sub(0, 0, w-1, h)
|
|
||||||
} else {
|
|
||||||
s.buf.Fill(DefaultRune)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if s.buf == nil || s.buf.Width() != w || s.buf.Height() != h {
|
if s.buf == nil || s.buf.Width() != w || s.buf.Height() != h {
|
||||||
s.buf = buf2d.NewBuffer(w, h, DefaultRune)
|
s.buf = buf2d.NewBuffer(w, h, DefaultRune)
|
||||||
} else {
|
} else {
|
||||||
s.buf.Fill(DefaultRune)
|
s.buf.Fill(DefaultRune)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rw, rh := s.Root.Layout()
|
||||||
|
s.Root.Draw(truncateBuffer(s.buf, rw, rh))
|
||||||
|
drawBuffer(s.scr, s.buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,13 +143,3 @@ 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()
|
|
||||||
}
|
|
||||||
|
@ -7,13 +7,11 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.milar.in/milarin/tui"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
"git.milar.in/milarin/tui/layouts"
|
"git.tordarus.net/Tordarus/tui/views"
|
||||||
"git.milar.in/milarin/tui/modals"
|
|
||||||
"git.milar.in/milarin/tui/views"
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,7 +39,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 := layouts.NewFlowLayout(tui.Vertical)
|
flowLayout := views.NewFlowLayout(tui.Vertical)
|
||||||
flowLayout.AppendViews(textViews...)
|
flowLayout.AppendViews(textViews...)
|
||||||
|
|
||||||
scrollView := views.NewScrollView(flowLayout)
|
scrollView := views.NewScrollView(flowLayout)
|
||||||
@ -86,11 +84,11 @@ func TestScrollView(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBorderView(t *testing.T) {
|
func TestBorderView(t *testing.T) {
|
||||||
textView := views.NewTextView("hello world! こんにちは!")
|
textView := views.NewTextView("hello world!")
|
||||||
borderView := views.NewBorderView(textView)
|
borderView := views.NewBorderView(textView)
|
||||||
//borderView2 := views.NewBorderView(borderView)
|
//borderView2 := views.NewBorderView(borderView)
|
||||||
|
|
||||||
screen, err := tui.NewScreen(views.NewGrowView(borderView))
|
screen, err := tui.NewScreen(borderView)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -131,14 +129,6 @@ func TestMousePosition(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
modal := modals.NewInfoModal("Programm wird geschlossen")
|
|
||||||
screen.OpenModal(modal)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
screen.Stop()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := screen.Start(); err != nil {
|
if err := screen.Start(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
@ -148,10 +138,7 @@ func TestFlowLayout(t *testing.T) {
|
|||||||
textView := views.NewTextView("hello world!")
|
textView := views.NewTextView("hello world!")
|
||||||
textView.SetStyle(tui.StyleDefault.Background(tcell.ColorRed).Foreground(tcell.ColorBlack))
|
textView.SetStyle(tui.StyleDefault.Background(tcell.ColorRed).Foreground(tcell.ColorBlack))
|
||||||
|
|
||||||
textView.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
|
marginView := views.NewMarginView(textView, 3, 1, 1, 0)
|
||||||
textView.Text = "hi"
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
//borderView := views.NewBorderView(textView)
|
//borderView := views.NewBorderView(textView)
|
||||||
|
|
||||||
@ -164,15 +151,28 @@ 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 := layouts.NewFlowLayout(tui.Vertical)
|
flowLayout := views.NewFlowLayout(tui.Vertical)
|
||||||
flowLayout.AppendViews(textView, growView, textView2)
|
flowLayout.AppendViews(marginView, growView, textView2)
|
||||||
|
|
||||||
screen, err := tui.NewScreen(flowLayout)
|
constrainView := views.NewConstrainView(flowLayout, -1, -1)
|
||||||
|
constrainView.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
|
||||||
|
|
||||||
|
screen, err := tui.NewScreen(constrainView)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
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()
|
err = screen.Start()
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
@ -192,7 +192,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 := layouts.NewSeparatorLayout(tui.Vertical)
|
separatorLayout := views.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)
|
||||||
@ -271,13 +271,13 @@ func TestBorderLayout(t *testing.T) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
borderLayout := layouts.NewBorderLayout()
|
borderLayout := views.NewBorderLayout()
|
||||||
borderLayout.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
|
borderLayout.SetStyle(tui.StyleDefault.Background(tcell.ColorPurple))
|
||||||
borderLayout.SetView(topView, layouts.Top)
|
borderLayout.SetView(topView, views.Top)
|
||||||
borderLayout.SetView(bottomView, layouts.Bottom)
|
borderLayout.SetView(bottomView, views.Bottom)
|
||||||
borderLayout.SetView(leftView, layouts.Left)
|
borderLayout.SetView(leftView, views.Left)
|
||||||
borderLayout.SetView(rightView, layouts.Right)
|
borderLayout.SetView(rightView, views.Right)
|
||||||
borderLayout.SetView(centerView, layouts.Center)
|
borderLayout.SetView(centerView, views.Center)
|
||||||
|
|
||||||
screen, err := tui.NewScreen(borderLayout)
|
screen, err := tui.NewScreen(borderLayout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -285,6 +285,23 @@ func TestBorderLayout(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
screen.MouseEvent = func(event *tui.MouseEvent) (consumed bool) {
|
||||||
|
if event.Button == tui.MouseButtonLeft {
|
||||||
|
b := new(strings.Builder)
|
||||||
|
b.WriteString(textView.Text)
|
||||||
|
|
||||||
|
for slot, dim := range borderLayout.ViewDimensions {
|
||||||
|
if event.Position.In(dim) {
|
||||||
|
b.WriteString(fmt.Sprintf("%s: %s\n", slot, dim))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textView.Text = b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
err = screen.Start()
|
err = screen.Start()
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
4
types.go
4
types.go
@ -3,7 +3,7 @@ package tui
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.milar.in/milarin/buf2d"
|
"git.tordarus.net/Tordarus/buf2d"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ func P(x, y int) Point {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p Point) In(d Dimension) bool {
|
func (p Point) In(d Dimension) bool {
|
||||||
return p.X >= d.X && p.X < d.X+d.Width && p.Y >= d.Y && p.Y < d.Y+d.Height
|
return p.X > d.X && p.X < d.X+d.Width && p.Y > d.Y && p.Y < d.Y+d.Height
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Point) String() string {
|
func (p Point) String() string {
|
||||||
|
28
utils.go
28
utils.go
@ -3,7 +3,6 @@ package tui
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.milar.in/milarin/gmath"
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
@ -33,7 +32,7 @@ func WriteMultiLineString(b *ViewBuffer, str string, style Style, x, y int) (max
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
lineWidth := WriteString(b, line, style, x, y+dy)
|
lineWidth := WriteString(b, line, style, x, y+dy)
|
||||||
maxLineWidth = gmath.Max(maxLineWidth, lineWidth)
|
maxLineWidth = max(maxLineWidth, lineWidth)
|
||||||
}
|
}
|
||||||
return maxLineWidth, len(lines)
|
return maxLineWidth, len(lines)
|
||||||
}
|
}
|
||||||
@ -52,7 +51,7 @@ func MeasureMultiLineString(str string) (maxLineWidth, lineCount int) {
|
|||||||
lines := strings.Split(str, "\n")
|
lines := strings.Split(str, "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
lineWidth := MeasureString(line)
|
lineWidth := MeasureString(line)
|
||||||
maxLineWidth = gmath.Max(maxLineWidth, lineWidth)
|
maxLineWidth = max(maxLineWidth, lineWidth)
|
||||||
}
|
}
|
||||||
return maxLineWidth, len(lines)
|
return maxLineWidth, len(lines)
|
||||||
}
|
}
|
||||||
@ -70,6 +69,20 @@ func runeWidth(r rune) int {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func iff[T any](condition bool, trueValue, falseValue T) T {
|
||||||
if condition {
|
if condition {
|
||||||
return trueValue
|
return trueValue
|
||||||
@ -114,13 +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 true
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
if screen.Root != nil {
|
|
||||||
return screen.Root.OnKeyPressed(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
view.go
10
view.go
@ -38,13 +38,3 @@ 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)
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package layouts
|
package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.milar.in/milarin/tui"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BorderLayout ia a tui.Layout which places its children onto a given tui.Side
|
// BorderLayout ia a tui.Layout which places its children onto a given tui.Side
|
||||||
@ -11,15 +11,15 @@ type BorderLayout struct {
|
|||||||
horizontalLayout *LayoutResult
|
horizontalLayout *LayoutResult
|
||||||
verticalLayout *LayoutResult
|
verticalLayout *LayoutResult
|
||||||
|
|
||||||
viewDims map[Slot]tui.Dimension
|
ViewDimensions map[Slot]tui.Dimension
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ tui.Layout = &BorderLayout{}
|
var _ tui.Layout = &BorderLayout{}
|
||||||
|
|
||||||
func NewBorderLayout() *BorderLayout {
|
func NewBorderLayout() *BorderLayout {
|
||||||
return &BorderLayout{
|
return &BorderLayout{
|
||||||
views: map[Slot]tui.View{},
|
views: map[Slot]tui.View{},
|
||||||
viewDims: map[Slot]tui.Dimension{},
|
ViewDimensions: map[Slot]tui.Dimension{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ func (g *BorderLayout) Draw(buf *tui.ViewBuffer) {
|
|||||||
topHeight = int(float64(buf.Height()) * float64(topHeight) / float64(verticalLayout.Sum.Height))
|
topHeight = int(float64(buf.Height()) * float64(topHeight) / float64(verticalLayout.Sum.Height))
|
||||||
}
|
}
|
||||||
|
|
||||||
g.viewDims[Top] = tui.D(0, 0, buf.Width(), topHeight)
|
g.ViewDimensions[Top] = tui.D(0, 0, buf.Width(), topHeight)
|
||||||
view.Draw(buf.Sub(0, 0, buf.Width(), topHeight))
|
view.Draw(buf.Sub(0, 0, buf.Width(), topHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ func (g *BorderLayout) Draw(buf *tui.ViewBuffer) {
|
|||||||
bottomHeight = int(float64(buf.Height()) * float64(bottomHeight) / float64(verticalLayout.Sum.Height))
|
bottomHeight = int(float64(buf.Height()) * float64(bottomHeight) / float64(verticalLayout.Sum.Height))
|
||||||
}
|
}
|
||||||
|
|
||||||
g.viewDims[Bottom] = tui.D(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight)
|
g.ViewDimensions[Bottom] = tui.D(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight)
|
||||||
view.Draw(buf.Sub(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight))
|
view.Draw(buf.Sub(0, buf.Height()-bottomHeight, buf.Width(), bottomHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ func (g *BorderLayout) Draw(buf *tui.ViewBuffer) {
|
|||||||
leftWidth = int(float64(buf.Width()) * float64(leftWidth) / float64(horizontalLayout.Sum.Width))
|
leftWidth = int(float64(buf.Width()) * float64(leftWidth) / float64(horizontalLayout.Sum.Width))
|
||||||
}
|
}
|
||||||
|
|
||||||
g.viewDims[Left] = tui.D(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight)
|
g.ViewDimensions[Left] = tui.D(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight)
|
||||||
view.Draw(buf.Sub(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight))
|
view.Draw(buf.Sub(0, topHeight, leftWidth, buf.Height()-topHeight-bottomHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,12 +118,12 @@ func (g *BorderLayout) Draw(buf *tui.ViewBuffer) {
|
|||||||
rightWidth = int(float64(buf.Width()) * float64(rightWidth) / float64(horizontalLayout.Sum.Width))
|
rightWidth = int(float64(buf.Width()) * float64(rightWidth) / float64(horizontalLayout.Sum.Width))
|
||||||
}
|
}
|
||||||
|
|
||||||
g.viewDims[Right] = tui.D(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight)
|
g.ViewDimensions[Right] = tui.D(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight)
|
||||||
view.Draw(buf.Sub(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight))
|
view.Draw(buf.Sub(buf.Width()-rightWidth, topHeight, rightWidth, buf.Height()-topHeight-bottomHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
if view, ok := g.views[Center]; ok {
|
if view, ok := g.views[Center]; ok {
|
||||||
g.viewDims[Center] = tui.D(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight)
|
g.ViewDimensions[Center] = tui.D(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight)
|
||||||
view.Draw(buf.Sub(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight))
|
view.Draw(buf.Sub(leftWidth, topHeight, buf.Width()-leftWidth-rightWidth, buf.Height()-topHeight-bottomHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,12 +138,8 @@ func (g *BorderLayout) Layout() (prefWidth, prefHeight int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *BorderLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
func (g *BorderLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
||||||
if g.KeyPressed != nil {
|
|
||||||
return g.KeyPressed(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, view := range g.Views() {
|
for _, view := range g.Views() {
|
||||||
if consumed := view.OnKeyPressed(event); consumed {
|
if view.OnKeyPressed(event) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,11 +147,7 @@ func (g *BorderLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *BorderLayout) OnMouseEvent(event *tui.MouseEvent) (consumed bool) {
|
func (g *BorderLayout) OnMouseEvent(event *tui.MouseEvent) (consumed bool) {
|
||||||
if g.MouseEvent != nil {
|
for slot, dim := range g.ViewDimensions {
|
||||||
return g.MouseEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
for slot, dim := range g.viewDims {
|
|
||||||
if event.Position.In(dim) {
|
if event.Position.In(dim) {
|
||||||
g.views[slot].OnMouseEvent(event)
|
g.views[slot].OnMouseEvent(event)
|
||||||
return true
|
return true
|
@ -1,6 +1,6 @@
|
|||||||
package layouts
|
package views
|
||||||
|
|
||||||
import "git.milar.in/milarin/tui"
|
import "git.tordarus.net/Tordarus/tui"
|
||||||
|
|
||||||
// CoordLayout is a tui.Layout which places its children on predefined coordinates
|
// CoordLayout is a tui.Layout which places its children on predefined coordinates
|
||||||
type CoordLayout struct {
|
type CoordLayout struct {
|
||||||
@ -41,10 +41,6 @@ func (v *CoordLayout) Layout() (prefWidth, prefHeight int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *CoordLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
func (g *CoordLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
||||||
if g.KeyPressed != nil {
|
|
||||||
return g.KeyPressed(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, view := range g.Views() {
|
for _, view := range g.Views() {
|
||||||
if view.OnKeyPressed(event) {
|
if view.OnKeyPressed(event) {
|
||||||
return true
|
return true
|
||||||
@ -53,4 +49,4 @@ func (g *CoordLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO OnMouseEvent
|
// TODO OnMouseClicked
|
@ -1,7 +1,7 @@
|
|||||||
package layouts
|
package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.milar.in/milarin/tui"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FlowLayout ia a tui.Layout which places its children in a linear layout
|
// FlowLayout ia a tui.Layout which places its children in a linear layout
|
||||||
@ -10,8 +10,6 @@ type FlowLayout struct {
|
|||||||
views []tui.View
|
views []tui.View
|
||||||
lastLayoutPhase *LayoutResult
|
lastLayoutPhase *LayoutResult
|
||||||
|
|
||||||
viewDims map[tui.View]tui.Dimension
|
|
||||||
|
|
||||||
// Orientation defines in which direction the children will be placed
|
// Orientation defines in which direction the children will be placed
|
||||||
Orientation tui.Orientation
|
Orientation tui.Orientation
|
||||||
}
|
}
|
||||||
@ -21,7 +19,6 @@ var _ tui.Layout = &FlowLayout{}
|
|||||||
func NewFlowLayout(orientation tui.Orientation) *FlowLayout {
|
func NewFlowLayout(orientation tui.Orientation) *FlowLayout {
|
||||||
return &FlowLayout{
|
return &FlowLayout{
|
||||||
views: make([]tui.View, 0),
|
views: make([]tui.View, 0),
|
||||||
viewDims: map[tui.View]tui.Dimension{},
|
|
||||||
Orientation: orientation,
|
Orientation: orientation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,7 +42,6 @@ func (g *FlowLayout) InsertView(v tui.View, index int) {
|
|||||||
func (g *FlowLayout) removeView(v tui.View) {
|
func (g *FlowLayout) removeView(v tui.View) {
|
||||||
for index, view := range g.Views() {
|
for index, view := range g.Views() {
|
||||||
if v == view {
|
if v == view {
|
||||||
delete(g.viewDims, view)
|
|
||||||
g.views = append(g.views[:index], g.views[index+1:]...)
|
g.views = append(g.views[:index], g.views[index+1:]...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -85,7 +81,6 @@ func (g *FlowLayout) Draw(buf *tui.ViewBuffer) {
|
|||||||
size.Width = iff(layout.Sum.Width > buf.Width(), 0, remainingSpacePerView)
|
size.Width = iff(layout.Sum.Width > buf.Width(), 0, remainingSpacePerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
g.viewDims[view] = tui.D(x, 0, size.Width, size.Height)
|
|
||||||
view.Draw(buf.Sub(x, 0, size.Width, size.Height))
|
view.Draw(buf.Sub(x, 0, size.Width, size.Height))
|
||||||
|
|
||||||
x += size.Width
|
x += size.Width
|
||||||
@ -111,7 +106,6 @@ func (g *FlowLayout) Draw(buf *tui.ViewBuffer) {
|
|||||||
size.Height = iff(layout.Sum.Height > buf.Height(), 0, remainingSpacePerView)
|
size.Height = iff(layout.Sum.Height > buf.Height(), 0, remainingSpacePerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
g.viewDims[view] = tui.D(0, y, size.Width, size.Height)
|
|
||||||
view.Draw(buf.Sub(0, y, size.Width, size.Height))
|
view.Draw(buf.Sub(0, y, size.Width, size.Height))
|
||||||
|
|
||||||
y += size.Height
|
y += size.Height
|
||||||
@ -141,10 +135,6 @@ func (g *FlowLayout) Layout() (prefWidth, prefHeight int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *FlowLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
func (g *FlowLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
||||||
if g.KeyPressed != nil {
|
|
||||||
return g.KeyPressed(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, view := range g.Views() {
|
for _, view := range g.Views() {
|
||||||
if view.OnKeyPressed(event) {
|
if view.OnKeyPressed(event) {
|
||||||
return true
|
return true
|
||||||
@ -153,12 +143,4 @@ func (g *FlowLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *FlowLayout) OnMouseEvent(event *tui.MouseEvent) (consumed bool) {
|
// TODO OnMouseClicked
|
||||||
for view, dim := range g.viewDims {
|
|
||||||
if event.Position.In(dim) {
|
|
||||||
view.OnMouseEvent(event)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
3
views/layout_grid.go
Normal file
3
views/layout_grid.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
// TODO
|
@ -1,7 +1,7 @@
|
|||||||
package layouts
|
package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.milar.in/milarin/tui"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SeperatorLayout ia a tui.Layout which separates its view into gravity-based portions
|
// SeperatorLayout ia a tui.Layout which separates its view into gravity-based portions
|
||||||
@ -102,17 +102,12 @@ func (g *SeperatorLayout) Layout() (prefWidth, prefHeight int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *SeperatorLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
func (g *SeperatorLayout) OnKeyPressed(event *tui.KeyEvent) (consumed bool) {
|
||||||
if g.KeyPressed != nil {
|
|
||||||
return g.KeyPressed(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, view := range g.Views() {
|
for _, view := range g.Views() {
|
||||||
if view.OnKeyPressed(event) {
|
if view.OnKeyPressed(event) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO OnMouseEvent
|
// TODO OnMouseClicked
|
@ -1,8 +1,90 @@
|
|||||||
package views
|
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 limit(v, minv, maxv int) int {
|
||||||
|
return min(max(v, minv), maxv)
|
||||||
|
}
|
||||||
|
|
||||||
func iff[T any](condition bool, trueValue, falseValue T) T {
|
func iff[T any](condition bool, trueValue, falseValue T) T {
|
||||||
if condition {
|
if condition {
|
||||||
return trueValue
|
return trueValue
|
||||||
}
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import "git.milar.in/milarin/tui"
|
import "git.tordarus.net/Tordarus/tui"
|
||||||
|
|
||||||
// BorderView is a tui.Wrapper which draws an ASCII border around its view.
|
// 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.
|
// Be aware that box drawing characters must share the same tui.Style with the next character.
|
||||||
@ -101,9 +101,9 @@ func (v *BorderView) Layout() (prefWidth, prefHeight int) {
|
|||||||
|
|
||||||
for side, border := range v.ShowBorder {
|
for side, border := range v.ShowBorder {
|
||||||
if border {
|
if border {
|
||||||
if side.Horizontal() && w >= 0 {
|
if side.Horizontal() {
|
||||||
w++
|
w++
|
||||||
} else if side.Vertical() && h >= 0 {
|
} else if side.Vertical() {
|
||||||
h++
|
h++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.milar.in/milarin/gmath"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
"git.milar.in/milarin/tui"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConstrainView is a tui.Wrapper which constrains the dimensions of its View
|
// ConstrainView is a tui.Wrapper which constrains the dimensions of its View
|
||||||
@ -31,7 +30,7 @@ func (v *ConstrainView) Layout() (prefWidth, prefHeight int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vw, vh := v.View().Layout()
|
vw, vh := v.View().Layout()
|
||||||
prefWidth = iff(vw >= 0, gmath.Min(vw, v.MaxWidth), v.MaxWidth)
|
prefWidth = iff(vw >= 0, min(vw, v.MaxWidth), v.MaxWidth)
|
||||||
prefHeight = iff(vh >= 0, gmath.Min(vh, v.MaxHeight), v.MaxHeight)
|
prefHeight = iff(vh >= 0, min(vh, v.MaxHeight), v.MaxHeight)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import "git.milar.in/milarin/tui"
|
import "git.tordarus.net/Tordarus/tui"
|
||||||
|
|
||||||
// FrameView is a tui.Wrapper which draws its view preferably with preferred size on its tui.Anchor point
|
// FrameView is a tui.Wrapper which draws its view preferably with preferred size on its tui.Anchor point
|
||||||
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{}
|
||||||
@ -20,9 +18,7 @@ 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())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.milar.in/milarin/tui"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GrowView is a tui.Wrapper which always demands all available space
|
// GrowView is a tui.Wrapper which always demands all available space
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import "git.milar.in/milarin/tui"
|
import "git.tordarus.net/Tordarus/tui"
|
||||||
|
|
||||||
// MarginView is a tui.Wrapper which applies margin around its view
|
// MarginView is a tui.Wrapper which applies margin around its view
|
||||||
type MarginView struct {
|
type MarginView struct {
|
||||||
|
@ -3,9 +3,8 @@ package views
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.milar.in/milarin/buf2d"
|
"git.tordarus.net/Tordarus/buf2d"
|
||||||
"git.milar.in/milarin/gmath"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
"git.milar.in/milarin/tui"
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -116,8 +115,8 @@ func (v *ScrollView) Layout() (prefWidth, prefHeight int) {
|
|||||||
|
|
||||||
func (v *ScrollView) Scroll(verticalOffset, horizontalOffset int) {
|
func (v *ScrollView) Scroll(verticalOffset, horizontalOffset int) {
|
||||||
if v.buf != nil {
|
if v.buf != nil {
|
||||||
v.verticalScrollOffset = gmath.Clamp(v.verticalScrollOffset+verticalOffset, 0, gmath.Max(v.buf.Height()-v.height, 0))
|
v.verticalScrollOffset = limit(v.verticalScrollOffset+verticalOffset, 0, max(v.buf.Height()-v.height, 0))
|
||||||
v.horizontalScrollOffset = gmath.Clamp(v.horizontalScrollOffset+horizontalOffset, 0, gmath.Max(v.buf.Width()-v.width, 0))
|
v.horizontalScrollOffset = limit(v.horizontalScrollOffset+horizontalOffset, 0, max(v.buf.Width()-v.width, 0))
|
||||||
} else {
|
} else {
|
||||||
v.verticalScrollOffset = v.verticalScrollOffset + verticalOffset
|
v.verticalScrollOffset = v.verticalScrollOffset + verticalOffset
|
||||||
v.horizontalScrollOffset = v.horizontalScrollOffset + horizontalOffset
|
v.horizontalScrollOffset = v.horizontalScrollOffset + horizontalOffset
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.milar.in/milarin/tui"
|
"git.tordarus.net/Tordarus/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TextView is a tui.View which prints text
|
// TextView is a tui.View which prints text
|
||||||
|
Loading…
x
Reference in New Issue
Block a user