canvas.go

  1package lipgloss
  2
  3import (
  4	"image"
  5	"sort"
  6
  7	uv "github.com/charmbracelet/ultraviolet"
  8	"github.com/charmbracelet/x/ansi"
  9)
 10
 11// Canvas is a collection of layers that can be composed together to form a
 12// single frame of text.
 13type Canvas struct {
 14	layers []*Layer
 15}
 16
 17// NewCanvas creates a new Canvas with the given layers. This is a convenient
 18// way to create a Canvas with one or more layers.
 19func NewCanvas(layers ...*Layer) (c *Canvas) {
 20	c = new(Canvas)
 21	c.AddLayers(layers...)
 22	return
 23}
 24
 25// InBounds returns true if the point is within the bounds of the Canvas.
 26func (c *Canvas) InBounds(x, y int) bool {
 27	return image.Pt(x, y).In(c.Bounds())
 28}
 29
 30// Bounds returns the bounds of the Canvas.
 31func (c *Canvas) Bounds() image.Rectangle {
 32	// Figure out the size of the canvas
 33	x0, y0, x1, y1 := 0, 0, 0, 0
 34	for _, l := range c.layers {
 35		x0 = min(x0, l.rect.Min.X)
 36		y0 = min(y0, l.rect.Min.Y)
 37		x1 = max(x1, l.rect.Max.X)
 38		y1 = max(y1, l.rect.Max.Y)
 39	}
 40
 41	// Adjust the size of the canvas if it's negative
 42	x0, y0 = max(x0, 0), max(y0, 0)
 43
 44	return image.Rect(x0, y0, x1, y1)
 45}
 46
 47// Hit returns the [Layer.ID] at the given point. If no Layer is found,
 48// nil is returned.
 49func (c *Canvas) Hit(x, y int) string {
 50	for i := len(c.layers) - 1; i >= 0; i-- {
 51		if c.layers[i].InBounds(x, y) {
 52			return c.layers[i].Hit(x, y).GetID()
 53		}
 54	}
 55	return ""
 56}
 57
 58// AddLayers adds the given layers to the Canvas.
 59func (c *Canvas) AddLayers(layers ...*Layer) {
 60	c.layers = append(c.layers, layers...)
 61	sortLayers(c.layers, false)
 62}
 63
 64// Get returns the Layer with the given ID. If the ID is not found, nil is
 65// returned.
 66func (c *Canvas) Get(id string) *Layer {
 67	for _, l := range c.layers {
 68		if la := l.Get(id); la != nil {
 69			return la
 70		}
 71	}
 72	return nil
 73}
 74
 75// Draw draws the [Canvas] into the given screen and area.
 76func (c *Canvas) Draw(scr uv.Screen, area image.Rectangle) {
 77	for _, l := range c.layers {
 78		l.Draw(scr, area)
 79	}
 80}
 81
 82// Render renders the Canvas to a string.
 83func (c *Canvas) Render() string {
 84	area := c.Bounds()
 85	buf := uv.NewScreenBuffer(area.Dx(), area.Dy())
 86	buf.Method = ansi.GraphemeWidth
 87	c.Draw(buf, area)
 88	return buf.Render()
 89}
 90
 91// Layer represents a window layer that can be composed with other layers.
 92type Layer struct {
 93	rect     image.Rectangle
 94	zIndex   int
 95	children []*Layer
 96	id       string
 97	content  string
 98}
 99
