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