okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
03f41d2
upstream
1package main 2 3import ( 4"context" 5"log" 6"unicode" 7"unicode/utf8" 8 9 uv"github.com/charmbracelet/ultraviolet" 10"github.com/charmbracelet/ultraviolet/screen" 11"github.com/charmbracelet/x/ansi" 12"github.com/mattn/go-runewidth" 13) 14 15func main () { 16t := uv .DefaultTerminal () 17scr := t .Screen () 18 19// Start in altscreen mode 20scr .EnterAltScreen () 21 22if err := t .Start ();err != nil { 23log .Fatalf ("failed to start program: %v" ,err ) 24 } 25 26defer t .Stop () 27 28modes := []ansi.Mode { 29ansi .ButtonEventMouseMode , 30ansi .SgrExtMouseMode , 31ansi .FocusEventMode , 32 } 33 34scr .WriteString (ansi .SetMode (modes ... )) 35 36// Listen for input and mouse events. 37ctx ,cancel := context .WithCancel (context .Background ()) 38defer cancel () 39 40const help = `Welcome to Draw Example! 41 42Use the mouse to draw on the screen. 43Press ctrl+c to exit. 44Press esc to clear the screen. 45Press alt+esc to reset the pen character, color, and the screen. 46Press 0-9 to set the foreground color. 47Press any other key to set the pen character. 48Press ctrl+h for this help message. 49 50Press any key to continue...` 51 52helpComp := uv .NewStyledString (help ) 53helpArea := helpComp .Bounds () 54helpW ,helpH := helpArea .Dx (),helpArea .Dy () 55 56var prevHelpBuf * uv.Buffer 57showingHelp := true 58displayHelp := func (show bool ) { 59bounds := scr .Bounds () 60midX ,midY := bounds .Dx ()/ 2 ,bounds .Dy ()/ 2 61x ,y := midX - helpW / 2 ,midY - helpH / 2 62midArea := uv .Rect (x ,y ,helpW ,helpH ) 63if show { 64// Save the area under the help to restore it later. 65prevHelpBuf = screen .CloneArea (scr ,midArea ) 66helpComp .Draw (scr ,midArea ) 67 }else if prevHelpBuf != nil { 68// Restore saved area under the help. 69prevHelpBuf .Draw (scr ,midArea ) 70 } 71scr .Render () 72scr .Flush () 73 } 74 75clearScreen := func () { 76screen .Clear (scr ) 77scr .Render () 78scr .Flush () 79 } 80 81// Display first frame. 82displayHelp (showingHelp ) 83 84const defaultChar = "█" 85pen := uv .EmptyCell 86pen .Content = defaultChar 87draw := func (ev uv.MouseEvent ) { 88m := ev .Mouse () 89cur := scr .CellAt (m .X ,m .Y ) 90if cur == nil { 91// Position out of bounds. 92return 93 } 94 95if cur .IsZero ()&& pen .Width == 1 { 96// Find the previous wide cell. 97var wide * uv.Cell 98var wideX ,wideY int 99for i := 1 ;i < 5 && m .X - i >= 0 ;i ++ { 100wide = scr .CellAt (m .X - i ,m .Y ) 101if wide != nil && ! wide .IsZero ()&& wide .Width > 1 { 102wideX ,wideY = m .X - i ,m .Y 103break 104 } 105 } 106 107if wide != nil { 108// Found a wide cell, make all cells blank. 109wc := * wide 110wc .Empty () 111scr .SetCell (wideX ,wideY ,& wc ) 112 } 113 } 114 115// Can we fit the cell? 116fit := true 117if w := pen .Width ;w > 1 { 118if cur .IsZero ()|| cur .Width > 1 { 119fit = false 120 }else { 121for i := 1 ;i < w ;i ++ { 122cur = scr .CellAt (m .X + i ,m .Y ) 123if cur == nil || cur .IsZero ()|| cur .Width > 1 { 124// Position out of bounds or not empty. 125fit = false 126break 127 } 128 } 129 } 130 } 131if ! fit { 132// Cell is too wide, ignore it. 133return 134 } 135 136scr .SetCell (m .X ,m .Y ,& pen ) 137scr .Render () 138scr .Flush () 139 } 140displayHelp (showingHelp ) 141 142LOOP: 143for { 144select { 145case <- ctx .Done (): 146break LOOP 147case ev := <- t .Events (): 148switch ev := ev .(type ) { 149case uv.WindowSizeEvent : 150if showingHelp { 151displayHelp (false ) 152 } 153scr .Resize (ev .Width ,ev .Height ) 154if showingHelp { 155displayHelp (showingHelp ) 156 } 157case uv.KeyPressEvent : 158if showingHelp { 159showingHelp = false 160displayHelp (showingHelp ) 161break 162 } 163switch { 164case ev .MatchString ("ctrl+c" ): 165cancel () 166case ev .MatchString ("alt+esc" ): 167pen .Style = uv.Style {} 168pen .Content = defaultChar 169fallthrough 170case ev .MatchString ("esc" ): 171clearScreen () 172case ev .MatchString ("ctrl+h" ): 173showingHelp = true 174displayHelp (showingHelp ) 175default : 176text := ev .Text 177if len (text )== 0 { 178break 179 } 180r ,rw := utf8 .DecodeRuneInString (text ) 181if rw == 1 && unicode .IsDigit (r ) { 182pen .Style .Fg = ansi .Black + ansi .BasicColor (r - '0' ) 183break 184 } 185pen .Content = text 186pen .Width = runewidth .RuneWidth (r ) 187 } 188case uv.MouseClickEvent : 189if showingHelp { 190break 191 } 192draw (ev ) 193case uv.MouseMotionEvent : 194if showingHelp || ev .Button == uv .MouseNone { 195break 196 } 197draw (ev ) 198 } 199 } 200 } 201 202scr .WriteString (ansi .ResetMode (modes ... )) 203}