summaryrefslogtreecommitdiff
path: root/tui
diff options
context:
space:
mode:
authorcitrons <citrons@mondecitronne.com>2025-05-31 07:16:32 -0500
committercitrons <citrons@mondecitronne.com>2025-05-31 07:16:32 -0500
commit0a58e68ad438ff43fa5bbecdb8914aa00cab5099 (patch)
treecb53e132302297881d5be79813afc62a05320eda /tui
parent22a50b723eb93c97d67eac362422d2424436b16b (diff)
support cursor in TUI layouts
Diffstat (limited to 'tui')
-rw-r--r--tui/draw.go20
-rw-r--r--tui/layout.go62
-rw-r--r--tui/text_input.go15
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)
+}