New features

pull/110/head
Levko Burburas 7 years ago
parent d9b056bafb
commit c8d49e02f7

@ -1,6 +1,8 @@
package tview package tview
import ( import (
"math/rand"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
) )
@ -13,9 +15,12 @@ import (
// //
// See https://github.com/rivo/tview/wiki/Box for an example. // See https://github.com/rivo/tview/wiki/Box for an example.
type Box struct { type Box struct {
id int
// The position of the rect. // The position of the rect.
x, y, width, height int x, y, width, height int
maxWidth, maxHeight, minWidth, minHeight int
// The inner rect reserved for the box's content. // The inner rect reserved for the box's content.
innerX, innerY, innerWidth, innerHeight int innerX, innerY, innerWidth, innerHeight int
@ -67,6 +72,7 @@ type Box struct {
// NewBox returns a Box without a border. // NewBox returns a Box without a border.
func NewBox() *Box { func NewBox() *Box {
b := &Box{ b := &Box{
id: rand.Int(),
width: 15, width: 15,
height: 10, height: 10,
innerX: -1, // Mark as uninitialized. innerX: -1, // Mark as uninitialized.
@ -80,15 +86,29 @@ func NewBox() *Box {
return b return b
} }
// SetHeight sets height of the box // GetID returns unique id of box
func (b *Box) SetHeight(height int) *Box { func (b *Box) GetID() int {
return b.id
}
// SetSize sets size of the box
func (b *Box) SetSize(width, height int) *Box {
b.width = width
b.height = height b.height = height
return b return b
} }
// SetWidth sets width of the box // SetMinSize sets min size of the box
func (b *Box) SetWidth(width int) *Box { func (b *Box) SetMinSize(width, height int) *Box {
b.width = width b.minWidth = width
b.minHeight = height
return b
}
// SetMaxSize sets max size of the box
func (b *Box) SetMaxSize(width, height int) *Box {
b.maxWidth = width
b.maxHeight = height
return b return b
} }
@ -114,7 +134,23 @@ func (b *Box) GetBorderPadding() (top, bottom, left, right int) {
// GetRect returns the current position of the rectangle, x, y, width, and // GetRect returns the current position of the rectangle, x, y, width, and
// height. // height.
func (b *Box) GetRect() (int, int, int, int) { func (b *Box) GetRect() (int, int, int, int) {
return b.x, b.y, b.width, b.height width := b.width
switch {
case b.maxWidth > 0 && width > b.maxWidth:
width = b.maxWidth
case b.minWidth > 0 && width < b.minWidth:
width = b.minWidth
}
height := b.height
switch {
case b.maxHeight > 0 && height > b.maxHeight:
height = b.maxHeight
case b.minHeight > 0 && height < b.minHeight:
height = b.minHeight
}
return b.x, b.y, width, height
} }
// GetInnerRect returns the position of the inner rectangle (x, y, width, // GetInnerRect returns the position of the inner rectangle (x, y, width,
@ -256,8 +292,9 @@ func (b *Box) SetTitleAlign(align int) *Box {
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (b *Box) Draw(screen tcell.Screen) { func (b *Box) Draw(screen tcell.Screen) {
_, _, width, height := b.GetRect()
// Don't draw anything if there is no space. // Don't draw anything if there is no space.
if b.width <= 0 || b.height <= 0 { if width <= 0 || height <= 0 {
return return
} }
@ -265,14 +302,14 @@ func (b *Box) Draw(screen tcell.Screen) {
// Fill background. // Fill background.
background := def.Background(b.backgroundColor) background := def.Background(b.backgroundColor)
for y := b.y; y < b.y+b.height; y++ { for y := b.y; y < b.y+height; y++ {
for x := b.x; x < b.x+b.width; x++ { for x := b.x; x < b.x+width; x++ {
screen.SetContent(x, y, ' ', nil, background) screen.SetContent(x, y, ' ', nil, background)
} }
} }
// Draw border. // Draw border.
if b.border && b.width >= 2 && b.height >= 2 { if b.border && width >= 2 && height >= 2 {
border := background.Foreground(b.borderColor) border := background.Foreground(b.borderColor)
var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
if b.focus.HasFocus() { if b.focus.HasFocus() {
@ -290,34 +327,34 @@ func (b *Box) Draw(screen tcell.Screen) {
bottomLeft = Styles.GraphicsBottomLeftCorner bottomLeft = Styles.GraphicsBottomLeftCorner
bottomRight = Styles.GraphicsBottomRightCorner bottomRight = Styles.GraphicsBottomRightCorner
} }
for x := b.x + 1; x < b.x+b.width-1; x++ { for x := b.x + 1; x < b.x+width-1; x++ {
screen.SetContent(x, b.y, vertical, nil, border) screen.SetContent(x, b.y, vertical, nil, border)
screen.SetContent(x, b.y+b.height-1, vertical, nil, border) screen.SetContent(x, b.y+height-1, vertical, nil, border)
} }
for y := b.y + 1; y < b.y+b.height-1; y++ { for y := b.y + 1; y < b.y+height-1; y++ {
screen.SetContent(b.x, y, horizontal, nil, border) screen.SetContent(b.x, y, horizontal, nil, border)
screen.SetContent(b.x+b.width-1, y, horizontal, nil, border) screen.SetContent(b.x+width-1, y, horizontal, nil, border)
} }
screen.SetContent(b.x, b.y, topLeft, nil, border) screen.SetContent(b.x, b.y, topLeft, nil, border)
screen.SetContent(b.x+b.width-1, b.y, topRight, nil, border) screen.SetContent(b.x+width-1, b.y, topRight, nil, border)
screen.SetContent(b.x, b.y+b.height-1, bottomLeft, nil, border) screen.SetContent(b.x, b.y+height-1, bottomLeft, nil, border)
screen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, border) screen.SetContent(b.x+width-1, b.y+height-1, bottomRight, nil, border)
// Draw title. // Draw title.
if b.title != "" && b.width >= 4 { if b.title != "" && width >= 4 {
title := " " + b.title + " " title := " " + b.title + " "
_, printed := Print(screen, title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor) _, printed := Print(screen, title, b.x+1, b.y, width-2, b.titleAlign, b.titleColor)
if StringWidth(title)-printed > 0 && printed > 0 { if StringWidth(title)-printed > 0 && printed > 0 {
_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y) _, _, style, _ := screen.GetContent(b.x+width-2, b.y)
fg, _, _ := style.Decompose() fg, _, _ := style.Decompose()
Print(screen, string(Styles.GraphicsEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg) Print(screen, string(Styles.GraphicsEllipsis), b.x+width-2, b.y, 1, AlignLeft, fg)
} }
} }
} }
// Call custom draw function. // Call custom draw function.
if b.draw != nil { if b.draw != nil {
b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height) b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, width, height)
} else { } else {
// Remember the inner rect. // Remember the inner rect.
b.innerX = -1 b.innerX = -1

@ -144,7 +144,7 @@ func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Prim
if b.selected != nil { if b.selected != nil {
b.selected() b.selected()
} }
case tcell.KeyBacktab, tcell.KeyTab, tcell.KeyEscape: // Leave. No action. case tcell.KeyBacktab, tcell.KeyTab, tcell.KeyEscape, tcell.KeyLeft, tcell.KeyRight: // Leave. No action.
if b.blur != nil { if b.blur != nil {
b.blur(key) b.blur(key)
} }

@ -209,6 +209,11 @@ func (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return c return c
} }
// GetFinishedFunc returns SetDoneFunc().
func (c *Checkbox) GetFinishedFunc() func(key tcell.Key) {
return c.finished
}
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (c *Checkbox) Draw(screen tcell.Screen) { func (c *Checkbox) Draw(screen tcell.Screen) {
c.Box.Draw(screen) c.Box.Draw(screen)

@ -289,6 +289,11 @@ func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return d return d
} }
// GetFinishedFunc returns SetDoneFunc().
func (d *DropDown) GetFinishedFunc() func(key tcell.Key) {
return d.finished
}
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (d *DropDown) Draw(screen tcell.Screen) { func (d *DropDown) Draw(screen tcell.Screen) {
d.Box.Draw(screen) d.Box.Draw(screen)

@ -83,6 +83,12 @@ func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *F
return f return f
} }
// AddModal adds a new model window
func (f *Flex) AddModal(item Primitive) *Flex {
f.items = append(f.items, flexItem{Item: item, FixedSize: -1, Proportion: 0, Focus: false})
return f
}
// RemoveItem removes all items for the given primitive from the container, // RemoveItem removes all items for the given primitive from the container,
// keeping the order of the remaining items intact. // keeping the order of the remaining items intact.
func (f *Flex) RemoveItem(p Primitive) *Flex { func (f *Flex) RemoveItem(p Primitive) *Flex {
@ -139,7 +145,7 @@ func (f *Flex) Draw(screen tcell.Screen) {
} }
for _, item := range f.items { for _, item := range f.items {
size := item.FixedSize size := item.FixedSize
if size <= 0 { if size == 0 {
size = distSize * item.Proportion / proportionSum size = distSize * item.Proportion / proportionSum
distSize -= size distSize -= size
proportionSum -= item.Proportion proportionSum -= item.Proportion
@ -151,7 +157,9 @@ func (f *Flex) Draw(screen tcell.Screen) {
item.Item.SetRect(x, pos, width, size) item.Item.SetRect(x, pos, width, size)
} }
} }
if size > 0 {
pos += size pos += size
}
if item.Item != nil { if item.Item != nil {
if item.Item.GetFocusable().HasFocus() { if item.Item.GetFocusable().HasFocus() {
@ -175,6 +183,7 @@ func (f *Flex) Focus(delegate func(p Primitive)) {
// HasFocus returns whether or not this primitive has focus. // HasFocus returns whether or not this primitive has focus.
func (f *Flex) HasFocus() bool { func (f *Flex) HasFocus() bool {
//fmt.Println(f.items)
for _, item := range f.items { for _, item := range f.items {
if item.Item != nil && item.Item.GetFocusable().HasFocus() { if item.Item != nil && item.Item.GetFocusable().HasFocus() {
return true return true

@ -2,7 +2,6 @@ package tview
import ( import (
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rivo/tview"
) )
// DefaultFormFieldWidth is the default field screen width of form elements // DefaultFormFieldWidth is the default field screen width of form elements
@ -40,6 +39,8 @@ type FormItem interface {
// Enter key (we're done), the Escape key (cancel input), the Tab key (move to // Enter key (we're done), the Escape key (cancel input), the Tab key (move to
// next field), and the Backtab key (move to previous field). // next field), and the Backtab key (move to previous field).
SetFinishedFunc(handler func(key tcell.Key)) FormItem SetFinishedFunc(handler func(key tcell.Key)) FormItem
GetID() int
} }
// Form allows you to combine multiple one-line form elements into a vertical // Form allows you to combine multiple one-line form elements into a vertical
@ -65,7 +66,7 @@ type Form struct {
buttonsAlign int buttonsAlign int
buttonsPaddingTop int buttonsPaddingTop int
buttonsPaddingSeparate int buttonsIndent int
// The number of empty rows between items. // The number of empty rows between items.
itemPadding int itemPadding int
@ -110,7 +111,7 @@ func NewForm() *Form {
columnPadding: 1, columnPadding: 1,
itemPadding: 1, itemPadding: 1,
buttonsPaddingTop: 2, buttonsPaddingTop: 2,
buttonsPaddingSeparate: 4, buttonsIndent: 4,
labelColor: Styles.LabelTextColor, labelColor: Styles.LabelTextColor,
fieldBackgroundColor: Styles.FieldBackgroundColor, fieldBackgroundColor: Styles.FieldBackgroundColor,
fieldTextColor: Styles.FieldTextColor, fieldTextColor: Styles.FieldTextColor,
@ -146,6 +147,18 @@ func (f *Form) SetColumnPadding(padding int) *Form {
return f return f
} }
// SetButtonPadding sets the number of empty rows between fields.
func (f *Form) SetButtonPadding(padding int) *Form {
f.buttonsPaddingTop = padding
return f
}
// SetButtonIndent makes indent between buttons
func (f *Form) SetButtonIndent(padding int) *Form {
f.buttonsIndent = padding
return f
}
// SetHorizontal sets the direction the form elements are laid out. If set to // SetHorizontal sets the direction the form elements are laid out. If set to
// true, instead of positioning them from top to bottom (the default), they are // true, instead of positioning them from top to bottom (the default), they are
// positioned from left to right, moving into the next row if there is not // positioned from left to right, moving into the next row if there is not
@ -305,6 +318,13 @@ func (f *Form) GetFormItem(index int) FormItem {
return f.items[index] return f.items[index]
} }
// GetFormButton returns the form element at the given position, starting with
// index 0. Elements are referenced in the order they were added. Buttons are
// not included.
func (f *Form) GetFormButton(index int) *Button {
return f.buttons[index]
}
// GetFormItemByLabel returns the first form element with the given label. If // GetFormItemByLabel returns the first form element with the given label. If
// no such element is found, nil is returned. Buttons are not searched and will // no such element is found, nil is returned. Buttons are not searched and will
// therefore not be returned. // therefore not be returned.
@ -344,21 +364,7 @@ func (f *Form) GetRect() (int, int, int, int) {
} }
if height == 0 { if height == 0 {
maxHeight := make([]int, maxColumns) height = f.getMaxHeightColumn()
if len(f.items) > 0 {
for i := 0; i < len(f.items); i++ {
column := f.itemsColumn[i]
_, _, _, h := f.items[i].GetRect()
maxHeight[column] += h + f.itemPadding
}
for column := 0; column < maxColumns; column++ {
if height < maxHeight[column] {
height = maxHeight[column]
}
}
height -= f.itemPadding
}
if len(f.buttons) > 0 { if len(f.buttons) > 0 {
height += 1 + f.buttonsPaddingTop height += 1 + f.buttonsPaddingTop
@ -382,6 +388,28 @@ func (f *Form) getColoumnsCount() int {
return maxColumns + 1 return maxColumns + 1
} }
func (f *Form) getMaxHeightColumn() (height int) {
maxColumns := f.getColoumnsCount()
maxHeight := make([]int, maxColumns)
if len(f.items) > 0 {
for i := 0; i < len(f.items); i++ {
column := f.itemsColumn[i]
_, _, _, h := f.items[i].GetRect()
maxHeight[column] += h + f.itemPadding
}
for column := 0; column < maxColumns; column++ {
if height < maxHeight[column] {
height = maxHeight[column]
}
}
height -= f.itemPadding
}
return
}
func (f *Form) getMaxWidthItems() (maxWidth, maxLabelWidth, maxFieldWidth []int) { func (f *Form) getMaxWidthItems() (maxWidth, maxLabelWidth, maxFieldWidth []int) {
maxColumns := f.getColoumnsCount() maxColumns := f.getColoumnsCount()
@ -442,9 +470,9 @@ func (f *Form) Draw(screen tcell.Screen) {
} }
switch f.align { switch f.align {
case tview.AlignCenter: case AlignCenter:
x += (boxWidth - maxWidth) / 2 x += (boxWidth - maxWidth) / 2
case tview.AlignRight: case AlignRight:
x += boxWidth - maxWidth x += boxWidth - maxWidth
} }
@ -460,8 +488,8 @@ func (f *Form) Draw(screen tcell.Screen) {
var focusedPosition struct{ x, y, width, height int } var focusedPosition struct{ x, y, width, height int }
for index, item := range f.items { for index, item := range f.items {
column := f.itemsColumn[index] column := f.itemsColumn[index]
x = colX[column] x := colX[column]
y = colY[column] y := colY[column]
_, _, leftPadding, rightPadding := item.GetBorderPadding() _, _, leftPadding, rightPadding := item.GetBorderPadding()
labelWidth := item.GetLabelWidth() + leftPadding labelWidth := item.GetLabelWidth() + leftPadding
fieldWidth := item.GetFieldWidth() + rightPadding fieldWidth := item.GetFieldWidth() + rightPadding
@ -523,13 +551,15 @@ func (f *Form) Draw(screen tcell.Screen) {
colY[column] = y colY[column] = y
} }
y = topLimit + f.getMaxHeightColumn()
// How wide are the buttons? // How wide are the buttons?
buttonWidths := make([]int, len(f.buttons)) buttonWidths := make([]int, len(f.buttons))
buttonsWidth := 0 buttonsWidth := 0
for index, button := range f.buttons { for index, button := range f.buttons {
w := StringWidth(button.GetLabel()) + 4 w := StringWidth(button.GetLabel()) + 4
buttonWidths[index] = w buttonWidths[index] = w
buttonsWidth += w + f.buttonsPaddingSeparate buttonsWidth += w + f.buttonsIndent
} }
buttonsWidth-- buttonsWidth--
@ -542,17 +572,16 @@ func (f *Form) Draw(screen tcell.Screen) {
} }
// In vertical layouts, buttons always appear after an empty line. // In vertical layouts, buttons always appear after an empty line.
if f.itemPadding == 0 { // if f.itemPadding == 0 {
y++ // y++
} // }
} }
if len(f.items) > 0 {
y -= f.itemPadding
}
if len(f.buttons) > 0 { if len(f.buttons) > 0 {
y += f.buttonsPaddingTop y += f.buttonsPaddingTop
} }
// Calculate positions of buttons. // Calculate positions of buttons.
for index, button := range f.buttons { for index, button := range f.buttons {
space := rightLimit - x space := rightLimit - x
@ -586,7 +615,7 @@ func (f *Form) Draw(screen tcell.Screen) {
focusedPosition = positions[buttonIndex] focusedPosition = positions[buttonIndex]
} }
x += buttonWidth + f.buttonsPaddingSeparate x += buttonWidth + f.buttonsIndent
} }
// Determine vertical offset based on the position of the focused item. // Determine vertical offset based on the position of the focused item.
@ -651,10 +680,57 @@ func (f *Form) Focus(delegate func(p Primitive)) {
if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) { if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
f.focusedElement = 0 f.focusedElement = 0
} }
handler := func(key tcell.Key) {
itemHandler := func(key tcell.Key) {
switch key { switch key {
case tcell.KeyTab, tcell.KeyEnter: case tcell.KeyTab, tcell.KeyEnter:
previous := f.focusedElement
for {
f.focusedElement++
if f.focusedElement < len(f.items) && f.items[previous].GetID() == f.items[f.focusedElement].GetID() {
continue
}
break
}
f.Focus(delegate)
case tcell.KeyBacktab:
f.focusedElement--
if f.focusedElement < 0 {
f.focusedElement = len(f.items) + len(f.buttons) - 1
}
f.Focus(delegate)
case tcell.KeyEscape:
if f.cancel != nil {
f.cancel()
} else {
f.focusedElement = 0
f.Focus(delegate)
}
}
}
buttonHandler := func(key tcell.Key) {
switch key {
case tcell.KeyRight:
f.focusedElement--
if f.focusedElement <= len(f.items)-1 {
f.focusedElement = len(f.items) + len(f.buttons) - 1
}
f.Focus(delegate)
case tcell.KeyLeft:
f.focusedElement++ f.focusedElement++
if f.focusedElement >= len(f.items)+len(f.buttons) {
f.focusedElement = len(f.items)
}
f.Focus(delegate)
case tcell.KeyTab:
for {
f.focusedElement++
if f.focusedElement >= len(f.items) && f.focusedElement < len(f.items)+len(f.buttons) {
continue
}
break
}
f.Focus(delegate) f.Focus(delegate)
case tcell.KeyBacktab: case tcell.KeyBacktab:
f.focusedElement-- f.focusedElement--
@ -675,12 +751,13 @@ func (f *Form) Focus(delegate func(p Primitive)) {
if f.focusedElement < len(f.items) { if f.focusedElement < len(f.items) {
// We're selecting an item. // We're selecting an item.
item := f.items[f.focusedElement] item := f.items[f.focusedElement]
item.SetFinishedFunc(handler) item.SetFinishedFunc(itemHandler)
delegate(item) delegate(item)
} else { } else {
// We're selecting a button. // We're selecting a button.
button := f.buttons[f.focusedElement-len(f.items)] // fmt.Println(len(f.buttons) - 1 - (f.focusedElement - len(f.items)))
button.SetBlurFunc(handler) button := f.buttons[len(f.buttons)-1-(f.focusedElement-len(f.items))]
button.SetBlurFunc(buttonHandler)
delegate(button) delegate(button)
} }
} }

@ -70,6 +70,8 @@ type InputField struct {
// possible. // possible.
fieldWidth int fieldWidth int
cursorPosition int
// A character to mask entered text (useful for password fields). A value of 0 // A character to mask entered text (useful for password fields). A value of 0
// disables masking. // disables masking.
maskCharacter rune maskCharacter rune
@ -287,6 +289,11 @@ func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return i return i
} }
// GetFinishedFunc returns SetDoneFunc().
func (i *InputField) GetFinishedFunc() func(key tcell.Key) {
return i.finished
}
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (i *InputField) Draw(screen tcell.Screen) { func (i *InputField) Draw(screen tcell.Screen) {
i.Box.Draw(screen) i.Box.Draw(screen)
@ -362,14 +369,27 @@ func (i *InputField) Draw(screen tcell.Screen) {
Print(screen, i.placeholder, x, y, fieldWidth, AlignLeft, i.placeholderTextColor) Print(screen, i.placeholder, x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
} }
textWidth := runewidth.StringWidth(text)
// Draw entered text. // Draw entered text.
if i.maskCharacter > 0 { if i.maskCharacter > 0 {
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text)) text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
} }
if i.cursorPosition < 0 {
i.cursorPosition = 0
}
if i.cursorPosition > textWidth {
i.cursorPosition = textWidth
}
fieldWidth-- // We need one cell for the cursor. fieldWidth-- // We need one cell for the cursor.
if fieldWidth < runewidth.StringWidth(text) {
if fieldWidth < textWidth {
runes := []rune(text) runes := []rune(text)
for pos := len(runes) - 1; pos >= 0; pos-- { p := len(runes)
if i.cursorPosition-1 < textWidth-fieldWidth {
p = i.cursorPosition + fieldWidth
}
for pos := p - 1; pos >= 0; pos-- {
ch := runes[pos] ch := runes[pos]
w := runewidth.RuneWidth(ch) w := runewidth.RuneWidth(ch)
if fieldWidth-w < 0 { if fieldWidth-w < 0 {
@ -413,14 +433,28 @@ func (i *InputField) setCursor(screen tcell.Screen) {
y++ y++
rightLimit -= 2 rightLimit -= 2
} }
fieldWidth := runewidth.StringWidth(i.text)
if i.fieldWidth > 0 && fieldWidth > i.fieldWidth-1 { cursorIndent := i.cursorPosition
fieldWidth = i.fieldWidth - 1
textWidth := runewidth.StringWidth(i.text)
if textWidth > i.fieldWidth {
overflow := textWidth - (i.fieldWidth - 2)
if overflow > textWidth-cursorIndent {
}
cursorIndent -= overflow
}
if cursorIndent < 0 {
cursorIndent = 0
}
if i.fieldWidth > 0 && cursorIndent > i.fieldWidth-1 {
cursorIndent = i.fieldWidth - 1
} }
if i.labelWidth > 0 { if i.labelWidth > 0 {
x += i.labelWidth + 1 + fieldWidth x += i.labelWidth + 1 + cursorIndent
} else { } else {
x += StringWidth(i.subLabel+i.label) + 1 + fieldWidth x += StringWidth(i.subLabel+i.label) + 1 + cursorIndent
} }
if x >= rightLimit { if x >= rightLimit {
x = rightLimit - 1 x = rightLimit - 1
@ -442,12 +476,13 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
// Process key event. // Process key event.
switch key := event.Key(); key { switch key := event.Key(); key {
case tcell.KeyRune: // Regular character. case tcell.KeyRune: // Regular character.
newText := i.text + string(event.Rune()) newText := i.text[0:i.cursorPosition] + string(event.Rune()) + i.text[i.cursorPosition:]
if i.accept != nil { if i.accept != nil {
if !i.accept(newText, event.Rune()) { if !i.accept(newText, event.Rune()) {
break break
} }
} }
i.cursorPosition++
i.text = newText i.text = newText
case tcell.KeyCtrlU: // Delete all. case tcell.KeyCtrlU: // Delete all.
i.text = "" i.text = ""
@ -455,11 +490,30 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
lastWord := regexp.MustCompile(`\s*\S+\s*$`) lastWord := regexp.MustCompile(`\s*\S+\s*$`)
i.text = lastWord.ReplaceAllString(i.text, "") i.text = lastWord.ReplaceAllString(i.text, "")
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character. case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
if len(i.text) == 0 { if len(i.text) == 0 || i.cursorPosition < 1 {
break
}
newText := i.text[0:i.cursorPosition-1] + i.text[i.cursorPosition:]
runes := []rune(i.text)
if i.accept != nil {
if !i.accept(newText, runes[i.cursorPosition-1]) {
break
}
}
i.cursorPosition--
i.text = newText
case tcell.KeyDelete:
if len(i.text) == 0 || i.cursorPosition < 1 || len(i.text) == i.cursorPosition {
break break
} }
newText := i.text[0:i.cursorPosition] + i.text[i.cursorPosition+1:]
runes := []rune(i.text) runes := []rune(i.text)
i.text = string(runes[:len(runes)-1]) if i.accept != nil {
if !i.accept(newText, runes[i.cursorPosition]) {
break
}
}
i.text = newText
case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done. case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
if i.done != nil { if i.done != nil {
i.done(key) i.done(key)
@ -467,6 +521,14 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
if i.finished != nil { if i.finished != nil {
i.finished(key) i.finished(key)
} }
case tcell.KeyLeft:
i.cursorPosition--
case tcell.KeyRight:
i.cursorPosition++
case tcell.KeyHome:
i.cursorPosition = 0
case tcell.KeyEnd:
i.cursorPosition = runewidth.StringWidth(i.text)
} }
}) })
} }

@ -56,6 +56,7 @@ type ListBox struct {
// The index of the currently selected item. // The index of the currently selected item.
currentItem int currentItem int
offset int
// Whether or not to show the secondary item texts. // Whether or not to show the secondary item texts.
showSecondaryText bool showSecondaryText bool
@ -84,6 +85,10 @@ type ListBox struct {
// An optional function which is called when the user presses the Escape key. // An optional function which is called when the user presses the Escape key.
done func(tcell.Key) done func(tcell.Key)
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
} }
// NewListBox returns a new form. // NewListBox returns a new form.
@ -96,8 +101,8 @@ func NewListBox() *ListBox {
selectedTextColor: Styles.PrimitiveBackgroundColor, selectedTextColor: Styles.PrimitiveBackgroundColor,
selectedBackgroundColor: Styles.PrimaryTextColor, selectedBackgroundColor: Styles.PrimaryTextColor,
labelColor: Styles.SecondaryTextColor, labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor, fieldBackgroundColor: Styles.FieldBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor, fieldTextColor: Styles.FieldTextColor,
align: AlignLeft, align: AlignLeft,
labelFiller: " ", labelFiller: " ",
} }
@ -109,7 +114,9 @@ func NewListBox() *ListBox {
// SetCurrentItem sets the currently selected item by its index. This triggers // SetCurrentItem sets the currently selected item by its index. This triggers
// a "changed" event. // a "changed" event.
func (l *ListBox) SetCurrentItem(index int) *ListBox { func (l *ListBox) SetCurrentItem(index int) *ListBox {
_, _, _, height := l.GetInnerRect()
l.currentItem = index l.currentItem = index
l.offset = l.currentItem - height/2
if l.currentItem < len(l.items) && l.changed != nil { if l.currentItem < len(l.items) && l.changed != nil {
item := l.items[l.currentItem] item := l.items[l.currentItem]
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
@ -134,6 +141,14 @@ func (l *ListBox) GetCurrentItem() int {
return l.currentItem return l.currentItem
} }
// GetCurrentItemText returns the index of the currently selected list item.
func (l *ListBox) GetCurrentItemText() string {
if len(l.items) == 0 {
return ""
}
return l.items[l.currentItem].MainText
}
// SetMainTextColor sets the color of the items' main text. // SetMainTextColor sets the color of the items' main text.
func (l *ListBox) SetMainTextColor(color tcell.Color) *ListBox { func (l *ListBox) SetMainTextColor(color tcell.Color) *ListBox {
l.mainTextColor = color l.mainTextColor = color
@ -249,20 +264,21 @@ func (l *ListBox) Draw(screen tcell.Screen) {
} }
// We want to keep the current selection in view. What is our offset? // We want to keep the current selection in view. What is our offset?
var offset int
if l.showSecondaryText { if l.showSecondaryText {
if l.currentItem >= height/2 { if l.currentItem >= height/2 {
offset = l.currentItem + 1 - (height / 2) l.offset = l.currentItem + 1 - (height / 2)
} }
} else { } else {
if l.currentItem >= height { if l.offset > 0 && l.offset > l.currentItem {
offset = l.currentItem + 1 - height l.offset--
} else if l.currentItem >= height && l.offset <= l.currentItem-height {
l.offset = l.currentItem + 1 - height
} }
} }
// Draw the list items. // Draw the list items.
for index, item := range l.items { for index, item := range l.items {
if index < offset { if index < l.offset {
continue continue
} }
@ -287,11 +303,14 @@ func (l *ListBox) Draw(screen tcell.Screen) {
if fg == l.mainTextColor { if fg == l.mainTextColor {
fg = l.selectedTextColor fg = l.selectedTextColor
} }
style = style.Background(l.selectedBackgroundColor).Foreground(fg) if l.focus.HasFocus() {
style = style.Background(l.fieldBackgroundColor).Foreground(fg)
} else {
style = style.Background(l.fieldTextColor).Foreground(l.fieldBackgroundColor).Underline(true)
}
screen.SetContent(x+bx, y, m, c, style) screen.SetContent(x+bx, y, m, c, style)
} }
} }
y++ y++
if y >= bottomLimit { if y >= bottomLimit {
@ -313,19 +332,49 @@ func (l *ListBox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pri
previousItem := l.currentItem previousItem := l.currentItem
switch key := event.Key(); key { switch key := event.Key(); key {
case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight: case tcell.KeyTab, tcell.KeyBacktab: // We're done.
if l.done != nil {
l.done(key)
}
if l.finished != nil {
l.finished(key)
}
case tcell.KeyDown, tcell.KeyRight:
l.currentItem++ l.currentItem++
case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft: if l.currentItem >= len(l.items) {
l.currentItem = 0
l.offset = 0
}
case tcell.KeyUp, tcell.KeyLeft:
l.currentItem-- l.currentItem--
if l.currentItem < 0 {
l.currentItem = len(l.items) - 1
}
case tcell.KeyHome: case tcell.KeyHome:
l.currentItem = 0 l.currentItem = 0
case tcell.KeyEnd: case tcell.KeyEnd:
l.currentItem = len(l.items) - 1 l.currentItem = len(l.items) - 1
case tcell.KeyPgDn: case tcell.KeyPgDn:
if l.currentItem < l.offset+height-1 {
l.currentItem = l.offset + height - 1
} else {
l.currentItem += height l.currentItem += height
}
if l.currentItem >= len(l.items) {
l.currentItem = 0
l.offset = 0
}
case tcell.KeyPgUp: case tcell.KeyPgUp:
if l.currentItem > l.offset {
l.currentItem = l.offset
} else {
l.currentItem -= height l.currentItem -= height
l.offset = l.currentItem
}
case tcell.KeyEnter: case tcell.KeyEnter:
if len(l.items) == 0 {
break
}
item := l.items[l.currentItem] item := l.items[l.currentItem]
if item.Selected != nil { if item.Selected != nil {
item.Selected() item.Selected()
@ -337,6 +386,9 @@ func (l *ListBox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pri
if l.done != nil { if l.done != nil {
l.done(key) l.done(key)
} }
if l.finished != nil {
l.finished(key)
}
case tcell.KeyRune: case tcell.KeyRune:
ch := event.Rune() ch := event.Rune()
if ch != ' ' { if ch != ' ' {
@ -439,7 +491,13 @@ func (l *ListBox) GetLabel() string {
// SetFinishedFunc calls SetDoneFunc(). // SetFinishedFunc calls SetDoneFunc().
func (l *ListBox) SetFinishedFunc(handler func(key tcell.Key)) FormItem { func (l *ListBox) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return l.SetDoneFunc(handler) l.finished = handler
return l
}
// GetFinishedFunc returns SetDoneFunc().
func (l *ListBox) GetFinishedFunc() func(key tcell.Key) {
return l.finished
} }
// SetFormAttributes sets attributes shared by all form items. // SetFormAttributes sets attributes shared by all form items.

@ -40,9 +40,10 @@ func NewModal() *Modal {
} }
m.form = NewForm(). m.form = NewForm().
SetButtonsAlign(AlignCenter). SetButtonsAlign(AlignCenter).
SetButtonBackgroundColor(Styles.PrimitiveBackgroundColor). SetButtonBackgroundColor(Styles.ButtonBackgroundColor).
SetButtonTextColor(Styles.PrimaryTextColor) SetButtonTextColor(Styles.ButtonTextColor)
m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0) m.form.SetButtonPadding(0)
m.form.SetBackgroundColor(Styles.ModalBackgroundColor).SetBorderPadding(0, 0, 0, 0)
m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0) m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
m.frame.SetBorder(true). m.frame.SetBorder(true).
SetBackgroundColor(Styles.ModalBackgroundColor). SetBackgroundColor(Styles.ModalBackgroundColor).
@ -74,6 +75,12 @@ func (m *Modal) SetText(text string) *Modal {
return m return m
} }
// SetTip is hard code, which will need to fix
func (m *Modal) SetTip() *Modal {
m.form.AddFormItem(NewInputField())
return m
}
// AddButtons adds buttons to the window. There must be at least one button and // AddButtons adds buttons to the window. There must be at least one button and
// a "done" handler so the window can be closed again. // a "done" handler so the window can be closed again.
func (m *Modal) AddButtons(labels []string) *Modal { func (m *Modal) AddButtons(labels []string) *Modal {

@ -227,6 +227,7 @@ func (p *Pages) Focus(delegate func(p Primitive)) {
topItem = page.Item topItem = page.Item
} }
} }
if topItem != nil { if topItem != nil {
delegate(topItem) delegate(topItem)
} }

@ -76,11 +76,17 @@ type RadioButtons struct {
labelWidth int labelWidth int
joinElements []*RadioButtons
currentElement int
// An optional function which is called when the user indicated that they // An optional function which is called when the user indicated that they
// are done selecting options. The key which was pressed is provided (tab, // are done selecting options. The key which was pressed is provided (tab,
// shift-tab, or escape). // shift-tab, or escape).
done func(tcell.Key) done func(tcell.Key)
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
// If set to true, instead of position items and buttons from top to bottom, // If set to true, instead of position items and buttons from top to bottom,
// they are positioned from left to right. // they are positioned from left to right.
horizontal bool horizontal bool
@ -90,6 +96,8 @@ type RadioButtons struct {
// An optional function which is called when the user has navigated to a list // An optional function which is called when the user has navigated to a list
// item. // item.
changed func(*RadioOption) changed func(*RadioOption)
inputHandler func() func(event *tcell.EventKey, setFocus func(p Primitive))
} }
// NewRadioButtons returns a new radio button primitive. // NewRadioButtons returns a new radio button primitive.
@ -108,10 +116,23 @@ func NewRadioButtons() *RadioButtons {
} }
r.focus = r r.focus = r
r.joinElements = append(r.joinElements, r)
r.inputHandler = r.defaultInputHandler
return r return r
} }
// Join combines two element the same type in one, for navigation
func (r *RadioButtons) Join(elements ...*RadioButtons) *RadioButtons {
for i := 0; i < len(elements); i++ {
elements[i].inputHandler = r.inputHandler
elements[i].id = r.id
elements[i].currentOption = -1
}
r.joinElements = append(r.joinElements, elements...)
return r
}
// SetOptions replaces all current options with the ones provided // SetOptions replaces all current options with the ones provided
func (r *RadioButtons) SetOptions(options []*RadioOption) *RadioButtons { func (r *RadioButtons) SetOptions(options []*RadioOption) *RadioButtons {
r.options = options r.options = options
@ -133,28 +154,81 @@ func (r *RadioButtons) SetAlign(align int) *RadioButtons {
return r return r
} }
// Focus is called when this primitive receives focus.
func (r *RadioButtons) Focus(delegate func(p Primitive)) {
if r != r.joinElements[r.currentElement] {
delegate(r.joinElements[r.currentElement])
return
}
r.hasFocus = true
}
// InputHandler returns the handler for this primitive. // InputHandler returns the handler for this primitive.
func (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { func (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
if r.inputHandler != nil {
return r.inputHandler()
}
return r.Box.InputHandler()
}
// InputHandler returns the handler for this primitive.
func (r *RadioButtons) defaultInputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return r.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { return r.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
parent := r
r = parent.joinElements[parent.currentElement]
switch key := event.Key(); key { switch key := event.Key(); key {
case tcell.KeyUp, tcell.KeyLeft: case tcell.KeyUp, tcell.KeyLeft:
r.currentOption-- r.currentOption--
if r.currentOption < 0 { if r.currentOption < 0 {
if len(parent.joinElements) > 1 {
parent.currentElement--
if parent.currentElement < 0 {
parent.currentElement = len(parent.joinElements) - 1
}
nextElement := parent.joinElements[parent.currentElement]
setFocus(nextElement)
nextElement.currentOption = len(nextElement.options) - 1
break
}
r.currentOption = len(r.options) - 1 - int(math.Mod(float64(len(r.options)), float64(r.currentOption))) r.currentOption = len(r.options) - 1 - int(math.Mod(float64(len(r.options)), float64(r.currentOption)))
} }
if r.changed != nil {
r.changed(r.options[r.currentOption])
}
case tcell.KeyDown, tcell.KeyRight: case tcell.KeyDown, tcell.KeyRight:
r.currentOption++ r.currentOption++
if r.currentOption >= len(r.options) { if r.currentOption >= len(r.options) {
if len(parent.joinElements) > 1 {
parent.currentElement++
if parent.currentElement >= len(parent.joinElements) {
parent.currentElement = 0
}
nextElement := parent.joinElements[parent.currentElement]
setFocus(nextElement)
nextElement.currentOption = 0
break
}
r.currentOption = int(math.Mod(float64(len(r.options)), float64(r.currentOption))) r.currentOption = int(math.Mod(float64(len(r.options)), float64(r.currentOption)))
} }
if r.changed != nil {
r.changed(r.options[r.currentOption])
}
case tcell.KeyEnter, tcell.KeyRune: // We're done. case tcell.KeyEnter, tcell.KeyRune: // We're done.
r.selectedOption = r.currentOption for index, element := range parent.joinElements {
if parent.currentElement != index {
element.currentOption = -1
}
}
if r.changed != nil { if r.changed != nil {
r.changed(r.options[r.currentOption]) r.changed(r.options[r.currentOption])
} }
case tcell.KeyTab, tcell.KeyBacktab: // We're done. case tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
if r.done != nil { if parent.done != nil {
r.done(key) parent.done(key)
}
if parent.finished != nil {
parent.finished(key)
} }
} }
}) })
@ -261,7 +335,13 @@ func (r *RadioButtons) SetDoneFunc(handler func(key tcell.Key)) *RadioButtons {
// SetFinishedFunc calls SetDoneFunc(). // SetFinishedFunc calls SetDoneFunc().
func (r *RadioButtons) SetFinishedFunc(handler func(key tcell.Key)) FormItem { func (r *RadioButtons) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return r.SetDoneFunc(handler) r.finished = handler
return r
}
// GetFinishedFunc returns SetDoneFunc().
func (r *RadioButtons) GetFinishedFunc() func(key tcell.Key) {
return r.finished
} }
// SetLabel sets the text to be displayed before the input area. // SetLabel sets the text to be displayed before the input area.
@ -328,9 +408,9 @@ func (r *RadioButtons) SetHorizontal(horizontal bool) *RadioButtons {
func (r *RadioButtons) SetCurrentOptionByName(name string) *RadioButtons { func (r *RadioButtons) SetCurrentOptionByName(name string) *RadioButtons {
for i := 0; i < len(r.options); i++ { for i := 0; i < len(r.options); i++ {
if r.options[i].Name == name { if r.options[i].Name == name {
r.selectedOption = i r.currentOption = i
if r.changed != nil { if r.changed != nil {
r.changed(r.options[r.selectedOption]) r.changed(r.options[r.currentOption])
} }
break break
} }
@ -341,9 +421,9 @@ func (r *RadioButtons) SetCurrentOptionByName(name string) *RadioButtons {
// SetCurrentOption sets the index of the currently selected option. This may // SetCurrentOption sets the index of the currently selected option. This may
// be a negative value to indicate that no option is currently selected. // be a negative value to indicate that no option is currently selected.
func (r *RadioButtons) SetCurrentOption(index int) *RadioButtons { func (r *RadioButtons) SetCurrentOption(index int) *RadioButtons {
r.selectedOption = index r.currentOption = index
if r.changed != nil { if r.changed != nil {
r.changed(r.options[r.selectedOption]) r.changed(r.options[r.currentOption])
} }
return r return r
} }
@ -351,12 +431,20 @@ func (r *RadioButtons) SetCurrentOption(index int) *RadioButtons {
// GetCurrentOption returns the index of the currently selected option as well // GetCurrentOption returns the index of the currently selected option as well
// as its text. If no option was selected, -1 and an empty string is returned. // as its text. If no option was selected, -1 and an empty string is returned.
func (r *RadioButtons) GetCurrentOption() *RadioOption { func (r *RadioButtons) GetCurrentOption() *RadioOption {
return r.options[r.selectedOption] r = r.joinElements[r.currentElement]
if len(r.options) > r.currentOption {
return r.options[r.currentOption]
}
return nil
} }
// GetCurrentOptionName returns the name of the currently selected option. // GetCurrentOptionName returns the name of the currently selected option.
func (r *RadioButtons) GetCurrentOptionName() string { func (r *RadioButtons) GetCurrentOptionName() string {
return r.options[r.selectedOption].Name r = r.joinElements[r.currentElement]
if len(r.options) > r.currentOption {
return r.options[r.currentOption].Name
}
return ""
} }
// SetChangedFunc sets the function which is called when the user navigates to // SetChangedFunc sets the function which is called when the user navigates to
@ -420,7 +508,7 @@ func (r *RadioButtons) Draw(screen tcell.Screen) {
break break
} }
radioButton := Styles.GraphicsRadioUnchecked // Unchecked. radioButton := Styles.GraphicsRadioUnchecked // Unchecked.
if index == r.selectedOption { if index == r.currentOption {
radioButton = Styles.GraphicsRadioChecked // Checked. radioButton = Styles.GraphicsRadioChecked // Checked.
} }

@ -411,7 +411,14 @@ func (t *Table) GetColumnCount() int {
// GetPageCount returns quantity of pages // GetPageCount returns quantity of pages
func (t *Table) GetPageCount() int { func (t *Table) GetPageCount() int {
return t.GetRowCount() / t.visibleRows _, _, _, height := t.GetInnerRect()
visibleRows := height
if t.borders {
visibleRows = height / 2
}
return t.GetRowCount() / visibleRows
} }
// ScrollToBeginning scrolls the table to the beginning to that the top left // ScrollToBeginning scrolls the table to the beginning to that the top left

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"regexp" "regexp"
"strings"
"sync" "sync"
"unicode/utf8" "unicode/utf8"
@ -85,6 +86,8 @@ type TextView struct {
sync.Mutex sync.Mutex
*Box *Box
maxWidth, maxHeight, minWidth, minHeight int
// The text buffer. // The text buffer.
buffer []string buffer []string
@ -160,11 +163,15 @@ type TextView struct {
done func(tcell.Key) done func(tcell.Key)
scrollEnded func(screen tcell.Screen) scrollEnded func(screen tcell.Screen)
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
} }
// NewTextView returns a new text view. // NewTextView returns a new text view.
func NewTextView() *TextView { func NewTextView() *TextView {
return &TextView{ textview := &TextView{
Box: NewBox(), Box: NewBox(),
highlights: make(map[string]struct{}), highlights: make(map[string]struct{}),
lineOffset: -1, lineOffset: -1,
@ -174,6 +181,23 @@ func NewTextView() *TextView {
textColor: Styles.PrimaryTextColor, textColor: Styles.PrimaryTextColor,
dynamicColors: false, dynamicColors: false,
} }
textview.height = 0
return textview
}
// SetMinSize sets min size of the box
func (t *TextView) SetMinSize(width, height int) *TextView {
t.minWidth = width
t.minHeight = height
return t
}
// SetMaxSize sets max size of the box
func (t *TextView) SetMaxSize(width, height int) *TextView {
t.maxWidth = width
t.maxHeight = height
return t
} }
// SetScrollable sets the flag that decides whether or not the text view is // SetScrollable sets the flag that decides whether or not the text view is
@ -648,6 +672,31 @@ func (t *TextView) reindexBuffer(width int) {
} }
} }
// GetInnerRect returns the position of the inner rectangle (x, y, width,
// height), without the border and without any padding.
func (t *TextView) GetInnerRect() (int, int, int, int) {
x, y, boxWidth, boxHeight := t.Box.GetInnerRect()
width := boxWidth
switch {
case t.maxWidth > 0 && width > t.maxWidth:
width = t.maxWidth
x += (boxWidth - width) / 2
case t.minWidth > 0 && width < t.minWidth:
width = t.minWidth
}
height := boxHeight
switch {
case t.maxHeight > 0 && height > t.maxHeight:
height = t.maxHeight
case t.minHeight > 0 && height < t.minHeight:
height = t.minHeight
}
return x, y, width, height
}
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
func (t *TextView) Draw(screen tcell.Screen) { func (t *TextView) Draw(screen tcell.Screen) {
t.Lock() t.Lock()
@ -844,8 +893,8 @@ func (t *TextView) Draw(screen tcell.Screen) {
if highlighted { if highlighted {
fg, bg, _ := style.Decompose() fg, bg, _ := style.Decompose()
if bg == tcell.ColorDefault { if bg == tcell.ColorDefault {
r, g, b := fg.RGB() r, g, t := fg.RGB()
c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255} c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(t) / 255}
_, _, li := c.Hcl() _, _, li := c.Hcl()
if li < .5 { if li < .5 {
bg = tcell.ColorWhite bg = tcell.ColorWhite
@ -878,11 +927,13 @@ func (t *TextView) Draw(screen tcell.Screen) {
func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
key := event.Key() key := event.Key()
if key == tcell.KeyEscape || key == tcell.KeyEnter || key == tcell.KeyTab || key == tcell.KeyBacktab { if key == tcell.KeyEscape || key == tcell.KeyEnter || key == tcell.KeyTab || key == tcell.KeyBacktab {
if t.done != nil { if t.done != nil {
t.done(key) t.done(key)
} }
if t.finished != nil {
t.finished(key)
}
return return
} }
@ -934,3 +985,58 @@ func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
} }
}) })
} }
// GetLabel returns the text to be displayed before the input area.
func (t *TextView) GetLabel() string {
return strings.Join(t.buffer, " ")
}
// GetLabelWidth returns label width.
func (t *TextView) GetLabelWidth() int {
return StringWidth(strings.Replace(strings.Join(t.buffer, " "), "%s", "", -1))
}
// SetFieldAlign sets the input alignment within the radiobutton box. This must be
// either AlignLeft, AlignCenter, or AlignRight.
func (t *TextView) SetFieldAlign(align int) FormItem {
t.align = align
return t
}
// GetFieldWidth returns this primitive's field width.
func (t *TextView) GetFieldWidth() int {
return 0
}
// GetFieldAlign returns the input alignment within the radiobutton box.
func (t *TextView) GetFieldAlign() (align int) {
return t.align
}
// SetFormAttributes sets attributes shared by all form items.
func (t *TextView) SetFormAttributes(labelWidth, fieldWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
t.height = 1
t.textColor = labelColor
t.backgroundColor = bgColor
return t
}
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
func (t *TextView) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
t.finished = handler
return t
}
// GetFinishedFunc returns SetDoneFunc().
func (t *TextView) GetFinishedFunc() func(key tcell.Key) {
return t.finished
}
// Focus is called when this primitive receives focus.
func (t *TextView) Focus(delegate func(p Primitive)) {
if t.finished != nil {
t.finished(tcell.KeyTAB)
return
}
t.hasFocus = true
}

Loading…
Cancel
Save