8 changed files with 368 additions and 13 deletions
@ -1,7 +1,31 @@
@@ -1,7 +1,31 @@
|
||||
--- |
||||
title: "Gotcha - Overiting a shared backing array when calling append" |
||||
title: "Gotcha - Up-ending a backing array" |
||||
date: 2019-09-26T21:42:18-07:00 |
||||
categories: [gotcha] |
||||
draft: true |
||||
--- |
||||
|
||||
Test |
||||
To give better context to the problem being presented in this article, we must go over how slices work. |
||||
|
||||
A slice value consists of three parts: a pointer to a backing array, a length and a capacity. |
||||
|
||||
Excerpt of **runtime/slice.go** from the standard library: |
||||
|
||||
```go |
||||
|
||||
type slice struct { |
||||
array unsafe.Pointer |
||||
len int |
||||
cap int |
||||
} |
||||
``` |
||||
|
||||
When calling append on a slice whose length is the same as its capacity, a new backing array of greater size is created. The previous backing array's contents are copied. |
||||
|
||||
A range of elements from a slice may be selected like so: |
||||
|
||||
```go |
||||
bar := foo[4:7] |
||||
``` |
||||
|
||||
When calling append on such a slice, |
||||
|
@ -0,0 +1,321 @@
@@ -0,0 +1,321 @@
|
||||
--- |
||||
title: "tview and you - Creating Rich Terminal User Interfaces" |
||||
date: 2019-11-08T01:42:18-07:00 |
||||
categories: [tutorial] |
||||
--- |
||||
|
||||
<a href="https://github.com/rivo/tview/tree/master/demos/presentation"><img src="https://raw.githubusercontent.com/rivo/tview/master/tview.gif" width=640" height="446" border="0" title="tview presentation demo"></a> |
||||
|
||||
This is an introduction to using [tview](https://github.com/rivo/tview) to create rich terminal-based user interfaces with Go. |
||||
|
||||
## Contents |
||||
|
||||
* [Primitives](#primitives) |
||||
* [Widgets](#widgets) |
||||
* [Elements](#widget-elements) |
||||
* [Containers](#widget-containers) |
||||
* [Thread Safety](#thread-safety) |
||||
* [Example Application](#example-application) |
||||
|
||||
# Primitives |
||||
|
||||
The [Primitive](https://godoc.org/github.com/rivo/tview#Primitive) interface is as follows: |
||||
|
||||
```go |
||||
type Primitive interface { |
||||
Draw(screen tcell.Screen) |
||||
GetRect() (int, int, int, int) |
||||
SetRect(x, y, width, height int) |
||||
InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) |
||||
Focus(delegate func(p Primitive)) |
||||
Blur() |
||||
GetFocusable() Focusable |
||||
} |
||||
``` |
||||
|
||||
[Box](https://godoc.org/github.com/rivo/tview#Box) is the only primitive implemented. |
||||
It has a size, padding amount, optional border, optional title and background color. |
||||
|
||||
# Widgets |
||||
|
||||
Widgets are structs which embed a [Box](https://godoc.org/github.com/rivo/tview#Box) and build upon it. |
||||
|
||||
From the [TextView](https://godoc.org/github.com/rivo/tview#TextView) declaration: |
||||
|
||||
```go |
||||
type TextView struct { |
||||
*Box |
||||
|
||||
// The text buffer. |
||||
buffer []string |
||||
|
||||
// The text alignment, one of AlignLeft, AlignCenter, or AlignRight. |
||||
align int |
||||
|
||||
// ... |
||||
} |
||||
``` |
||||
|
||||
Some widgets allow nesting other widgets within them, such as [Grid](https://godoc.org/github.com/rivo/tview#Grid). |
||||
|
||||
Most widget commands may be chained together: |
||||
|
||||
```go |
||||
nameLabel := tview.NewTextView(). |
||||
SetTextAlign(tview.AlignRight). |
||||
SetDynamicColors(true). |
||||
SetWrap(true). |
||||
SetWordWrap(true). |
||||
SetText("Please enter your name:") |
||||
``` |
||||
|
||||
New widgets may be defined, as documented in the [Primitive demo](https://github.com/rivo/tview/tree/master/demos/primitive). |
||||
|
||||
## Widget Elements |
||||
|
||||
--- |
||||
|
||||
[**Button**](https://godoc.org/github.com/rivo/tview#Button) is a labeled box that triggers an action when selected. |
||||
|
||||
```go |
||||
button := tview.NewButton("OK").SetSelectedFunc(func() { |
||||
pressedOK() |
||||
}) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
[**Checkbox**](https://godoc.org/github.com/rivo/tview#Checkbox) holds a label and boolean value which may be checked and unchecked. |
||||
|
||||
```go |
||||
checkbox := tview.NewCheckbox().SetLabel("Toggle value with Enter: ") |
||||
``` |
||||
|
||||
--- |
||||
|
||||
[**DropDown**](https://godoc.org/github.com/rivo/tview#DropDown) holds one or more options which may be selected as a dropdown list. |
||||
|
||||
```go |
||||
dropdown := tview.NewDropDown(). |
||||
SetLabel("Select an option with Enter: "). |
||||
SetOptions([]string{"Foo", "Bar", "Baz"}, nil) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
[**InputField**](https://godoc.org/github.com/rivo/tview#InputField) is a box where text may be entered. |
||||
|
||||
```go |
||||
inputField := tview.NewInputField(). |
||||
SetLabel("Name: "). |
||||
SetPlaceholder("John Smith"). |
||||
SetFieldWidth(14). |
||||
SetDoneFunc(func(key tcell.Key) { |
||||
processName() |
||||
}) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
[**Modal**](https://godoc.org/github.com/rivo/tview#Modal) is a centered message window which may have one or more buttons. |
||||
|
||||
```go |
||||
modal := tview.NewModal(). |
||||
SetText("Are you sure you want to exit?"). |
||||
AddButtons([]string{"Cancel", "Quit"}). |
||||
SetDoneFunc(func(buttonIndex int, buttonLabel string) { |
||||
if buttonIndex == 1 { |
||||
app.Stop() |
||||
} |
||||
}) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
[**TextView**](https://godoc.org/github.com/rivo/tview#TextView) is a box containing text. |
||||
[Colored text](https://godoc.org/github.com/rivo/tview#hdr-Colors) is supported when enabled. |
||||
|
||||
```go |
||||
textView := tview.NewTextView(). |
||||
SetWrap(true). |
||||
SetWordWrap(true). |
||||
SetText("Hello, World!") |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Widget Containers |
||||
|
||||
--- |
||||
|
||||
[**Flex**](https://godoc.org/github.com/rivo/tview#Flex) is a [flexbox layout](https://en.wikipedia.org/wiki/CSS_Flexible_Box_Layout) container. |
||||
|
||||
See the [Flex demo](https://github.com/rivo/tview/tree/master/demos/flex) for example usage. |
||||
|
||||
--- |
||||
|
||||
[**Grid**](https://godoc.org/github.com/rivo/tview#Grid) is a [grid layout](https://en.wikipedia.org/wiki/CSS_grid_layout) container. |
||||
|
||||
See the [Grid demo](https://github.com/rivo/tview/tree/master/demos/grid) for example usage. |
||||
|
||||
--- |
||||
|
||||
[**Form**](https://godoc.org/github.com/rivo/tview#Form) displays one or more form elements in a vertical or horizontal layout. |
||||
|
||||
See the [Form demo](https://github.com/rivo/tview/tree/master/demos/form) for example usage. |
||||
|
||||
--- |
||||
|
||||
[**List**](https://godoc.org/github.com/rivo/tview#List) displays one or more widgets as a selectable list. |
||||
|
||||
See the [List demo](https://github.com/rivo/tview/tree/master/demos/list) for example usage. |
||||
|
||||
--- |
||||
|
||||
[**Pages**](https://godoc.org/github.com/rivo/tview#Pages) displays one or more widgets at a time. |
||||
|
||||
See the [Pages demo](https://github.com/rivo/tview/tree/master/demos/pages) for example usage. |
||||
|
||||
--- |
||||
|
||||
[**Table**](https://godoc.org/github.com/rivo/tview#Table) displays one or more widgets in rows and columns. |
||||
|
||||
See the [Table demo](https://github.com/rivo/tview/tree/master/demos/table) for example usage. |
||||
|
||||
--- |
||||
|
||||
[**TreeView**](https://godoc.org/github.com/rivo/tview#TreeView) displays one or more widgets in a tree. |
||||
|
||||
See the [TreeView demo](https://github.com/rivo/tview/tree/master/demos/treeview) for example usage. |
||||
|
||||
--- |
||||
|
||||
# Thread Safety |
||||
|
||||
**Most tview functions cannot safely be called from any thread except the main one**. |
||||
|
||||
Using either of the following functions, we can queue a function to be executed in the main thread. |
||||
|
||||
* [Application.QueueUpdate](https://godoc.org/github.com/rivo/tview#Application.QueueUpdate) |
||||
* [Application.QueueUpdateDraw](https://godoc.org/github.com/rivo/tview#Application.QueueUpdateDraw) |
||||
|
||||
The only difference between the two is that QueueUpdateDraw calls [Application.Draw](https://godoc.org/github.com/rivo/tview#Application.Draw) after the queued function returns. |
||||
|
||||
One exception is [TextView.Write](https://godoc.org/github.com/rivo/tview#TextView.Write), which may safely be called from multiple goroutines. |
||||
|
||||
Below is an example of setting a new root primitive from another goroutine. |
||||
|
||||
```go |
||||
app.QueueUpdateDraw(func() { |
||||
app.SetRoot(appGrid, true) |
||||
}) |
||||
``` |
||||
|
||||
# Example Application |
||||
|
||||
A tview application is constructed of a running [Application](https://godoc.org/github.com/rivo/tview#Application) with at least one root widget. |
||||
|
||||
To display a primitive (and its contents), we call [Application.SetRoot](https://godoc.org/github.com/rivo/tview#Application.SetRoot). |
||||
|
||||
This function has two arguments, a primitive which will become the root of the screen, and a boolean which controls whether the primitive will be resized to fit the screen. |
||||
|
||||
In this example, the root is a Grid containing a label, an input and a submit button. For a more complex example, see [netris](https://git.sr.ht/~tslocum/netris). |
||||
|
||||
Install tview if you haven't already: |
||||
|
||||
```command |
||||
go get -u github.com/rivo/tview |
||||
``` |
||||
|
||||
Then create a file named greet.go: |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"strings" |
||||
|
||||
"github.com/gdamore/tcell" |
||||
"github.com/rivo/tview" |
||||
) |
||||
|
||||
func main() { |
||||
// Initialize application |
||||
app := tview.NewApplication() |
||||
|
||||
// Create label |
||||
label := tview.NewTextView().SetText("Please enter your name:") |
||||
|
||||
// Create input field |
||||
input := tview.NewInputField() |
||||
|
||||
// Create submit button |
||||
btn := tview.NewButton("Submit") |
||||
|
||||
// Create empty Box to pad each side of appGrid |
||||
bx := tview.NewBox() |
||||
|
||||
// Create Grid containing the application's widgets |
||||
appGrid := tview.NewGrid(). |
||||
SetColumns(-1, 24, 16, -1). |
||||
SetRows(-1, 2, 3, -1). |
||||
AddItem(bx, 0, 0, 3, 1, 0, 0, false). // Left - 3 rows |
||||
AddItem(bx, 0, 1, 1, 1, 0, 0, false). // Top - 1 row |
||||
AddItem(bx, 0, 3, 3, 1, 0, 0, false). // Right - 3 rows |
||||
AddItem(bx, 3, 1, 1, 1, 0, 0, false). // Bottom - 1 row |
||||
AddItem(label, 1, 1, 1, 1, 0, 0, false). |
||||
AddItem(input, 1, 2, 1, 1, 0, 0, false). |
||||
AddItem(btn, 2, 1, 1, 2, 0, 0, false) |
||||
|
||||
// submittedName is toggled each time Enter is pressed |
||||
var submittedName bool |
||||
|
||||
// Capture user input |
||||
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { |
||||
// Anything handled here will be executed on the main thread |
||||
switch event.Key() { |
||||
case tcell.KeyEnter: |
||||
submittedName = !submittedName |
||||
|
||||
if submittedName { |
||||
name := input.GetText() |
||||
if strings.TrimSpace(name) == "" { |
||||
name = "Anonymous" |
||||
} |
||||
|
||||
// Create a modal dialog |
||||
m := tview.NewModal(). |
||||
SetText(fmt.Sprintf("Greetings, %s!", name)). |
||||
AddButtons([]string{"Hello"}) |
||||
|
||||
// Display and focus the dialog |
||||
app.SetRoot(m, true).SetFocus(m) |
||||
} else { |
||||
// Clear the input field |
||||
input.SetText("") |
||||
|
||||
// Display appGrid and focus the input field |
||||
app.SetRoot(appGrid, true).SetFocus(input) |
||||
} |
||||
return nil |
||||
case tcell.KeyEsc: |
||||
// Exit the application |
||||
app.Stop() |
||||
return nil |
||||
} |
||||
|
||||
return event |
||||
}) |
||||
|
||||
// Set the grid as the application root and focus the input field |
||||
app.SetRoot(appGrid, true).SetFocus(input) |
||||
|
||||
// Run the application |
||||
err := app.Run() |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
``` |
Loading…
Reference in new issue