New features

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

@ -1,6 +1,8 @@
package tview
import (
"math/rand"
"github.com/gdamore/tcell"
)
@ -13,9 +15,12 @@ import (
//
// See https://github.com/rivo/tview/wiki/Box for an example.
type Box struct {
id int
// The position of the rect.
x, y, width, height int
maxWidth, maxHeight, minWidth, minHeight int
// The inner rect reserved for the box's content.
innerX, innerY, innerWidth, innerHeight int
@ -67,6 +72,7 @@ type Box struct {
// NewBox returns a Box without a border.
func NewBox() *Box {
b := &Box{
id: rand.Int(),
width: 15,
height: 10,
innerX: -1, // Mark as uninitialized.
@ -80,15 +86,29 @@ func NewBox() *Box {
return b
}
// SetHeight sets height of the box
func (b *Box) SetHeight(height int) *Box {
// GetID returns unique id of 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
return b
}
// SetWidth sets width of the box
func (b *Box) SetWidth(width int) *Box {
b.width = width
// SetMinSize sets min size of the box
func (b *Box) SetMinSize(width, height int) *Box {
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
}
@ -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
// height.
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,
@ -256,8 +292,9 @@ func (b *Box) SetTitleAlign(align int) *Box {
// Draw draws this primitive onto the screen.
func (b *Box) Draw(screen tcell.Screen) {
_, _, width, height := b.GetRect()
// Don't draw anything if there is no space.
if b.width <= 0 || b.height <= 0 {
if width <= 0 || height <= 0 {
return
}
@ -265,14 +302,14 @@ func (b *Box) Draw(screen tcell.Screen) {
// 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++ {
for y := b.y; y < b.y+height; y++ {
for x := b.x; x < b.x+width; x++ {
screen.SetContent(x, y, ' ', nil, background)
}
}
// Draw border.
if b.border && b.width >= 2 && b.height >= 2 {
if b.border && width >= 2 && height >= 2 {
border := background.Foreground(b.borderColor)
var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
if b.focus.HasFocus() {
@ -290,34 +327,34 @@ func (b *Box) Draw(screen tcell.Screen) {
bottomLeft = Styles.GraphicsBottomLeftCorner
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+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+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.width-1, b.y, topRight, nil, border)
screen.SetContent(b.x, b.y+b.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, topRight, nil, border)
screen.SetContent(b.x, b.y+height-1, bottomLeft, nil, border)
screen.SetContent(b.x+width-1, b.y+height-1, bottomRight, nil, border)
// Draw title.
if b.title != "" && b.width >= 4 {
if b.title != "" && width >= 4 {
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 {
_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
_, _, style, _ := screen.GetContent(b.x+width-2, b.y)
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.
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 {
// Remember the inner rect.
b.innerX = -1

@ -144,7 +144,7 @@ func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Prim
if b.selected != nil {
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 {
b.blur(key)
}

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

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

@ -83,6 +83,12 @@ func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *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,
// keeping the order of the remaining items intact.
func (f *Flex) RemoveItem(p Primitive) *Flex {
@ -139,7 +145,7 @@ func (f *Flex) Draw(screen tcell.Screen) {
}
for _, item := range f.items {
size := item.FixedSize
if size <= 0 {
if size == 0 {
size = distSize * item.Proportion / proportionSum
distSize -= size
proportionSum -= item.Proportion
@ -151,7 +157,9 @@ func (f *Flex) Draw(screen tcell.Screen) {
item.Item.SetRect(x, pos, width, size)
}
}
pos += size
if size > 0 {
pos += size
}
if item.Item != nil {
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.
func (f *Flex) HasFocus() bool {
//fmt.Println(f.items)
for _, item := range f.items {
if item.Item != nil && item.Item.GetFocusable().HasFocus() {
return true

@ -2,7 +2,6 @@ package tview
import (
"github.com/gdamore/tcell"
"github.com/rivo/tview"
)
// 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
// next field), and the Backtab key (move to previous field).
SetFinishedFunc(handler func(key tcell.Key)) FormItem
GetID() int
}
// Form allows you to combine multiple one-line form elements into a vertical
@ -64,8 +65,8 @@ type Form struct {
// The alignment of the buttons.
buttonsAlign int
buttonsPaddingTop int
buttonsPaddingSeparate int
buttonsPaddingTop int
buttonsIndent int
// The number of empty rows between items.
itemPadding int
@ -105,17 +106,17 @@ func NewForm() *Form {
box := NewBox().SetBorderPadding(1, 1, 1, 1)
f := &Form{
Box: box,
align: AlignLeft,
columnPadding: 1,
itemPadding: 1,
buttonsPaddingTop: 2,
buttonsPaddingSeparate: 4,
labelColor: Styles.LabelTextColor,
fieldBackgroundColor: Styles.FieldBackgroundColor,
fieldTextColor: Styles.FieldTextColor,
buttonBackgroundColor: Styles.ButtonBackgroundColor,
buttonTextColor: Styles.ButtonTextColor,
Box: box,
align: AlignLeft,
columnPadding: 1,
itemPadding: 1,
buttonsPaddingTop: 2,
buttonsIndent: 4,
labelColor: Styles.LabelTextColor,
fieldBackgroundColor: Styles.FieldBackgroundColor,
fieldTextColor: Styles.FieldTextColor,
buttonBackgroundColor: Styles.ButtonBackgroundColor,
buttonTextColor: Styles.ButtonTextColor,
}
f.width = 0
@ -146,6 +147,18 @@ func (f *Form) SetColumnPadding(padding int) *Form {
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
// 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
@ -305,6 +318,13 @@ func (f *Form) GetFormItem(index int) FormItem {
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
// no such element is found, nil is returned. Buttons are not searched and will
// therefore not be returned.
@ -344,21 +364,7 @@ func (f *Form) GetRect() (int, int, int, int) {
}
if height == 0 {
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
}
height = f.getMaxHeightColumn()
if len(f.buttons) > 0 {
height += 1 + f.buttonsPaddingTop
@ -382,6 +388,28 @@ func (f *Form) getColoumnsCount() int {
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) {
maxColumns := f.getColoumnsCount()
@ -442,9 +470,9 @@ func (f *Form) Draw(screen tcell.Screen) {
}
switch f.align {
case tview.AlignCenter:
case AlignCenter:
x += (boxWidth - maxWidth) / 2
case tview.AlignRight:
case AlignRight:
x += boxWidth - maxWidth
}
@ -460,8 +488,8 @@ func (f *Form) Draw(screen tcell.Screen) {
var focusedPosition struct{ x, y, width, height int }
for index, item := range f.items {
column := f.itemsColumn[index]
x = colX[column]
y = colY[column]
x := colX[column]
y := colY[column]
_, _, leftPadding, rightPadding := item.GetBorderPadding()
labelWidth := item.GetLabelWidth() + leftPadding
fieldWidth := item.GetFieldWidth() + rightPadding
@ -523,13 +551,15 @@ func (f *Form) Draw(screen tcell.Screen) {
colY[column] = y
}
y = topLimit + f.getMaxHeightColumn()
// How wide are the buttons?
buttonWidths := make([]int, len(f.buttons))
buttonsWidth := 0
for index, button := range f.buttons {
w := StringWidth(button.GetLabel()) + 4
buttonWidths[index] = w
buttonsWidth += w + f.buttonsPaddingSeparate
buttonsWidth += w + f.buttonsIndent
}
buttonsWidth--
@ -542,17 +572,16 @@ func (f *Form) Draw(screen tcell.Screen) {
}
// In vertical layouts, buttons always appear after an empty line.
if f.itemPadding == 0 {
y++
}
// if f.itemPadding == 0 {
// y++
// }
}
if len(f.items) > 0 {
y -= f.itemPadding
}
if len(f.buttons) > 0 {
y += f.buttonsPaddingTop
}
// Calculate positions of buttons.
for index, button := range f.buttons {
space := rightLimit - x
@ -586,7 +615,7 @@ func (f *Form) Draw(screen tcell.Screen) {
focusedPosition = positions[buttonIndex]
}
x += buttonWidth + f.buttonsPaddingSeparate
x += buttonWidth + f.buttonsIndent
}
// 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) {
f.focusedElement = 0
}
handler := func(key tcell.Key) {
itemHandler := func(key tcell.Key) {
switch key {
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++
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)
case tcell.KeyBacktab:
f.focusedElement--
@ -675,12 +751,13 @@ func (f *Form) Focus(delegate func(p Primitive)) {
if f.focusedElement < len(f.items) {
// We're selecting an item.
item := f.items[f.focusedElement]
item.SetFinishedFunc(handler)
item.SetFinishedFunc(itemHandler)
delegate(item)
} else {
// We're selecting a button.
button := f.buttons[f.focusedElement-len(f.items)]
button.SetBlurFunc(handler)
// fmt.Println(len(f.buttons) - 1 - (f.focusedElement - len(f.items)))
button := f.buttons[len(f.buttons)-1-(f.focusedElement-len(f.items))]
button.SetBlurFunc(buttonHandler)
delegate(button)
}
}

@ -70,6 +70,8 @@ type InputField struct {
// possible.
fieldWidth int
cursorPosition int
// A character to mask entered text (useful for password fields). A value of 0
// disables masking.
maskCharacter rune
@ -287,6 +289,11 @@ func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return i
}
// GetFinishedFunc returns SetDoneFunc().
func (i *InputField) GetFinishedFunc() func(key tcell.Key) {
return i.finished
}
// Draw draws this primitive onto the screen.
func (i *InputField) Draw(screen tcell.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)
}
textWidth := runewidth.StringWidth(text)
// Draw entered text.
if i.maskCharacter > 0 {
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.
if fieldWidth < runewidth.StringWidth(text) {
if fieldWidth < textWidth {
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]
w := runewidth.RuneWidth(ch)
if fieldWidth-w < 0 {
@ -413,14 +433,28 @@ func (i *InputField) setCursor(screen tcell.Screen) {
y++
rightLimit -= 2
}
fieldWidth := runewidth.StringWidth(i.text)
if i.fieldWidth > 0 && fieldWidth > i.fieldWidth-1 {
fieldWidth = i.fieldWidth - 1
cursorIndent := i.cursorPosition
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 {
x += i.labelWidth + 1 + fieldWidth
x += i.labelWidth + 1 + cursorIndent
} else {
x += StringWidth(i.subLabel+i.label) + 1 + fieldWidth
x += StringWidth(i.subLabel+i.label) + 1 + cursorIndent
}
if x >= rightLimit {
x = rightLimit - 1
@ -442,12 +476,13 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
// Process key event.
switch key := event.Key(); key {
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(newText, event.Rune()) {
break
}
}
i.cursorPosition++
i.text = newText
case tcell.KeyCtrlU: // Delete all.
i.text = ""
@ -455,11 +490,30 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
lastWord := regexp.MustCompile(`\s*\S+\s*$`)
i.text = lastWord.ReplaceAllString(i.text, "")
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
}
newText := i.text[0:i.cursorPosition] + i.text[i.cursorPosition+1:]
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.
if i.done != nil {
i.done(key)
@ -467,6 +521,14 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
if i.finished != nil {
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.
currentItem int
offset int
// Whether or not to show the secondary item texts.
showSecondaryText bool
@ -84,6 +85,10 @@ type ListBox struct {
// An optional function which is called when the user presses the Escape 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.
@ -96,8 +101,8 @@ func NewListBox() *ListBox {
selectedTextColor: Styles.PrimitiveBackgroundColor,
selectedBackgroundColor: Styles.PrimaryTextColor,
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
fieldBackgroundColor: Styles.FieldBackgroundColor,
fieldTextColor: Styles.FieldTextColor,
align: AlignLeft,
labelFiller: " ",
}
@ -109,7 +114,9 @@ func NewListBox() *ListBox {
// SetCurrentItem sets the currently selected item by its index. This triggers
// a "changed" event.
func (l *ListBox) SetCurrentItem(index int) *ListBox {
_, _, _, height := l.GetInnerRect()
l.currentItem = index
l.offset = l.currentItem - height/2
if l.currentItem < len(l.items) && l.changed != nil {
item := l.items[l.currentItem]
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
@ -134,6 +141,14 @@ func (l *ListBox) GetCurrentItem() int {
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.
func (l *ListBox) SetMainTextColor(color tcell.Color) *ListBox {
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?
var offset int
if l.showSecondaryText {
if l.currentItem >= height/2 {
offset = l.currentItem + 1 - (height / 2)
l.offset = l.currentItem + 1 - (height / 2)
}
} else {
if l.currentItem >= height {
offset = l.currentItem + 1 - height
if l.offset > 0 && l.offset > l.currentItem {
l.offset--
} else if l.currentItem >= height && l.offset <= l.currentItem-height {
l.offset = l.currentItem + 1 - height
}
}
// Draw the list items.
for index, item := range l.items {
if index < offset {
if index < l.offset {
continue
}
@ -287,11 +303,14 @@ func (l *ListBox) Draw(screen tcell.Screen) {
if fg == l.mainTextColor {
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)
}
}
y++
if y >= bottomLimit {
@ -313,19 +332,49 @@ func (l *ListBox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pri
previousItem := l.currentItem
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++
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--
if l.currentItem < 0 {
l.currentItem = len(l.items) - 1
}
case tcell.KeyHome:
l.currentItem = 0
case tcell.KeyEnd:
l.currentItem = len(l.items) - 1
case tcell.KeyPgDn:
l.currentItem += height
if l.currentItem < l.offset+height-1 {
l.currentItem = l.offset + height - 1
} else {
l.currentItem += height
}
if l.currentItem >= len(l.items) {
l.currentItem = 0
l.offset = 0
}
case tcell.KeyPgUp:
l.currentItem -= height
if l.currentItem > l.offset {
l.currentItem = l.offset
} else {
l.currentItem -= height
l.offset = l.currentItem
}
case tcell.KeyEnter:
if len(l.items) == 0 {
break
}
item := l.items[l.currentItem]
if item.Selected != nil {
item.Selected()
@ -337,6 +386,9 @@ func (l *ListBox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pri
if l.done != nil {
l.done(key)
}
if l.finished != nil {
l.finished(key)
}
case tcell.KeyRune:
ch := event.Rune()
if ch != ' ' {
@ -439,7 +491,13 @@ func (l *ListBox) GetLabel() string {
// SetFinishedFunc calls SetDoneFunc().
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.

@ -40,9 +40,10 @@ func NewModal() *Modal {
}
m.form = NewForm().
SetButtonsAlign(AlignCenter).
SetButtonBackgroundColor(Styles.PrimitiveBackgroundColor).
SetButtonTextColor(Styles.PrimaryTextColor)
m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)
SetButtonBackgroundColor(Styles.ButtonBackgroundColor).
SetButtonTextColor(Styles.ButtonTextColor)
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.SetBorder(true).
SetBackgroundColor(Styles.ModalBackgroundColor).
@ -74,6 +75,12 @@ func (m *Modal) SetText(text string) *Modal {
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
// a "done" handler so the window can be closed again.
func (m *Modal) AddButtons(labels []string) *Modal {

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

@ -76,11 +76,17 @@ type RadioButtons struct {
labelWidth int
joinElements []*RadioButtons
currentElement int
// An optional function which is called when the user indicated that they
// are done selecting options. The key which was pressed is provided (tab,
// shift-tab, or escape).
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,
// they are positioned from left to right.
horizontal bool
@ -90,6 +96,8 @@ type RadioButtons struct {
// An optional function which is called when the user has navigated to a list
// item.
changed func(*RadioOption)
inputHandler func() func(event *tcell.EventKey, setFocus func(p Primitive))
}
// NewRadioButtons returns a new radio button primitive.
@ -108,10 +116,23 @@ func NewRadioButtons() *RadioButtons {
}
r.focus = r
r.joinElements = append(r.joinElements, r)
r.inputHandler = r.defaultInputHandler
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
func (r *RadioButtons) SetOptions(options []*RadioOption) *RadioButtons {
r.options = options
@ -133,28 +154,81 @@ func (r *RadioButtons) SetAlign(align int) *RadioButtons {
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.
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)) {
parent := r
r = parent.joinElements[parent.currentElement]
switch key := event.Key(); key {
case tcell.KeyUp, tcell.KeyLeft:
r.currentOption--
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)))
}
if r.changed != nil {
r.changed(r.options[r.currentOption])
}
case tcell.KeyDown, tcell.KeyRight:
r.currentOption++
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)))
}
if r.changed != nil {
r.changed(r.options[r.currentOption])
}
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 {
r.changed(r.options[r.currentOption])
}
case tcell.KeyTab, tcell.KeyBacktab: // We're done.
if r.done != nil {
r.done(key)
case tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
if parent.done != nil {
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().
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.
@ -328,9 +408,9 @@ func (r *RadioButtons) SetHorizontal(horizontal bool) *RadioButtons {
func (r *RadioButtons) SetCurrentOptionByName(name string) *RadioButtons {
for i := 0; i < len(r.options); i++ {
if r.options[i].Name == name {
r.selectedOption = i
r.currentOption = i
if r.changed != nil {
r.changed(r.options[r.selectedOption])
r.changed(r.options[r.currentOption])
}
break
}
@ -341,9 +421,9 @@ func (r *RadioButtons) SetCurrentOptionByName(name string) *RadioButtons {
// SetCurrentOption sets the index of the currently selected option. This may
// be a negative value to indicate that no option is currently selected.
func (r *RadioButtons) SetCurrentOption(index int) *RadioButtons {
r.selectedOption = index
r.currentOption = index
if r.changed != nil {
r.changed(r.options[r.selectedOption])
r.changed(r.options[r.currentOption])
}
return r
}
@ -351,12 +431,20 @@ func (r *RadioButtons) SetCurrentOption(index int) *RadioButtons {
// 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.
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.
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
@ -420,7 +508,7 @@ func (r *RadioButtons) Draw(screen tcell.Screen) {
break
}
radioButton := Styles.GraphicsRadioUnchecked // Unchecked.
if index == r.selectedOption {
if index == r.currentOption {
radioButton = Styles.GraphicsRadioChecked // Checked.
}

@ -411,7 +411,14 @@ func (t *Table) GetColumnCount() int {
// GetPageCount returns quantity of pages
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

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"regexp"
"strings"
"sync"
"unicode/utf8"
@ -85,6 +86,8 @@ type TextView struct {
sync.Mutex
*Box
maxWidth, maxHeight, minWidth, minHeight int
// The text buffer.
buffer []string
@ -160,11 +163,15 @@ type TextView struct {
done func(tcell.Key)
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.
func NewTextView() *TextView {
return &TextView{
textview := &TextView{
Box: NewBox(),
highlights: make(map[string]struct{}),
lineOffset: -1,
@ -174,6 +181,23 @@ func NewTextView() *TextView {
textColor: Styles.PrimaryTextColor,
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
@ -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.
func (t *TextView) Draw(screen tcell.Screen) {
t.Lock()
@ -844,8 +893,8 @@ func (t *TextView) Draw(screen tcell.Screen) {
if highlighted {
fg, bg, _ := style.Decompose()
if bg == tcell.ColorDefault {
r, g, b := fg.RGB()
c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
r, g, t := fg.RGB()
c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(t) / 255}
_, _, li := c.Hcl()
if li < .5 {
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)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
key := event.Key()
if key == tcell.KeyEscape || key == tcell.KeyEnter || key == tcell.KeyTab || key == tcell.KeyBacktab {
if t.done != nil {
t.done(key)
}
if t.finished != nil {
t.finished(key)
}
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