diff options
| -rw-r--r-- | tui/draw.go | 20 | ||||
| -rw-r--r-- | tui/layout.go | 62 | ||||
| -rw-r--r-- | tui/text_input.go | 15 |
3 files changed, 80 insertions, 17 deletions
diff --git a/tui/draw.go b/tui/draw.go index 3772aa0..37a678e 100644 --- a/tui/draw.go +++ b/tui/draw.go @@ -29,6 +29,7 @@ type screen struct { prevSize ScreenSize writer *bufio.Writer cursor pos + showCursor bool } var ( @@ -73,10 +74,16 @@ func WriteAt(x int, y int, text string, style Style) int { func Clear() { scr.front = make(surface) + ClearCursor() } -func MoveCursor(x int, y int) { +func ShowCursor(x int, y int) { scr.cursor = pos {x, y} + scr.showCursor = true +} + +func ClearCursor() { + scr.showCursor = false } var saved *term.State @@ -120,7 +127,12 @@ func End() { saved = nil } +func writeClearCursor() { + scr.writer.WriteString(terminfo.Get(caps.CursorInvisible)) +} + func writeCursor(x int, y int) { + scr.writer.WriteString(terminfo.Get(caps.CursorNormal)) scr.writer.WriteString(terminfo.Get(caps.CursorAddress, y, x)) } @@ -143,6 +155,8 @@ func writeStyle(style Style) { } func Present() error { + writeClearCursor() + s := Size() reset := true style := DefaultStyle @@ -179,7 +193,9 @@ func Present() error { p.x += cw } } - writeCursor(scr.cursor.x, scr.cursor.y) + if scr.showCursor { + writeCursor(scr.cursor.x, scr.cursor.y) + } scr.prevSize = s f := scr.front diff --git a/tui/layout.go b/tui/layout.go index 4feda2f..0681e8e 100644 --- a/tui/layout.go +++ b/tui/layout.go @@ -16,6 +16,9 @@ type Box struct { Scroll *ScrollState text []textRun computedLines [][]textRun + cursorLine int + cursorCol int + hasCursor bool children []*Box computedPosition int computedSize [2]int @@ -62,6 +65,7 @@ func (d Direction) reverse() bool { type textRun struct { text string style *Style + hasCursor bool } type rect struct { @@ -124,7 +128,11 @@ func Push(id string, box Box) { } func Text(text string, style *Style) { - top().text = append(top().text, textRun {text, style}) + top().text = append(top().text, textRun {text, style, false}) +} + +func Cursor() { + top().text = append(top().text, textRun {"", nil, true}) } func Pop() { @@ -232,23 +240,36 @@ func (b *Box) computeText(axis int) { line []textRun text, word strings.Builder lineWidth, wordWidth int + hasCursor bool + cursorAt int run textRun ) - breakLine := func() { + flushText := func() { if text.Len() != 0 { - line = append(line, textRun {text.String(), run.style}) + line = append(line, textRun {text.String(), run.style, false}) } text.Reset() + } + flushLine := func() { + flushText() b.computedLines = append(b.computedLines, line) lineWidth = 0 line = nil } - flushWord := func() { + breakWord := func() { if lineWidth + wordWidth > limit { - breakLine() + flushLine() } g := uniseg.NewGraphemes(word.String()) + pos := 0 for g.Next() { + if hasCursor && pos == cursorAt { + b.cursorLine = len(b.computedLines) + b.cursorCol = lineWidth + b.hasCursor = true + hasCursor = false + } + if g.Width() > limit { continue } @@ -257,18 +278,24 @@ func (b *Box) computeText(axis int) { } text.WriteString(g.Str()) if g.Width() + lineWidth > limit { - breakLine() + flushLine() } lineWidth += g.Width() + + pos++ } wordWidth = 0 word.Reset() } for _, run := range b.text { + if run.hasCursor { + hasCursor = true + cursorAt = wordWidth + } g := uniseg.NewGraphemes(run.text) for g.Next() { if g.LineBreak() == uniseg.LineCanBreak { - flushWord() + breakWord() } if lineWidth != 0 || !unicode.IsSpace(g.Runes()[0]) { word.WriteString(g.Str()) @@ -276,21 +303,18 @@ func (b *Box) computeText(axis int) { } _, end := g.Positions() if end == len(run.text) { - flushWord() + breakWord() break } if g.LineBreak() == uniseg.LineMustBreak { - flushWord() - breakLine() + breakWord() + flushLine() } } - if text.Len() != 0 { - line = append(line, textRun {text.String(), run.style}) - text.Reset() - } + flushText() } if len(line) != 0 || text.Len() != 0 { - breakLine() + flushLine() } if b.Height == TextSize { b.computedSize[axis] = len(b.computedLines) + b.marginsSize(axis) @@ -437,6 +461,14 @@ func (b *Box) drawComputed(parentRect rect, parentStyle Style) { x += WriteAt(x, y, t.text, s) } } + if b.hasCursor { + cx := b.cursorCol + b.computedRect.min[0] + b.Margins[0] + cy := b.cursorLine + b.computedRect.min[1] + b.Margins[2] + if cx >= viewRect.min[0] && cx < viewRect.max[0] && + cy >= viewRect.min[1] && cy < viewRect.max[1] { + ShowCursor(cx, cy) + } + } for _, c := range b.children { c.drawComputed(viewRect, style) } diff --git a/tui/text_input.go b/tui/text_input.go new file mode 100644 index 0000000..a8c7d6d --- /dev/null +++ b/tui/text_input.go @@ -0,0 +1,15 @@ +package tui + +import ( +// "github.com/rivo/uniseg" +// "strings" +) + +type TextInput struct { + Contents string + Cursor int +} + +func (t *TextInput) Update(ev Event) { + t.Cursor = max(t.Cursor, 0) +} |
