tcell/TUTORIAL.adoc

326 lines
7.0 KiB
Plaintext
Raw Normal View History

2020-10-20 04:30:53 +08:00
= tcell tutorial
tcell provides a low-level, portable API for building terminal-based programs.
A https://en.wikipedia.org/wiki/Terminal_emulator[terminal emulator] is used to
interact with such a program.
Applications typically initialize a screen and enter an event loop, then
finalize the screen before exiting.
Application frameworks such as https://github.com/rivo/tview[tview] and
https://gitlab.com/tslocum/cview[cview] provide widgets and additional features.
== Resize events
Applications receive an event of type `EventResize` when they are first initialized and each time the terminal is resized.
The new size is available as `Size`.
[source,go]
----
switch ev := ev.(type) {
case *tcell.EventResize:
w, h := ev.Size()
logMessage(fmt.Sprintf("Resized to %dx%d", w, h))
}
----
== Key events
When a key is pressed, applications receive an event of type `EventKey`.
This event describes the modifier keys pressed (if any) and the pressed key or rune.
When a rune key is pressed, an event with its `Key` set to `KeyRune` is dispatched.
When a non-rune key is pressed, it is available as the `Key` of the event.
[source,go]
----
switch ev := ev.(type) {
case *tcell.EventKey:
mod, key, ch := ev.Mod(), ev.Key(), ev.Rune()
logMessage(fmt.Sprintf("EventKey Modifiers: %d Key: %d Rune: %d", mod, key, ch))
}
----
=== Key event restrictions
Terminal-based programs have less visibility into keyboard activity than graphical applications.
When a key is pressed and held, additional key press events are sent by the terminal emulator.
The rate of these repeated events depends on the emulator's configuration.
Key release events are not available.
It is not possible to distinguish runes typed while holding shift and runes typed using caps lock.
Capital letters are reported without the Shift modifier.
=== Key event handling library
https://gitlab.com/tslocum/cbind[cbind] provides key event encoding and decoding
to and from human-readable strings. It also provides keybinding-based input handling.
== Mouse events
Applications receive an event of type `EventMouse` when the mouse moves, or a mouse button is pressed or released.
Mouse events are only delivered if
`EnableMouse` has been called.
The mouse buttons being pressed (if any) are available as `Buttons`, and the position of the mouse is available as `Position`.
[source,go]
----
switch ev := ev.(type) {
case *tcell.EventMouse:
mod := ev.Modifiers()
btns := ev.Buttons()
x, y := ev.Position()
logMessage(fmt.Sprintf("EventMouse Modifiers: %d Buttons: %d Position: %d,%d", mod, btns, x, y))
}
----
=== Mouse buttons
[cols=3*,options=header]
|===
|Identifier
|Alias
|Description
|Button1
|ButtonPrimary
|Left button
|Button2
|ButtonSecondary
|Right button
|Button3
|ButtonMiddle
|Middle button
|Button4
|
|Side button (thumb/next)
|Button5
|
|Side button (thumb/prev)
|===
== Usage
To create a tcell application, first initialize a screen to hold it.
[source,go]
----
// Initialize screen
s, err := tcell.NewScreen()
if err != nil {
log.Fatalf("%+v", err)
}
if err := s.Init(); err != nil {
log.Fatalf("%+v", err)
}
// Set default text style
defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
s.SetStyle(defStyle)
// Clear screen
s.Clear()
----
Text may be drawn on the screen using `SetContent`.
[source,go]
----
s.SetContent(0, 0, 'H', nil, defStyle)
s.SetContent(1, 0, 'i', nil, defStyle)
s.SetContent(2, 0, '!', nil, defStyle)
----
To draw text more easily, define a render function.
[source,go]
----
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
row := y1
col := x1
for _, r := range []rune(text) {
s.SetContent(col, row, r, nil, style)
col++
if col >= x2 {
row++
col = x1
}
if row > y2 {
break
}
}
}
----
Lastly, define an event loop to handle user input and update application state.
[source,go]
----
quit := func() {
s.Fini()
os.Exit(0)
}
for {
// Update screen
s.Show()
// Poll event
ev := s.PollEvent()
// Process event
switch ev := ev.(type) {
case *tcell.EventResize:
s.Sync()
case *tcell.EventKey:
if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
quit()
}
}
}
----
== Demo application
The following demonstrates how to initialize a screen, draw text/graphics and handle user input.
[source,go]
----
package main
import (
"fmt"
"log"
"os"
"github.com/gdamore/tcell/v2"
)
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
row := y1
col := x1
for _, r := range []rune(text) {
s.SetContent(col, row, r, nil, style)
col++
if col >= x2 {
row++
col = x1
}
if row > y2 {
break
}
}
}
func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
if y2 < y1 {
y1, y2 = y2, y1
}
if x2 < x1 {
x1, x2 = x2, x1
}
// Fill background
for row := y1; row <= y2; row++ {
for col := x1; col <= x2; col++ {
s.SetContent(col, row, ' ', nil, style)
}
}
// Draw borders
for col := x1; col <= x2; col++ {
s.SetContent(col, y1, tcell.RuneHLine, nil, style)
s.SetContent(col, y2, tcell.RuneHLine, nil, style)
}
for row := y1 + 1; row < y2; row++ {
s.SetContent(x1, row, tcell.RuneVLine, nil, style)
s.SetContent(x2, row, tcell.RuneVLine, nil, style)
}
// Only draw corners if necessary
if y1 != y2 && x1 != x2 {
s.SetContent(x1, y1, tcell.RuneULCorner, nil, style)
s.SetContent(x2, y1, tcell.RuneURCorner, nil, style)
s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style)
s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style)
}
drawText(s, x1+1, y1+1, x2-1, y2-1, style, text)
}
func main() {
defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
boxStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorPurple)
// Initialize screen
s, err := tcell.NewScreen()
if err != nil {
log.Fatalf("%+v", err)
}
if err := s.Init(); err != nil {
log.Fatalf("%+v", err)
}
s.SetStyle(defStyle)
s.EnableMouse()
s.EnablePaste()
s.Clear()
// Draw initial boxes
drawBox(s, 1, 1, 42, 7, boxStyle, "Click and drag to draw a box")
drawBox(s, 5, 9, 32, 14, boxStyle, "Press C to reset")
// Event loop
ox, oy := -1, -1
quit := func() {
s.Fini()
os.Exit(0)
}
for {
// Update screen
s.Show()
// Poll event
ev := s.PollEvent()
// Process event
switch ev := ev.(type) {
case *tcell.EventResize:
s.Sync()
case *tcell.EventKey:
if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
quit()
} else if ev.Key() == tcell.KeyCtrlL {
s.Sync()
} else if ev.Rune() == 'C' || ev.Rune() == 'c' {
s.Clear()
}
case *tcell.EventMouse:
x, y := ev.Position()
button := ev.Buttons()
// Only process button events, not wheel events
button &= tcell.ButtonMask(0xff)
if button != tcell.ButtonNone && ox < 0 {
ox, oy = x, y
}
switch ev.Buttons() {
case tcell.ButtonNone:
if ox >= 0 {
label := fmt.Sprintf("%d,%d to %d,%d", ox, oy, x, y)
drawBox(s, ox, oy, x, y, boxStyle, label)
ox, oy = -1, -1
}
}
}
}
}
----