1= tcell tutorial
2
3tcell provides a low-level, portable API for building terminal-based programs.
4A https://en.wikipedia.org/wiki/Terminal_emulator[terminal emulator] is used to
5interact with such a program.
6
7Applications typically initialize a screen and enter an event loop, then
8finalize the screen before exiting.
9
10Application frameworks such as https://github.com/rivo/tview[tview] and
11https://gitlab.com/tslocum/cview[cview] provide widgets and additional features.
12
13== Resize events
14
15Applications receive an event of type `EventResize` when they are first initialized and each time the terminal is resized.
16The new size is available as `Size`.
17
18[source,go]
19----
20switch ev := ev.(type) {
21case *tcell.EventResize:
22	w, h := ev.Size()
23	logMessage(fmt.Sprintf("Resized to %dx%d", w, h))
24}
25----
26
27== Key events
28
29When a key is pressed, applications receive an event of type `EventKey`.
30This event describes the modifier keys pressed (if any) and the pressed key or rune.
31
32When a rune key is pressed, an event with its `Key` set to `KeyRune` is dispatched.
33
34When a non-rune key is pressed, it is available as the `Key` of the event.
35
36[source,go]
37----
38switch ev := ev.(type) {
39case *tcell.EventKey:
40    mod, key, ch := ev.Mod(), ev.Key(), ev.Rune()
41    logMessage(fmt.Sprintf("EventKey Modifiers: %d Key: %d Rune: %d", mod, key, ch))
42}
43----
44
45=== Key event restrictions
46
47Terminal-based programs have less visibility into keyboard activity than graphical applications.
48
49When a key is pressed and held, additional key press events are sent by the terminal emulator.
50The rate of these repeated events depends on the emulator's configuration.
51Key release events are not available.
52
53It is not possible to distinguish runes typed while holding shift and runes typed using caps lock.
54Capital letters are reported without the Shift modifier.
55
56=== Key event handling library
57
58https://gitlab.com/tslocum/cbind[cbind] provides key event encoding and decoding
59to and from human-readable strings. It also provides keybinding-based input handling.
60
61== Mouse events
62
63Applications receive an event of type `EventMouse` when the mouse moves, or a mouse button is pressed or released.
64Mouse events are only delivered if
65`EnableMouse` has been called.
66
67The mouse buttons being pressed (if any) are available as `Buttons`, and the position of the mouse is available as `Position`.
68
69[source,go]
70----
71switch ev := ev.(type) {
72case *tcell.EventMouse:
73	mod := ev.Modifiers()
74	btns := ev.Buttons()
75	x, y := ev.Position()
76	logMessage(fmt.Sprintf("EventMouse Modifiers: %d Buttons: %d Position: %d,%d", mod, btns, x, y))
77}
78----
79
80=== Mouse buttons
81
82[cols=3*,options=header]
83|===
84
85|Identifier
86|Alias
87|Description
88
89|Button1
90|ButtonPrimary
91|Left button
92
93|Button2
94|ButtonSecondary
95|Right button
96
97|Button3
98|ButtonMiddle
99|Middle button
100
101|Button4
102|
103|Side button (thumb/next)
104
105|Button5
106|
107|Side button (thumb/prev)
108
109|===
110
111== Usage
112
113To create a tcell application, first initialize a screen to hold it.
114
115[source,go]
116----
117// Initialize screen
118s, err := tcell.NewScreen()
119if err != nil {
120	log.Fatalf("%+v", err)
121}
122if err := s.Init(); err != nil {
123	log.Fatalf("%+v", err)
124}
125
126// Set default text style
127defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
128s.SetStyle(defStyle)
129
130// Clear screen
131s.Clear()
132----
133
134Text may be drawn on the screen using `SetContent`.
135
136[source,go]
137----
138s.SetContent(0, 0, 'H', nil, defStyle)
139s.SetContent(1, 0, 'i', nil, defStyle)
140s.SetContent(2, 0, '!', nil, defStyle)
141----
142
143To draw text more easily, define a render function.
144
145[source,go]
146----
147func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
148	row := y1
149	col := x1
150	for _, r := range []rune(text) {
151		s.SetContent(col, row, r, nil, style)
152		col++
153		if col >= x2 {
154			row++
155			col = x1
156		}
157		if row > y2 {
158			break
159		}
160	}
161}
162----
163
164Lastly, define an event loop to handle user input and update application state.
165
166[source,go]
167----
168quit := func() {
169    s.Fini()
170    os.Exit(0)
171}
172for {
173    // Update screen
174    s.Show()
175
176    // Poll event
177    ev := s.PollEvent()
178
179    // Process event
180    switch ev := ev.(type) {
181    case *tcell.EventResize:
182        s.Sync()
183    case *tcell.EventKey:
184        if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
185            quit()
186        }
187    }
188}
189----
190
191== Demo application
192
193The following demonstrates how to initialize a screen, draw text/graphics and handle user input.
194
195[source,go]
196----
197package main
198
199import (
200	"fmt"
201	"log"
202	"os"
203
204	"github.com/gdamore/tcell/v2"
205)
206
207func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
208	row := y1
209	col := x1
210	for _, r := range []rune(text) {
211		s.SetContent(col, row, r, nil, style)
212		col++
213		if col >= x2 {
214			row++
215			col = x1
216		}
217		if row > y2 {
218			break
219		}
220	}
221}
222
223func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
224	if y2 < y1 {
225		y1, y2 = y2, y1
226	}
227	if x2 < x1 {
228		x1, x2 = x2, x1
229	}
230
231	// Fill background
232	for row := y1; row <= y2; row++ {
233		for col := x1; col <= x2; col++ {
234			s.SetContent(col, row, ' ', nil, style)
235		}
236	}
237
238	// Draw borders
239	for col := x1; col <= x2; col++ {
240		s.SetContent(col, y1, tcell.RuneHLine, nil, style)
241		s.SetContent(col, y2, tcell.RuneHLine, nil, style)
242	}
243	for row := y1 + 1; row < y2; row++ {
244		s.SetContent(x1, row, tcell.RuneVLine, nil, style)
245		s.SetContent(x2, row, tcell.RuneVLine, nil, style)
246	}
247
248	// Only draw corners if necessary
249	if y1 != y2 && x1 != x2 {
250		s.SetContent(x1, y1, tcell.RuneULCorner, nil, style)
251		s.SetContent(x2, y1, tcell.RuneURCorner, nil, style)
252		s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style)
253		s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style)
254	}
255
256	drawText(s, x1+1, y1+1, x2-1, y2-1, style, text)
257}
258
259func main() {
260	defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
261	boxStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorPurple)
262
263	// Initialize screen
264	s, err := tcell.NewScreen()
265	if err != nil {
266		log.Fatalf("%+v", err)
267	}
268	if err := s.Init(); err != nil {
269		log.Fatalf("%+v", err)
270	}
271	s.SetStyle(defStyle)
272	s.EnableMouse()
273	s.EnablePaste()
274	s.Clear()
275
276	// Draw initial boxes
277	drawBox(s, 1, 1, 42, 7, boxStyle, "Click and drag to draw a box")
278	drawBox(s, 5, 9, 32, 14, boxStyle, "Press C to reset")
279
280	// Event loop
281	ox, oy := -1, -1
282	quit := func() {
283		s.Fini()
284		os.Exit(0)
285	}
286	for {
287		// Update screen
288		s.Show()
289
290		// Poll event
291		ev := s.PollEvent()
292
293		// Process event
294		switch ev := ev.(type) {
295		case *tcell.EventResize:
296			s.Sync()
297		case *tcell.EventKey:
298			if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
299				quit()
300			} else if ev.Key() == tcell.KeyCtrlL {
301				s.Sync()
302			} else if ev.Rune() == 'C' || ev.Rune() == 'c' {
303				s.Clear()
304			}
305		case *tcell.EventMouse:
306			x, y := ev.Position()
307			button := ev.Buttons()
308			// Only process button events, not wheel events
309			button &= tcell.ButtonMask(0xff)
310
311			if button != tcell.ButtonNone && ox < 0 {
312				ox, oy = x, y
313			}
314			switch ev.Buttons() {
315			case tcell.ButtonNone:
316				if ox >= 0 {
317					label := fmt.Sprintf("%d,%d to %d,%d", ox, oy, x, y)
318					drawBox(s, ox, oy, x, y, boxStyle, label)
319					ox, oy = -1, -1
320				}
321			}
322		}
323	}
324}
325----
326