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] }