overlay.go

 1package tui
 2
 3import (
 4	"strings"
 5
 6	"github.com/charmbracelet/x/ansi"
 7)
 8
 9// overlayBlock paints the lines of block on top of base starting at the
10// (row, col) cell position. Lines that extend past the bottom of base are
11// appended. The result preserves existing ANSI styling around the
12// overlaid region.
13func overlayBlock(base string, block []string, row, col int) string {
14	if len(block) == 0 {
15		return base
16	}
17	lines := strings.Split(base, "\n")
18	for i, overlay := range block {
19		r := row + i
20		for r >= len(lines) {
21			lines = append(lines, "")
22		}
23		lines[r] = overlayLine(lines[r], overlay, col)
24	}
25	return strings.Join(lines, "\n")
26}
27
28// overlayLine returns base with overlay painted starting at column col.
29// Existing cells under the overlay are removed; cells to the left and
30// right of the overlay are preserved with their ANSI styling intact.
31// When col exceeds the visible width of base the gap is padded with
32// spaces.
33func overlayLine(base, overlay string, col int) string {
34	if overlay == "" {
35		return base
36	}
37	overlayWidth := ansi.StringWidth(overlay)
38	baseWidth := ansi.StringWidth(base)
39
40	left := ansi.Truncate(base, col, "")
41	leftWidth := ansi.StringWidth(left)
42
43	var pad string
44	if leftWidth < col {
45		pad = strings.Repeat(" ", col-leftWidth)
46	}
47
48	var right string
49	rightStart := col + overlayWidth
50	if rightStart < baseWidth {
51		right = ansi.Cut(base, rightStart, baseWidth)
52	}
53
54	// Reset SGR after the overlay so the overlay's styles don't bleed
55	// into the surrounding cells (the rest of the row may inherit ANSI
56	// from earlier in the string).
57	return left + pad + overlay + "\x1b[0m" + right
58}