summaryrefslogtreecommitdiff
path: root/tui/layout.go
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/layout.go
parent22a50b723eb93c97d67eac362422d2424436b16b (diff)
support cursor in TUI layouts
Diffstat (limited to 'tui/layout.go')
-rw-r--r--tui/layout.go62
1 files changed, 47 insertions, 15 deletions
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)
}