100// NewLayer creates a new Layer with the given content. It calculates the size
101// based on the widest line and the number of lines in the content.
102func NewLayer(content string) (l *Layer) {
103	l = new(Layer)
104	l.content = content
105	height := Height(content)
106	width := Width(content)
107	l.rect = image.Rect(0, 0, width, height)
108	return l
109}
110
111// InBounds returns true if the point is within the bounds of the Layer.
112func (l *Layer) InBounds(x, y int) bool {
113	return image.Pt(x, y).In(l.Bounds())
114}
115
116// Bounds returns the bounds of the Layer.
117func (l *Layer) Bounds() image.Rectangle {
118	return l.rect
119}
120
121// Hit returns the [Layer.ID] at the given point. If no Layer is found,
122// returns nil is returned.
123func (l *Layer) Hit(x, y int) *Layer {
124	// Reverse the order of the layers so that the top-most layer is checked
125	// first.
126	for i := len(l.children) - 1; i >= 0; i-- {
127		if l.children[i].InBounds(x, y) {
128			return l.children[i].Hit(x, y)
129		}
130	}
131
132	if image.Pt(x, y).In(l.Bounds()) {
133		return l
134	}
135
136	return nil
137}
138
139// ID sets the ID of the Layer. The ID can be used to identify the Layer when
140// performing hit tests.
141func (l *Layer) ID(id string) *Layer {
142	l.id = id
143	return l
144}
145
146// GetID returns the ID of the Layer.
147func (l *Layer) GetID() string {
148	return l.id
149}
150
151// X sets the x-coordinate of the Layer.
152func (l *Layer) X(x int) *Layer {
153	l.rect = l.rect.Add(image.Pt(x, 0))
154	return l
155}
156
157// Y sets the y-coordinate of the Layer.
158func (l *Layer) Y(y int) *Layer {
159	l.rect = l.rect.Add(image.Pt(0, y))
160	return l
161}
162
163// Z sets the z-index of the Layer.
164func (l *Layer) Z(z int) *Layer {
165	l.zIndex = z
166	return l
167}
168
169// GetX returns the x-coordinate of the Layer.
170func (l *Layer) GetX() int {
171	return l.rect.Min.X
172}
173
174// GetY returns the y-coordinate of the Layer.
175func (l *Layer) GetY() int {
176	return l.rect.Min.Y
177}
178
179// GetZ returns the z-index of the Layer.
180func (l *Layer) GetZ() int {
181	return l.zIndex
182}
183
184// Width sets the width of the Layer.
185func (l *Layer) Width(width int) *Layer {
186	l.rect.Max.X = l.rect.Min.X + width
187	return l
188}
189
190// Height sets the height of the Layer.
191func (l *Layer) Height(height int) *Layer {
192	l.rect.Max.Y = l.rect.Min.Y + height
193	return l
194}
195
196// GetWidth returns the width of the Layer.
197func (l *Layer) GetWidth() int {
198	return l.rect.Dx()
199}
200
201// GetHeight returns the height of the Layer.
202func (l *Layer) GetHeight() int {
203	return l.rect.Dy()
204}
205
206// AddLayers adds child layers to the Layer.
207func (l *Layer) AddLayers(layers ...*Layer) *Layer {
208	// Make children relative to the parent
209	for _, child := range layers {
210		child.rect = child.rect.Add(l.rect.Min)
211		child.zIndex += l.zIndex
212	}
213	l.children = append(l.children, layers...)
214	sortLayers(l.children, false)
215	return l
216}
217
218// SetContent sets the content of the Layer.
219func (l *Layer) SetContent(content string) *Layer {
220	l.content = content
221	return l
222}
223
224// Content returns the content of the Layer.
225func (l *Layer) Content() string {
226	return l.content
227}
228
229// Draw draws the Layer onto the given screen buffer.
230func (l *Layer) Draw(scr uv.Screen, _ image.Rectangle) {
231	ss := uv.NewStyledString(l.content)
232	ss.Draw(scr, l.Bounds())
233	for _, child := range l.children {
234		ss := uv.NewStyledString(child.content)
235		ss.Draw(scr, child.Bounds())
236	}
237}
238
239// Get returns the Layer with the given ID. If the ID is not found, it returns
240// nil.
241func (l *Layer) Get(id string) *Layer {
242	if l.id == id {
243		return l
244	}
245	for _, child := range l.children {
246		if child.id == id {
247			return child
248		}
249	}
250	return nil
251}
252
253// sortLayers sorts the layers by z-index, from lowest to highest.
254func sortLayers(ls []*Layer, reverse bool) {
255	if reverse {
256		sort.Stable(sort.Reverse(layers(ls)))
257	} else {
258		sort.Stable(layers(ls))
259	}
260}
261
262// layers implements sort.Interface for []*Layer.
263type layers []*Layer
264
265func (l layers) Len() int           { return len(l) }
266func (l layers) Less(i, j int) bool { return l[i].zIndex < l[j].zIndex }
267func (l layers) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }