From 0a58e68ad438ff43fa5bbecdb8914aa00cab5099 Mon Sep 17 00:00:00 2001 From: citrons Date: Sat, 31 May 2025 07:16:32 -0500 Subject: support cursor in TUI layouts --- tui/layout.go | 62 ++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 15 deletions(-) (limited to 'tui/layout.go') 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) } -- cgit v1.2.3