okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauffeat(api)!: migrate daemon and clients to protocol 361c43ca

main
2.1 KiB106 linesraw
1package tui
2
3import (
4	"strings"
5
6	"github.com/rivo/uniseg"
7
8	"github.com/charmbracelet/x/ansi"
9)
10
11type agentCell struct {
12	hue          *rgb
13	text         string
14	width        int
15	continuation bool
16}
17
18type agentCanvas struct {
19	cells [][]agentCell
20	w, h  int
21}
22
23func newCanvas(w, h int) *agentCanvas {
24	c := &agentCanvas{w: w, h: h, cells: make([][]agentCell, h)}
25	for y := range c.cells {
26		c.cells[y] = make([]agentCell, w)
27		for x := range c.cells[y] {
28			c.cells[y][x] = agentCell{text: " ", width: 1}
29		}
30	}
31	return c
32}
33
34// clearAt removes a whole grapheme when another graph element overwrites it.
35func (c *agentCanvas) clearAt(x, y int) {
36	start := x
37	for start > 0 && c.cells[y][start].continuation {
38		start--
39	}
40	width := max(1, c.cells[y][start].width)
41	for column := start; column < min(c.w, start+width); column++ {
42		c.cells[y][column] = agentCell{text: " ", width: 1}
43	}
44}
45
46func (c *agentCanvas) put(x, y int, text string, hue rgb) {
47	if y < 0 || y >= c.h {
48		return
49	}
50	graphemes := uniseg.NewGraphemes(text)
51	for graphemes.Next() {
52		cluster := graphemes.Str()
53		width := ansi.StringWidth(cluster)
54		if width == 0 {
55			continue
56		}
57		if x >= 0 && x+width <= c.w {
58			for column := x; column < x+width; column++ {
59				c.clearAt(column, y)
60			}
61			c.cells[y][x] = agentCell{text: cluster, width: width, hue: &hue}
62			for column := x + 1; column < x+width; column++ {
63				c.cells[y][column] = agentCell{continuation: true}
64			}
65		}
66		x += width
67	}
68}
69
70var sgrCache = map[string]string{}
71
72// sgrCached memoizes the escape sequence for one hex color.
73func sgrCached(hex string) string {
74	seq, ok := sgrCache[hex]
75	if !ok {
76		seq = sgr(hex, "")
77		sgrCache[hex] = seq
78	}
79	return seq
80}
81
82func (c *agentCanvas) line(y int) string {
83	var b strings.Builder
84	current := ""
85	for _, cell := range c.cells[y] {
86		if cell.continuation {
87			continue
88		}
89		seq := ""
90		if cell.hue != nil && cell.text != " " {
91			seq = sgrCached(cell.hue.hex())
92		}
93		if seq != current {
94			if current != "" {
95				b.WriteString(ansiReset)
96			}
97			b.WriteString(seq)
98			current = seq
99		}
100		b.WriteString(cell.text)
101	}
102	if current != "" {
103		b.WriteString(ansiReset)
104	}
105	return b.String()
106}