Skip to content

Commit 6314995

Browse files
tslocumgdamore
authored andcommitted
Add tutorial
Resolves #163.
1 parent 1057d55 commit 6314995

File tree

1 file changed

+325
-0
lines changed

1 file changed

+325
-0
lines changed

TUTORIAL.adoc

+325
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
= tcell tutorial
2+
3+
tcell provides a low-level, portable API for building terminal-based programs.
4+
A https://en.wikipedia.org/wiki/Terminal_emulator[terminal emulator] is used to
5+
interact with such a program.
6+
7+
Applications typically initialize a screen and enter an event loop, then
8+
finalize the screen before exiting.
9+
10+
Application frameworks such as https://github.com/rivo/tview[tview] and
11+
https://gitlab.com/tslocum/cview[cview] provide widgets and additional features.
12+
13+
== Resize events
14+
15+
Applications receive an event of type `EventResize` when they are first initialized and each time the terminal is resized.
16+
The new size is available as `Size`.
17+
18+
[source,go]
19+
----
20+
switch ev := ev.(type) {
21+
case *tcell.EventResize:
22+
w, h := ev.Size()
23+
logMessage(fmt.Sprintf("Resized to %dx%d", w, h))
24+
}
25+
----
26+
27+
== Key events
28+
29+
When a key is pressed, applications receive an event of type `EventKey`.
30+
This event describes the modifier keys pressed (if any) and the pressed key or rune.
31+
32+
When a rune key is pressed, an event with its `Key` set to `KeyRune` is dispatched.
33+
34+
When a non-rune key is pressed, it is available as the `Key` of the event.
35+
36+
[source,go]
37+
----
38+
switch ev := ev.(type) {
39+
case *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+
47+
Terminal-based programs have less visibility into keyboard activity than graphical applications.
48+
49+
When a key is pressed and held, additional key press events are sent by the terminal emulator.
50+
The rate of these repeated events depends on the emulator's configuration.
51+
Key release events are not available.
52+
53+
It is not possible to distinguish runes typed while holding shift and runes typed using caps lock.
54+
Capital letters are reported without the Shift modifier.
55+
56+
=== Key event handling library
57+
58+
https://gitlab.com/tslocum/cbind[cbind] provides key event encoding and decoding
59+
to and from human-readable strings. It also provides keybinding-based input handling.
60+
61+
== Mouse events
62+
63+
Applications receive an event of type `EventMouse` when the mouse moves, or a mouse button is pressed or released.
64+
Mouse events are only delivered if
65+
`EnableMouse` has been called.
66+
67+
The 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+
----
71+
switch ev := ev.(type) {
72+
case *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+
113+
To create a tcell application, first initialize a screen to hold it.
114+
115+
[source,go]
116+
----
117+
// Initialize screen
118+
s, err := tcell.NewScreen()
119+
if err != nil {
120+
log.Fatalf("%+v", err)
121+
}
122+
if err := s.Init(); err != nil {
123+
log.Fatalf("%+v", err)
124+
}
125+
126+
// Set default text style
127+
defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
128+
s.SetStyle(defStyle)
129+
130+
// Clear screen
131+
s.Clear()
132+
----
133+
134+
Text may be drawn on the screen using `SetContent`.
135+
136+
[source,go]
137+
----
138+
s.SetContent(0, 0, 'H', nil, defStyle)
139+
s.SetContent(1, 0, 'i', nil, defStyle)
140+
s.SetContent(2, 0, '!', nil, defStyle)
141+
----
142+
143+
To draw text more easily, define a render function.
144+
145+
[source,go]
146+
----
147+
func 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+
164+
Lastly, define an event loop to handle user input and update application state.
165+
166+
[source,go]
167+
----
168+
quit := func() {
169+
s.Fini()
170+
os.Exit(0)
171+
}
172+
for {
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+
193+
The following demonstrates how to initialize a screen, draw text/graphics and handle user input.
194+
195+
[source,go]
196+
----
197+
package main
198+
199+
import (
200+
"fmt"
201+
"log"
202+
"os"
203+
204+
"github.com/gdamore/tcell/v2"
205+
)
206+
207+
func 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+
223+
func 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+
259+
func 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+
----

0 commit comments

Comments
 (0)