gemini-browser

A text-based gemini browser
git clone git://git.laack.co/gemini-browser.git
Log | Files | Refs | README

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