okami.mom/ultraviolet

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

Ayman Bagabaschore(examples): update terminal initialization in examples03f41d2

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