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