okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
524a660
upstream
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 { 14scr uv.Screen 15 16style uv.Style 17link uv.Link 18pos uv.Position 19} 20 21// NewContext creates a new drawing context for the given screen. 22func NewContext (scr uv.Screen )* Context { 23c := & Context {scr :scr } 24c .Reset () 25return c 26} 27 28// Reset resets the context to its default state. 29func (c * Context )Reset () { 30c .style = uv.Style {} 31c .link = uv.Link {} 32c .pos = uv.Position {X :0 ,Y :0 } 33} 34 35// SetStyle sets the style of the context. 36func (c * Context )SetStyle (style uv.Style ) { 37c .style = style 38} 39 40// WithStyle returns a copy of the context with the given style. 41func (c Context )WithStyle (style uv.Style )Context { 42c .SetStyle (style ) 43return c 44} 45 46// SetLink sets the link of the context. 47func (c * Context )SetLink (link uv.Link ) { 48c .link = link 49} 50 51// WithLink returns a copy of the context with the given link. 52func (c Context )WithLink (link uv.Link )Context { 53c .SetLink (link ) 54return c 55} 56 57// SetAttrs sets the attributes of the context. 58func (c * Context )SetAttrs (attrs uint8 ) { 59c .style .Attrs = attrs 60} 61 62// WithAttrs returns a copy of the context with the given attributes. 63func (c Context )WithAttrs (attrs uint8 )Context { 64c .SetAttrs (attrs ) 65return 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 ) { 70c .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 { 75c .SetBackground (bg ) 76return 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 ) { 81c .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 { 86c .SetForeground (fg ) 87return c 88} 89 90// SetBold sets whether the text in the context should be bold. 91func (c * Context )SetBold (bold bool ) { 92if bold { 93c .style .Attrs |= uv .AttrBold 94 }else { 95c .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 { 101c .SetBold (bold ) 102return c 103} 104 105// SetItalic sets whether the text in the context should be italic. 106func (c * Context )SetItalic (italic bool ) { 107if italic { 108c .style .Attrs |= uv .AttrItalic 109 }else { 110c .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 { 116c .SetItalic (italic ) 117return c 118} 119 120// SetStrikethrough sets whether the text in the context should be strikethrough. 121func (c * Context )SetStrikethrough (strikethrough bool ) { 122if strikethrough { 123c .style .Attrs |= uv .AttrStrikethrough 124 }else { 125c .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 { 132c .SetStrikethrough (strikethrough ) 133return c 134} 135 136// SetFaint sets whether the text in the context should be faint. 137func (c * Context )SetFaint (faint bool ) { 138if faint { 139c .style .Attrs |= uv .AttrFaint 140 }else { 141c .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 { 147c .SetFaint (faint ) 148return c 149} 150 151// SetBlink sets whether the text in the context should blink. 152func (c * Context )SetBlink (blink bool ) { 153if blink { 154c .style .Attrs |= uv .AttrBlink 155 }else { 156c .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 { 162c .SetBlink (blink ) 163return c 164} 165 166// SetReverse sets whether the text in the context should be reversed. 167func (c * Context )SetReverse (reverse bool ) { 168if reverse { 169c .style .Attrs |= uv .AttrReverse 170 }else { 171c .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 { 177c .SetReverse (reverse ) 178return c 179} 180 181// SetConceal sets whether the text in the context should be concealed. 182func (c * Context )SetConceal (conceal bool ) { 183if conceal { 184c .style .Attrs |= uv .AttrConceal 185 }else { 186c .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 { 192c .SetConceal (conceal ) 193return c 194} 195 196// SetUnderlineStyle sets the underline style of the context. 197func (c * Context )SetUnderlineStyle (u uv.Underline ) { 198c .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 { 203c .SetUnderlineStyle (u ) 204return 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 ) { 213if underline { 214c .SetUnderlineStyle (uv .UnderlineSingle ) 215 }else { 216c .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 { 223c .SetUnderline (underline ) 224return 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 ) { 229c .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 { 234c .SetUnderlineColor (color ) 235return 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 ) { 240if url == "" { 241c .link = uv.Link {} 242return 243 } 244c .link = uv.Link { 245URL :url , 246Params :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 { 252c .SetURL (url ,params ... ) 253return c 254} 255 256// Position returns the current position of the context. 257func (c * Context )Position () (x ,y int ) { 258return 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 ) { 266c .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 { 271c .MoveTo (x ,y ) 272return 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 ) { 278c .pos .X = x 279c .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 )v ... any ) (int ,error ) { 285return 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 ) { 291return 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 ) { 297return 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 ) { 303drawStringAt (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 ) { 309drawStringAt (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 ) { 316c .pos .X ,c .pos .Y = drawStringAt (c .scr ,graphemes .FromBytes (p ),c .pos .X ,c .pos .Y ,c .style ,c .link ,true ) 317return 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 ) { 324c .pos .X ,c .pos .Y = drawStringAt (c .scr ,graphemes .FromString (s ),c .pos .X ,c .pos .Y ,c .style ,c .link ,true ) 325return 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 ) { 329bounds := scr .Bounds () 330bounds .Max .X -= bounds .Min .X 331bounds .Max .Y -= bounds .Min .Y 332bounds .Min .X = 0 333bounds .Min .Y = 0 334pos := uv .Pos (x ,y ) 335if ! pos .In (bounds ) { 336return x ,y 337 } 338 339wm := scr .WidthMethod () 340for grs .Next () { 341gr := string (grs .Value ()) 342switch gr { 343case "\n" : 344x = bounds .Min .X 345y ++ 346continue 347 } 348 349w := wm .StringWidth (gr ) 350pos := uv .Pos (x ,y ) 351if x + w > bounds .Max .X { 352if wrap { 353x = bounds .Min .X 354y ++ 355pos = uv .Pos (x ,y ) 356 }else { 357break 358 } 359 } 360if ! pos .In (bounds ) { 361break 362 } 363 364scr .SetCell (x ,y ,& uv.Cell { 365Content :gr , 366Width :w , 367Style :style , 368Link :link , 369 }) 370 371x += w 372if wrap && x >= bounds .Max .X { 373x = bounds .Min .X 374y ++ 375 } 376 } 377 378return x ,y 379}