commit f9f139caafa003b658dc3fbc2a94e657d1a429fb Author: Oliver <480930+rivo@users.noreply.github.com> Date: Fri Dec 15 15:29:21 2017 +0100 First commit. Some basic functionality. Publishing to GitHub now. diff --git a/Application.go b/Application.go new file mode 100644 index 0000000..c19b634 --- /dev/null +++ b/Application.go @@ -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 +} diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/LICENSE.txt @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c89d042 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Widgets for Terminal GUIs + +Based on [github.com/gdamore/tcell](https://github.com/gdamore/tcell). + +Work in progress. diff --git a/box.go b/box.go new file mode 100644 index 0000000..7057561 --- /dev/null +++ b/box.go @@ -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 +} diff --git a/demos/basic.go b/demos/basic.go new file mode 100644 index 0000000..7ea6e28 --- /dev/null +++ b/demos/basic.go @@ -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) + } +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..a13ef9d --- /dev/null +++ b/doc.go @@ -0,0 +1,5 @@ +/* +Package tview implements primitives for terminal based applications. It uses +github.com/gdamore/tcell. +*/ +package tview diff --git a/flex.go b/flex.go new file mode 100644 index 0000000..3302c05 --- /dev/null +++ b/flex.go @@ -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() { +} diff --git a/form.go b/form.go new file mode 100644 index 0000000..b362636 --- /dev/null +++ b/form.go @@ -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) { + } +} diff --git a/inputfield.go b/inputfield.go new file mode 100644 index 0000000..cb10945 --- /dev/null +++ b/inputfield.go @@ -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) + } + } + } +} diff --git a/primitive.go b/primitive.go new file mode 100644 index 0000000..1dff02c --- /dev/null +++ b/primitive.go @@ -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() +}