First commit. Some basic functionality. Publishing to GitHub now.
commit
f9f139caaf
|
@ -0,0 +1,157 @@
|
|||
package tview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// Application represents the top node of an application.
|
||||
type Application struct {
|
||||
sync.Mutex
|
||||
|
||||
// The application's screen.
|
||||
screen tcell.Screen
|
||||
|
||||
// The primitive which currently has the keyboard focus.
|
||||
focus Primitive
|
||||
|
||||
// The root primitive to be seen on the screen.
|
||||
root Primitive
|
||||
|
||||
// Whether or not the application resizes the root primitive.
|
||||
rootAutoSize bool
|
||||
}
|
||||
|
||||
// NewApplication creates and returns a new application.
|
||||
func NewApplication() *Application {
|
||||
return &Application{}
|
||||
}
|
||||
|
||||
// Run starts the application and thus the event loop. This function returns
|
||||
// when Stop() was called.
|
||||
func (a *Application) Run() error {
|
||||
var err error
|
||||
|
||||
// Make a screen.
|
||||
a.screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = a.screen.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We catch panics to clean up because they mess up the terminal.
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
a.screen.Fini()
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
|
||||
// Draw the screen for the first time.
|
||||
if a.rootAutoSize && a.root != nil {
|
||||
width, height := a.screen.Size()
|
||||
a.root.SetRect(0, 0, width, height)
|
||||
}
|
||||
a.Draw()
|
||||
|
||||
// Start event loop.
|
||||
for {
|
||||
event := a.screen.PollEvent()
|
||||
if event == nil {
|
||||
break // The screen was finalized.
|
||||
}
|
||||
switch event := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if event.Key() == tcell.KeyCtrlC {
|
||||
a.Stop()
|
||||
}
|
||||
a.Lock()
|
||||
p := a.focus
|
||||
a.Unlock()
|
||||
if p != nil {
|
||||
if handler := p.InputHandler(); handler != nil {
|
||||
handler(event)
|
||||
a.Draw()
|
||||
}
|
||||
}
|
||||
case *tcell.EventResize:
|
||||
if a.rootAutoSize && a.root != nil {
|
||||
width, height := a.screen.Size()
|
||||
a.Lock()
|
||||
a.root.SetRect(0, 0, width, height)
|
||||
a.Unlock()
|
||||
a.Draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the application, causing Run() to return.
|
||||
func (a *Application) Stop() {
|
||||
a.screen.Fini()
|
||||
}
|
||||
|
||||
// Draw refreshes the screen. It calls the Draw() function of the application's
|
||||
// root primitive and then syncs the screen buffer.
|
||||
func (a *Application) Draw() *Application {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
// Maybe we're not ready yet.
|
||||
if a.screen == nil {
|
||||
return a
|
||||
}
|
||||
|
||||
// Draw all primitives.
|
||||
if a.root != nil {
|
||||
a.root.Draw(a.screen)
|
||||
}
|
||||
|
||||
// Sync screen.
|
||||
a.screen.Show()
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// SetRoot sets the root primitive for this application. This function must be
|
||||
// called or nothing will be displayed when the application starts.
|
||||
//
|
||||
// If autoSize is set to true, the application will set the root primitive's
|
||||
// position to (0,0) and its size to the screen's size. It will also resize and
|
||||
// redraw it when the screen resizes.
|
||||
func (a *Application) SetRoot(root Primitive, autoSize bool) *Application {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
a.root = root
|
||||
a.rootAutoSize = autoSize
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// SetFocus sets the focus on a new primitive. All key events will be redirected
|
||||
// to that primitive. Callers must ensure that the primitive will handle key
|
||||
// events.
|
||||
//
|
||||
// Blur() will be called on the previously focused primitive. Focus() will be
|
||||
// called on the new primitive.
|
||||
func (a *Application) SetFocus(p Primitive) *Application {
|
||||
if p.InputHandler() == nil {
|
||||
return a
|
||||
}
|
||||
|
||||
a.Lock()
|
||||
if a.focus != nil {
|
||||
a.focus.Blur()
|
||||
}
|
||||
a.focus = p
|
||||
a.Unlock()
|
||||
p.Focus(a)
|
||||
|
||||
return a
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) [year] [fullname]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,5 @@
|
|||
# Widgets for Terminal GUIs
|
||||
|
||||
Based on [github.com/gdamore/tcell](https://github.com/gdamore/tcell).
|
||||
|
||||
Work in progress.
|
|
@ -0,0 +1,174 @@
|
|||
package tview
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
|
||||
// Characters to draw the box border.
|
||||
const (
|
||||
BoxVertBar = '\u2500'
|
||||
BoxHorBar = '\u2502'
|
||||
BoxTopLeftCorner = '\u250c'
|
||||
BoxTopRightCorner = '\u2510'
|
||||
BoxBottomRightCorner = '\u2518'
|
||||
BoxBottomLeftCorner = '\u2514'
|
||||
BoxEllipsis = '\u2026'
|
||||
)
|
||||
|
||||
// Box implements Rect with a background and optional elements such as a border
|
||||
// and a title.
|
||||
type Box struct {
|
||||
// The position of the rect.
|
||||
x, y, width, height int
|
||||
|
||||
// Whether or not the box has focus.
|
||||
hasFocus bool
|
||||
|
||||
// The box's background color.
|
||||
backgroundColor tcell.Color
|
||||
|
||||
// Whether or not a border is drawn, reducing the box's space for content by
|
||||
// two in width and height.
|
||||
border bool
|
||||
|
||||
// The color of the border.
|
||||
borderColor tcell.Color
|
||||
|
||||
// The color of the border when the box has focus.
|
||||
focusedBorderColor tcell.Color
|
||||
|
||||
// The title. Only visible if there is a border, too.
|
||||
title string
|
||||
|
||||
// The color of the title.
|
||||
titleColor tcell.Color
|
||||
}
|
||||
|
||||
// NewBox returns a Box without a border.
|
||||
func NewBox() *Box {
|
||||
return &Box{
|
||||
width: 15,
|
||||
height: 10,
|
||||
borderColor: tcell.ColorWhite,
|
||||
focusedBorderColor: tcell.ColorYellow,
|
||||
titleColor: tcell.ColorWhite,
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (b *Box) Draw(screen tcell.Screen) {
|
||||
// Don't draw anything if there is no space.
|
||||
if b.width <= 0 || b.height <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
def := tcell.StyleDefault
|
||||
|
||||
// Fill background.
|
||||
background := def.Background(b.backgroundColor)
|
||||
for y := b.y; y < b.y+b.height; y++ {
|
||||
for x := b.x; x < b.x+b.width; x++ {
|
||||
screen.SetContent(x, y, ' ', nil, background)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw border.
|
||||
if b.border && b.width >= 2 && b.height >= 2 {
|
||||
border := background.Foreground(b.borderColor)
|
||||
if b.hasFocus {
|
||||
border = background.Foreground(b.focusedBorderColor)
|
||||
}
|
||||
for x := b.x + 1; x < b.x+b.width-1; x++ {
|
||||
screen.SetContent(x, b.y, BoxVertBar, nil, border)
|
||||
screen.SetContent(x, b.y+b.height-1, BoxVertBar, nil, border)
|
||||
}
|
||||
for y := b.y + 1; y < b.y+b.height-1; y++ {
|
||||
screen.SetContent(b.x, y, BoxHorBar, nil, border)
|
||||
screen.SetContent(b.x+b.width-1, y, BoxHorBar, nil, border)
|
||||
}
|
||||
screen.SetContent(b.x, b.y, BoxTopLeftCorner, nil, border)
|
||||
screen.SetContent(b.x+b.width-1, b.y, BoxTopRightCorner, nil, border)
|
||||
screen.SetContent(b.x, b.y+b.height-1, BoxBottomLeftCorner, nil, border)
|
||||
screen.SetContent(b.x+b.width-1, b.y+b.height-1, BoxBottomRightCorner, nil, border)
|
||||
|
||||
// Draw title.
|
||||
if b.title != "" && b.width >= 4 {
|
||||
title := background.Foreground(b.titleColor)
|
||||
x := b.x
|
||||
for index, ch := range b.title {
|
||||
x++
|
||||
if x >= b.x+b.width-1 {
|
||||
break
|
||||
}
|
||||
if x == b.x+b.width-2 && index < len(b.title)-1 {
|
||||
ch = BoxEllipsis
|
||||
}
|
||||
screen.SetContent(x, b.y, ch, nil, title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRect returns the current position of the rectangle, x, y, width, and
|
||||
// height.
|
||||
func (b *Box) GetRect() (int, int, int, int) {
|
||||
return b.x, b.y, b.width, b.height
|
||||
}
|
||||
|
||||
// SetRect sets a new position of the rectangle.
|
||||
func (b *Box) SetRect(x, y, width, height int) {
|
||||
b.x = x
|
||||
b.y = y
|
||||
b.width = width
|
||||
b.height = height
|
||||
}
|
||||
|
||||
// InputHandler returns nil.
|
||||
func (b *Box) InputHandler() func(event *tcell.EventKey) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBackgroundColor sets the box's background color.
|
||||
func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
|
||||
b.backgroundColor = color
|
||||
return b
|
||||
}
|
||||
|
||||
// SetBorder sets the flag indicating whether or not the box should have a
|
||||
// border.
|
||||
func (b *Box) SetBorder(show bool) *Box {
|
||||
b.border = show
|
||||
return b
|
||||
}
|
||||
|
||||
// SetBorderColor sets the box's border color.
|
||||
func (b *Box) SetBorderColor(color tcell.Color) *Box {
|
||||
b.borderColor = color
|
||||
return b
|
||||
}
|
||||
|
||||
// SetFocusedBorderColor sets the box's border color for when the box has focus.
|
||||
func (b *Box) SetFocusedBorderColor(color tcell.Color) *Box {
|
||||
b.focusedBorderColor = color
|
||||
return b
|
||||
}
|
||||
|
||||
// SetTitle sets the box's title.
|
||||
func (b *Box) SetTitle(title string) *Box {
|
||||
b.title = title
|
||||
return b
|
||||
}
|
||||
|
||||
// SetTitleColor sets the box's title color.
|
||||
func (b *Box) SetTitleColor(color tcell.Color) *Box {
|
||||
b.titleColor = color
|
||||
return b
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (b *Box) Focus(app *Application) {
|
||||
b.hasFocus = true
|
||||
}
|
||||
|
||||
// Blur is called when this primitive loses focus.
|
||||
func (b *Box) Blur() {
|
||||
b.hasFocus = false
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package main
|
||||
|
||||
import "github.com/rivo/tview"
|
||||
|
||||
func main() {
|
||||
form := tview.NewForm().AddItem("First name", "", 20, nil).AddItem("Last name", "", 20, nil).AddItem("Age", "", 4, nil)
|
||||
form.SetBorder(true)
|
||||
|
||||
box := tview.NewFlex(tview.FlexColumn, []tview.Primitive{
|
||||
form,
|
||||
tview.NewFlex(tview.FlexRow, []tview.Primitive{
|
||||
tview.NewBox().SetBorder(true).SetTitle("Second"),
|
||||
tview.NewBox().SetBorder(true).SetTitle("Third"),
|
||||
}),
|
||||
tview.NewBox().SetBorder(true).SetTitle("Fourth"),
|
||||
})
|
||||
box.AddItem(tview.NewBox().SetBorder(true).SetTitle("Fifth"), 20)
|
||||
|
||||
inputField := tview.NewInputField().
|
||||
SetLabel("Type something: ").
|
||||
SetFieldLength(10).
|
||||
SetAcceptanceFunc(tview.InputFieldFloat)
|
||||
inputField.SetBorder(true).SetTitle("Type!")
|
||||
|
||||
final := tview.NewFlex(tview.FlexRow, []tview.Primitive{box})
|
||||
final.AddItem(inputField, 3)
|
||||
|
||||
app := tview.NewApplication()
|
||||
app.SetRoot(final, true).SetFocus(form)
|
||||
|
||||
if err := app.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
Package tview implements primitives for terminal based applications. It uses
|
||||
github.com/gdamore/tcell.
|
||||
*/
|
||||
package tview
|
|
@ -0,0 +1,112 @@
|
|||
package tview
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
|
||||
// Configuration values.
|
||||
const (
|
||||
FlexRow = iota
|
||||
FlexColumn
|
||||
)
|
||||
|
||||
// FlexItem holds layout options for one item.
|
||||
type FlexItem struct {
|
||||
Item Primitive // The item to be positioned.
|
||||
FixedSize int // The item's fixed size which may not be changed, 0 if it has no fixed size.
|
||||
}
|
||||
|
||||
// Flex is a basic implementation of a flexbox layout.
|
||||
type Flex struct {
|
||||
x, y, width, height int // The size and position of this primitive.
|
||||
Items []FlexItem // The items to be positioned.
|
||||
Direction int // FlexRow or FlexColumn.
|
||||
}
|
||||
|
||||
// NewFlex returns a new flexbox layout container with the given primitives.
|
||||
// The items all have no fixed size. If more control is needed, call AddItem().
|
||||
// The direction argument must be FlexRow or FlexColumn.
|
||||
func NewFlex(direction int, items []Primitive) *Flex {
|
||||
box := &Flex{
|
||||
width: 15,
|
||||
height: 10,
|
||||
Direction: direction,
|
||||
}
|
||||
for _, item := range items {
|
||||
box.Items = append(box.Items, FlexItem{Item: item})
|
||||
}
|
||||
return box
|
||||
}
|
||||
|
||||
// AddItem adds a new item to the container. fixedSize is a size that may not be
|
||||
// changed. A value of 0 means that its size may be changed.
|
||||
func (f *Flex) AddItem(item Primitive, fixedSize int) *Flex {
|
||||
f.Items = append(f.Items, FlexItem{Item: item, FixedSize: fixedSize})
|
||||
return f
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (f *Flex) Draw(screen tcell.Screen) {
|
||||
// Calculate size and position of the items.
|
||||
|
||||
// How much space can we distribute?
|
||||
var variables int
|
||||
distSize := f.width
|
||||
if f.Direction == FlexRow {
|
||||
distSize = f.height
|
||||
}
|
||||
for _, item := range f.Items {
|
||||
if item.FixedSize > 0 {
|
||||
distSize -= item.FixedSize
|
||||
} else {
|
||||
variables++
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate positions and draw items.
|
||||
pos := f.x
|
||||
if f.Direction == FlexRow {
|
||||
pos = f.y
|
||||
}
|
||||
for _, item := range f.Items {
|
||||
size := item.FixedSize
|
||||
if size <= 0 {
|
||||
size = distSize / variables
|
||||
distSize -= size
|
||||
variables--
|
||||
}
|
||||
if f.Direction == FlexColumn {
|
||||
item.Item.SetRect(pos, f.y, size, f.height)
|
||||
} else {
|
||||
item.Item.SetRect(f.x, pos, f.width, size)
|
||||
}
|
||||
pos += size
|
||||
|
||||
item.Item.Draw(screen)
|
||||
}
|
||||
}
|
||||
|
||||
// GetRect returns the current position of the primitive, x, y, width, and
|
||||
// height.
|
||||
func (f *Flex) GetRect() (int, int, int, int) {
|
||||
return f.x, f.y, f.width, f.height
|
||||
}
|
||||
|
||||
// SetRect sets a new position of the primitive.
|
||||
func (f *Flex) SetRect(x, y, width, height int) {
|
||||
f.x = x
|
||||
f.y = y
|
||||
f.width = width
|
||||
f.height = height
|
||||
}
|
||||
|
||||
// InputHandler returns nil.
|
||||
func (f *Flex) InputHandler() func(event *tcell.EventKey) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (f *Flex) Focus(app *Application) {
|
||||
}
|
||||
|
||||
// Blur is called when this primitive loses focus.
|
||||
func (f *Flex) Blur() {
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package tview
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// Form is a Box which contains multiple input fields, one per row.
|
||||
type Form struct {
|
||||
Box
|
||||
|
||||
// The items of the form (one row per item).
|
||||
items []*InputField
|
||||
|
||||
// The number of empty rows between items.
|
||||
itemPadding int
|
||||
|
||||
// The index of the item which has focus.
|
||||
focusedItem int
|
||||
|
||||
// The label color.
|
||||
labelColor tcell.Color
|
||||
|
||||
// The background color of the input area.
|
||||
fieldBackgroundColor tcell.Color
|
||||
|
||||
// The text color of the input area.
|
||||
fieldTextColor tcell.Color
|
||||
}
|
||||
|
||||
// NewForm returns a new form.
|
||||
func NewForm() *Form {
|
||||
return &Form{
|
||||
Box: *NewBox(),
|
||||
itemPadding: 1,
|
||||
labelColor: tcell.ColorYellow,
|
||||
fieldBackgroundColor: tcell.ColorBlue,
|
||||
fieldTextColor: tcell.ColorWhite,
|
||||
}
|
||||
}
|
||||
|
||||
// SetItemPadding sets the number of empty rows between form items.
|
||||
func (f *Form) SetItemPadding(padding int) *Form {
|
||||
f.itemPadding = padding
|
||||
return f
|
||||
}
|
||||
|
||||
// SetLabelColor sets the color of the labels.
|
||||
func (f *Form) SetLabelColor(color tcell.Color) *Form {
|
||||
f.labelColor = color
|
||||
return f
|
||||
}
|
||||
|
||||
// SetFieldBackgroundColor sets the background color of the input areas.
|
||||
func (f *Form) SetFieldBackgroundColor(color tcell.Color) *Form {
|
||||
f.fieldBackgroundColor = color
|
||||
return f
|
||||
}
|
||||
|
||||
// SetFieldTextColor sets the text color of the input areas.
|
||||
func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
|
||||
f.fieldTextColor = color
|
||||
return f
|
||||
}
|
||||
|
||||
// AddItem adds a new item to the form. It has a label, an optional initial
|
||||
// value, a field length (a value of 0 extends it as far as possible), and
|
||||
// an optional accept function to validate the item's value (set to nil to
|
||||
// accept any text).
|
||||
func (f *Form) AddItem(label, value string, fieldLength int, accept func(textToCheck string, lastChar rune) bool) *Form {
|
||||
f.items = append(f.items, NewInputField().
|
||||
SetLabel(label).
|
||||
SetText(value).
|
||||
SetFieldLength(fieldLength).
|
||||
SetAcceptanceFunc(accept))
|
||||
return f
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (f *Form) Draw(screen tcell.Screen) {
|
||||
f.Box.Draw(screen)
|
||||
|
||||
// Determine the dimensions.
|
||||
x := f.x
|
||||
y := f.y
|
||||
width := f.width
|
||||
bottomLimit := f.y + f.height
|
||||
if f.border {
|
||||
x++
|
||||
y++
|
||||
width -= 2
|
||||
bottomLimit -= 2
|
||||
}
|
||||
|
||||
// Find the longest label.
|
||||
var labelLength int
|
||||
for _, inputField := range f.items {
|
||||
label := strings.TrimSpace(inputField.GetLabel())
|
||||
if len([]rune(label)) > labelLength {
|
||||
labelLength = len([]rune(label))
|
||||
}
|
||||
}
|
||||
labelLength++ // Add one space.
|
||||
|
||||
// Set up and draw the input fields.
|
||||
for _, inputField := range f.items {
|
||||
if y >= bottomLimit {
|
||||
break
|
||||
}
|
||||
label := strings.TrimSpace(inputField.GetLabel())
|
||||
inputField.SetLabelColor(f.labelColor).
|
||||
SetFieldBackgroundColor(f.fieldBackgroundColor).
|
||||
SetFieldTextColor(f.fieldTextColor).
|
||||
SetLabel(label+strings.Repeat(" ", labelLength-len([]rune(label)))).
|
||||
SetBackgroundColor(f.backgroundColor).
|
||||
SetRect(x, y, width, 1)
|
||||
inputField.Draw(screen)
|
||||
y += 1 + f.itemPadding
|
||||
}
|
||||
}
|
||||
|
||||
// Focus is called by the application when the primitive receives focus.
|
||||
func (f *Form) Focus(app *Application) {
|
||||
f.Box.Focus(app)
|
||||
|
||||
if len(f.items) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Hand on the focus to one of our items.
|
||||
if f.focusedItem < 0 || f.focusedItem >= len(f.items) {
|
||||
f.focusedItem = 0
|
||||
}
|
||||
f.hasFocus = false
|
||||
inputField := f.items[f.focusedItem]
|
||||
inputField.SetDoneFunc(func(key tcell.Key) {
|
||||
switch key {
|
||||
case tcell.KeyTab:
|
||||
f.focusedItem++
|
||||
f.Focus(app)
|
||||
}
|
||||
})
|
||||
app.SetFocus(inputField)
|
||||
}
|
||||
|
||||
// InputHandler returns the handler for this primitive.
|
||||
func (f *Form) InputHandler() func(event *tcell.EventKey) {
|
||||
return func(event *tcell.EventKey) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
package tview
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
var (
|
||||
// InputFieldInteger accepts integers.
|
||||
InputFieldInteger func(text string, ch rune) bool
|
||||
|
||||
// InputFieldFloat accepts floating-point numbers.
|
||||
InputFieldFloat func(text string, ch rune) bool
|
||||
|
||||
// InputFieldMaxLength returns an input field accept handler which accepts
|
||||
// input strings up to a given length. Use it like this:
|
||||
//
|
||||
// inputField.SetAcceptanceFunc(InputFieldMaxLength(10)) // Accept up to 10 characters.
|
||||
InputFieldMaxLength func(maxLength int) func(text string, ch rune) bool
|
||||
)
|
||||
|
||||
// Package initialization.
|
||||
func init() {
|
||||
// Initialize the predefined handlers.
|
||||
|
||||
InputFieldInteger = func(text string, ch rune) bool {
|
||||
if text == "-" {
|
||||
return true
|
||||
}
|
||||
_, err := strconv.Atoi(text)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
InputFieldFloat = func(text string, ch rune) bool {
|
||||
if text == "-" || text == "." {
|
||||
return true
|
||||
}
|
||||
_, err := strconv.ParseFloat(text, 64)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
InputFieldMaxLength = func(maxLength int) func(text string, ch rune) bool {
|
||||
return func(text string, ch rune) bool {
|
||||
return len([]rune(text)) <= maxLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InputField is a one-line box (three lines if there is a title) where the
|
||||
// user can enter text.
|
||||
type InputField struct {
|
||||
Box
|
||||
|
||||
// The text that was entered.
|
||||
text string
|
||||
|
||||
// The text to be displayed before the input area.
|
||||
label string
|
||||
|
||||
// The label color.
|
||||
labelColor tcell.Color
|
||||
|
||||
// The background color of the input area.
|
||||
fieldBackgroundColor tcell.Color
|
||||
|
||||
// The text color of the input area.
|
||||
fieldTextColor tcell.Color
|
||||
|
||||
// The length of the input area. A value of 0 means extend as much as
|
||||
// possible.
|
||||
fieldLength int
|
||||
|
||||
// An optional function which may reject the last character that was entered.
|
||||
accept func(text string, ch rune) bool
|
||||
|
||||
// An optional function which is called when the user indicated that they
|
||||
// are done entering text. The key which was pressed is provided.
|
||||
done func(tcell.Key)
|
||||
}
|
||||
|
||||
// NewInputField returns a new input field.
|
||||
func NewInputField() *InputField {
|
||||
return &InputField{
|
||||
Box: *NewBox(),
|
||||
labelColor: tcell.ColorYellow,
|
||||
fieldBackgroundColor: tcell.ColorBlue,
|
||||
fieldTextColor: tcell.ColorWhite,
|
||||
}
|
||||
}
|
||||
|
||||
// SetText sets the current text of the input field.
|
||||
func (i *InputField) SetText(text string) *InputField {
|
||||
i.text = text
|
||||
return i
|
||||
}
|
||||
|
||||
// GetText returns the current text of the input field.
|
||||
func (i *InputField) GetText() string {
|
||||
return i.text
|
||||
}
|
||||
|
||||
// SetLabel sets the text to be displayed before the input area.
|
||||
func (i *InputField) SetLabel(label string) *InputField {
|
||||
i.label = label
|
||||
return i
|
||||
}
|
||||
|
||||
// GetLabel returns the text to be displayed before the input area.
|
||||
func (i *InputField) GetLabel() string {
|
||||
return i.label
|
||||
}
|
||||
|
||||
// SetLabelColor sets the color of the label.
|
||||
func (i *InputField) SetLabelColor(color tcell.Color) *InputField {
|
||||
i.labelColor = color
|
||||
return i
|
||||
}
|
||||
|
||||
// SetFieldBackgroundColor sets the background color of the input area.
|
||||
func (i *InputField) SetFieldBackgroundColor(color tcell.Color) *InputField {
|
||||
i.fieldBackgroundColor = color
|
||||
return i
|
||||
}
|
||||
|
||||
// SetFieldTextColor sets the text color of the input area.
|
||||
func (i *InputField) SetFieldTextColor(color tcell.Color) *InputField {
|
||||
i.fieldTextColor = color
|
||||
return i
|
||||
}
|
||||
|
||||
// SetFieldLength sets the length of the input area. A value of 0 means extend
|
||||
// as much as possible.
|
||||
func (i *InputField) SetFieldLength(length int) *InputField {
|
||||
i.fieldLength = length
|
||||
return i
|
||||
}
|
||||
|
||||
// SetAcceptanceFunc sets a handler which may reject the last character that was
|
||||
// entered (by returning false).
|
||||
//
|
||||
// This package defines a number of variables Prefixed with InputField which may
|
||||
// be used for common input (e.g. numbers, maximum text length).
|
||||
func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
|
||||
i.accept = handler
|
||||
return i
|
||||
}
|
||||
|
||||
// SetDoneFunc sets a handler which is called when the user is done entering
|
||||
// text. The callback function is provided with the key that was pressed, which
|
||||
// is one of the following:
|
||||
//
|
||||
// - KeyEnter: Done entering text.
|
||||
// - KeyEscape: Abort text input.
|
||||
// - KeyTab: Move to the next field.
|
||||
func (i *InputField) SetDoneFunc(handler func(key tcell.Key)) *InputField {
|
||||
i.done = handler
|
||||
return i
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (i *InputField) Draw(screen tcell.Screen) {
|
||||
i.Box.Draw(screen)
|
||||
|
||||
// Prepare
|
||||
x := i.x
|
||||
y := i.y
|
||||
rightLimit := x + i.width
|
||||
height := i.height
|
||||
if i.border {
|
||||
x++
|
||||
y++
|
||||
rightLimit -= 2
|
||||
height -= 2
|
||||
}
|
||||
if height < 1 || rightLimit <= x {
|
||||
return
|
||||
}
|
||||
|
||||
// Draw label.
|
||||
labelStyle := tcell.StyleDefault.Background(i.backgroundColor).Foreground(i.labelColor)
|
||||
for _, ch := range i.label {
|
||||
if x >= rightLimit {
|
||||
return
|
||||
}
|
||||
screen.SetContent(x, y, ch, nil, labelStyle)
|
||||
x++
|
||||
}
|
||||
|
||||
// Draw input area.
|
||||
inputStyle := tcell.StyleDefault.Background(i.fieldBackgroundColor).Foreground(i.fieldTextColor)
|
||||
fieldLength := i.fieldLength
|
||||
if fieldLength == 0 {
|
||||
fieldLength = math.MaxInt64
|
||||
}
|
||||
if rightLimit-x < fieldLength {
|
||||
fieldLength = rightLimit - x
|
||||
}
|
||||
text := []rune(i.text)
|
||||
index := 0
|
||||
if fieldLength-1 < len(text) {
|
||||
index = len(text) - fieldLength + 1
|
||||
}
|
||||
for fieldLength > 0 {
|
||||
ch := ' '
|
||||
if index < len(text) {
|
||||
ch = text[index]
|
||||
}
|
||||
screen.SetContent(x, y, ch, nil, inputStyle)
|
||||
x++
|
||||
index++
|
||||
fieldLength--
|
||||
}
|
||||
|
||||
// Set cursor.
|
||||
if i.hasFocus {
|
||||
i.setCursor(screen)
|
||||
}
|
||||
}
|
||||
|
||||
// setCursor sets the cursor position.
|
||||
func (i *InputField) setCursor(screen tcell.Screen) {
|
||||
x := i.x
|
||||
y := i.y
|
||||
rightLimit := x + i.width
|
||||
if i.border {
|
||||
x++
|
||||
y++
|
||||
rightLimit -= 2
|
||||
}
|
||||
fieldLength := len([]rune(i.text))
|
||||
if fieldLength > i.fieldLength-1 {
|
||||
fieldLength = i.fieldLength - 1
|
||||
}
|
||||
x += len([]rune(i.label)) + fieldLength
|
||||
if x >= rightLimit {
|
||||
x = rightLimit - 1
|
||||
}
|
||||
screen.ShowCursor(x, y)
|
||||
}
|
||||
|
||||
// InputHandler returns the handler for this primitive.
|
||||
func (i *InputField) InputHandler() func(event *tcell.EventKey) {
|
||||
return func(event *tcell.EventKey) {
|
||||
// Process key event.
|
||||
switch key := event.Key(); key {
|
||||
case tcell.KeyRune: // Regular character.
|
||||
newText := i.text + string(event.Rune())
|
||||
if i.accept != nil {
|
||||
if !i.accept(newText, event.Rune()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
i.text = newText
|
||||
case tcell.KeyCtrlU: // Delete all.
|
||||
i.text = ""
|
||||
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
|
||||
if len([]rune(i.text)) == 0 {
|
||||
break
|
||||
}
|
||||
i.text = i.text[:len([]rune(i.text))-1]
|
||||
case tcell.KeyEnter, tcell.KeyTab, tcell.KeyEscape: // We're done.
|
||||
if i.done != nil {
|
||||
i.done(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package tview
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
|
||||
// Primitive is the top-most interface for all graphical primitives.
|
||||
type Primitive interface {
|
||||
// Draw draws this primitive onto the screen. Implementers can call the
|
||||
// screen's ShowCursor() function but should only do so when they have focus.
|
||||
// (They will need to keep track of this themselves.)
|
||||
Draw(screen tcell.Screen)
|
||||
|
||||
// GetRect returns the current position of the primitive, x, y, width, and
|
||||
// height.
|
||||
GetRect() (int, int, int, int)
|
||||
|
||||
// SetRect sets a new position of the primitive.
|
||||
SetRect(x, y, width, height int)
|
||||
|
||||
// InputHandler returns a handler which receives key events when it has focus.
|
||||
// It is called by the Application class.
|
||||
//
|
||||
// A value of nil may also be returned, in which case this primitive cannot
|
||||
// receive focus and will not process any key events.
|
||||
//
|
||||
// The Application's Draw() function will be called automatically after the
|
||||
// handler returns.
|
||||
InputHandler() func(event *tcell.EventKey)
|
||||
|
||||
// Focus is called by the application when the primitive receives focus.
|
||||
Focus(app *Application)
|
||||
|
||||
// Blur is called by the application when the primitive loses focus.
|
||||
Blur()
|
||||
}
|
Loading…
Reference in New Issue