okami.mom/ultraviolet

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

Ayman Bagabasfix(screen): change Context.Position to return x, y ints524a660

main
9.7 KiB379 linesraw
1package screen
2
3import (
4	"fmt"
5	"image/color"
6	"strings"
7
8	uv "github.com/charmbracelet/ultraviolet"
9	"github.com/clipperhouse/uax29/v2/graphemes"
10)
11
12// Context represents a drawing context for rendering operations on a screen.
13type Context struct {
14	scr uv.Screen
15
16	style uv.Style
17	link  uv.Link
18	pos   uv.Position
19}
20
21// NewContext creates a new drawing context for the given screen.
22func NewContext(scr uv.Screen) *Context {
23	c := &Context{scr: scr}
24	c.Reset()
25	return c
26}
27
28// Reset resets the context to its default state.
29func (c *Context) Reset() {
30	c.style = uv.Style{}
31	c.link = uv.Link{}
32	c.pos = uv.Position{X: 0, Y: 0}
33}
34
35// SetStyle sets the style of the context.
36func (c *Context) SetStyle(style uv.Style) {
37	c.style = style
38}
39
40// WithStyle returns a copy of the context with the given style.
41func (c Context) WithStyle(style uv.Style) Context {
42	c.SetStyle(style)
43	return c
44}
45
46// SetLink sets the link of the context.
47func (c *Context) SetLink(link uv.Link) {
48	c.link = link
49}
50
51// WithLink returns a copy of the context with the given link.
52func (c Context) WithLink(link uv.Link) Context {
53	c.SetLink(link)
54	return c
55}
56
57// SetAttrs sets the attributes of the context.
58func (c *Context) SetAttrs(attrs uint8) {
59	c.style.Attrs = attrs
60}
61
62// WithAttrs returns a copy of the context with the given attributes.
63func (c Context) WithAttrs(attrs uint8) Context {
64	c.SetAttrs(attrs)
65	return c
66}
67
68// SetBackground sets the background color of the context. Use nil to reset to default.
69func (c *Context) SetBackground(bg color.Color) {
70	c.style.Bg = bg
71}
72
73// WithBackground returns a copy of the context with the given background color.
74func (c Context) WithBackground(bg color.Color) Context {
75	c.SetBackground(bg)
76	return c
77}
78
79// SetForeground sets the foreground color of the context. Use nil to reset to default.
80func (c *Context) SetForeground(fg color.Color) {
81	c.style.Fg = fg
82}
83
84// WithForeground returns a copy of the context with the given foreground color.
85func (c Context) WithForeground(fg color.Color) Context {
86	c.SetForeground(fg)
87	return c
88}
89
90// SetBold sets whether the text in the context should be bold.
91func (c *Context) SetBold(bold bool) {
92	if bold {
93		c.style.Attrs |= uv.AttrBold
94	} else {
95		c.style.Attrs &^= uv.AttrBold
96	}
97}
98
99// WithBold returns a copy of the context with the given bold attribute.
100func (c Context) WithBold(bold bool) Context {
101	c.SetBold(bold)
102	return c
103}
104
105// SetItalic sets whether the text in the context should be italic.
106func (c *Context) SetItalic(italic bool) {
107	if italic {
108		c.style.Attrs |= uv.AttrItalic
109	} else {
110		c.style.Attrs &^= uv.AttrItalic
111	}
112}
113
114// WithItalic returns a copy of the context with the given italic attribute.
115func (c Context) WithItalic(italic bool) Context {
116	c.SetItalic(italic)
117	return c
118}
119
120// SetStrikethrough sets whether the text in the context should be strikethrough.
121func (c *Context) SetStrikethrough(strikethrough bool) {
122	if strikethrough {
123		c.style.Attrs |= uv.AttrStrikethrough
124	} else {
125		c.style.Attrs &^= uv.AttrStrikethrough
126	}
127}
128
129// WithStrikethrough returns a copy of the context with the given strikethrough
130// attribute.
131func (c Context) WithStrikethrough(strikethrough bool) Context {
132	c.SetStrikethrough(strikethrough)
133	return c
134}
135
136// SetFaint sets whether the text in the context should be faint.
137func (c *Context) SetFaint(faint bool) {
138	if faint {
139		c.style.Attrs |= uv.AttrFaint
140	} else {
141		c.style.Attrs &^= uv.AttrFaint
142	}
143}
144
145// WithFaint returns a copy of the context with the given faint attribute.
146func (c Context) WithFaint(faint bool) Context {
147	c.SetFaint(faint)
148	return c
149}
150
151// SetBlink sets whether the text in the context should blink.
152func (c *Context) SetBlink(blink bool) {
153	if blink {
154		c.style.Attrs |= uv.AttrBlink
155	} else {
156		c.style.Attrs &^= uv.AttrBlink
157	}
158}
159
160// WithBlink returns a copy of the context with the given blink attribute.
161func (c Context) WithBlink(blink bool) Context {
162	c.SetBlink(blink)
163	return c
164}
165
166// SetReverse sets whether the text in the context should be reversed.
167func (c *Context) SetReverse(reverse bool) {
168	if reverse {
169		c.style.Attrs |= uv.AttrReverse
170	} else {
171		c.style.Attrs &^= uv.AttrReverse
172	}
173}
174
175// WithReverse returns a copy of the context with the given reverse attribute.
176func (c Context) WithReverse(reverse bool) Context {
177	c.SetReverse(reverse)
178	return c
179}
180
181// SetConceal sets whether the text in the context should be concealed.
182func (c *Context) SetConceal(conceal bool) {
183	if conceal {
184		c.style.Attrs |= uv.AttrConceal
185	} else {
186		c.style.Attrs &^= uv.AttrConceal
187	}
188}
189
190// WithConceal returns a copy of the context with the given conceal attribute.
191func (c Context) WithConceal(conceal bool) Context {
192	c.SetConceal(conceal)
193	return c
194}
195
196// SetUnderlineStyle sets the underline style of the context.
197func (c *Context) SetUnderlineStyle(u uv.Underline) {
198	c.style.Underline = u
199}
200
201// WithUnderlineStyle returns a copy of the context with the given underline style.
202func (c Context) WithUnderlineStyle(u uv.Underline) Context {
203	c.SetUnderlineStyle(u)
204	return c
205}
206
207// SetUnderline sets whether the text in the context should be underlined.
208//
209// This is a convenience method that sets the underline style to single or
210// none. It is equivalent to calling [Context.SetUnderlineStyle] with
211// [uv.UnderlineSingle] or [uv.UnderlineNone].
212func (c *Context) SetUnderline(underline bool) {
213	if underline {
214		c.SetUnderlineStyle(uv.UnderlineSingle)
215	} else {
216		c.SetUnderlineStyle(uv.UnderlineNone)
217	}
218}
219
220// WithUnderline returns a copy of the context with the given underline
221// attribute.
222func (c Context) WithUnderline(underline bool) Context {
223	c.SetUnderline(underline)
224	return c
225}
226
227// SetUnderlineColor sets the underline color of the context. Use nil to reset to default.
228func (c *Context) SetUnderlineColor(color color.Color) {
229	c.style.UnderlineColor = color
230}
231
232// WithUnderlineColor returns a copy of the context with the given underline color.
233func (c Context) WithUnderlineColor(color color.Color) Context {
234	c.SetUnderlineColor(color)
235	return c
236}
237
238// SetURL sets the URL link for the context. Use an empty string to reset.
239func (c *Context) SetURL(url string, params ...string) {
240	if url == "" {
241		c.link = uv.Link{}
242		return
243	}
244	c.link = uv.Link{
245		URL:    url,
246		Params: strings.Join(params, ":"),
247	}
248}
249
250// WithURL returns a copy of the context with the given URL link.
251func (c Context) WithURL(url string, params ...string) Context {
252	c.SetURL(url, params...)
253	return c
254}
255
256// Position returns the current position of the context.
257func (c *Context) Position() (x, y int) {
258	return c.pos.X, c.pos.Y
259}
260
261// SetPosition moves the current position of the context cursor to the given
262// coordinates.
263//
264// This is an alias for [Context.MoveTo].
265func (c *Context) SetPosition(x, y int) {
266	c.MoveTo(x, y)
267}
268
269// WithPosition returns a copy of the context with the given position.
270func (c Context) WithPosition(x, y int) Context {
271	c.MoveTo(x, y)
272	return c
273}
274
275// MoveTo moves the current position of the context cursor to the given
276// coordinates.
277func (c *Context) MoveTo(x, y int) {
278	c.pos.X = x
279	c.pos.Y = y
280}
281
282// Print prints the given string to the screen at the current position, updating
283// the position accordingly.
284func (c *Context) Print(v ...any) (int, error) {
285	return fmt.Fprint(c, v...)
286}
287
288// Println prints the given string to the screen at the current position, appending
289// a newline, and updating the position accordingly.
290func (c *Context) Println(v ...any) (int, error) {
291	return fmt.Fprintln(c, v...)
292}
293
294// Printf formats according to a format specifier and writes to the screen at the
295// current position, updating the position accordingly.
296func (c *Context) Printf(format string, a ...any) (int, error) {
297	return fmt.Fprintf(c, format, a...)
298}
299
300// DrawString draws the given string at the given position with the current
301// style and link, cropping the string when it reaches the edge of the screen.
302func (c *Context) DrawString(s string, x, y int) {
303	drawStringAt(c.scr, graphemes.FromString(s), x, y, c.style, c.link, false)
304}
305
306// DrawStringWrapped draws the given string at the given position with the current
307// style and link, wrapping the string when it reaches the edge of the screen.
308func (c *Context) DrawStringWrapped(s string, x, y int) {
309	drawStringAt(c.scr, graphemes.FromString(s), x, y, c.style, c.link, true)
310}
311
312// Write implements the [io.Writer] interface for the context, writing the
313// given byte slice to the screen at the current position, updating the
314// position accordingly.
315func (c *Context) Write(p []byte) (n int, err error) {
316	c.pos.X, c.pos.Y = drawStringAt(c.scr, graphemes.FromBytes(p), c.pos.X, c.pos.Y, c.style, c.link, true)
317	return len(p), nil
318}
319
320// WriteString implements the [io.StringWriter] interface for the context,
321// writing the given string to the screen at the current position, updating the
322// position accordingly.
323func (c *Context) WriteString(s string) (n int, err error) {
324	c.pos.X, c.pos.Y = drawStringAt(c.scr, graphemes.FromString(s), c.pos.X, c.pos.Y, c.style, c.link, true)
325	return len(s), nil
326}
327
328func drawStringAt[T []byte | string](scr uv.Screen, grs *graphemes.Iterator[T], x, y int, style uv.Style, link uv.Link, wrap bool) (int, int) {
329	bounds := scr.Bounds()
330	bounds.Max.X -= bounds.Min.X
331	bounds.Max.Y -= bounds.Min.Y
332	bounds.Min.X = 0
333	bounds.Min.Y = 0
334	pos := uv.Pos(x, y)
335	if !pos.In(bounds) {
336		return x, y
337	}
338
339	wm := scr.WidthMethod()
340	for grs.Next() {
341		gr := string(grs.Value())
342		switch gr {
343		case "\n":
344			x = bounds.Min.X
345			y++
346			continue
347		}
348
349		w := wm.StringWidth(gr)
350		pos := uv.Pos(x, y)
351		if x+w > bounds.Max.X {
352			if wrap {
353				x = bounds.Min.X
354				y++
355				pos = uv.Pos(x, y)
356			} else {
357				break
358			}
359		}
360		if !pos.In(bounds) {
361			break
362		}
363
364		scr.SetCell(x, y, &uv.Cell{
365			Content: gr,
366			Width:   w,
367			Style:   style,
368			Link:    link,
369		})
370
371		x += w
372		if wrap && x >= bounds.Max.X {
373			x = bounds.Min.X
374			y++
375		}
376	}
377
378	return x, y
379}