Many improvements

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

@ -22,6 +22,9 @@ type Box struct {
// Border padding.
paddingTop, paddingBottom, paddingLeft, paddingRight int
// Border padding to be measured by percent.
paddingPercentTop, paddingPercentBottom, paddingPercentLeft, paddingPercentRight int
// The box's background color.
backgroundColor tcell.Color
@ -77,12 +80,37 @@ func NewBox() *Box {
return b
}
// SetHeight sets height of the box
func (b *Box) SetHeight(height int) *Box {
b.height = height
return b
}
// SetWidth sets width of the box
func (b *Box) SetWidth(width int) *Box {
b.width = width
return b
}
// SetPercentPadding sets border in percents. This feature implements resizable paddingPercent of the box relative to the size of the screen
func (b *Box) SetPercentPadding(top, bottom, left, right int) *Box {
b.paddingPercentTop, b.paddingPercentBottom, b.paddingPercentLeft, b.paddingPercentRight = top, bottom, left, right
return b
}
// SetBorderPadding sets the size of the borders around the box content.
func (b *Box) SetBorderPadding(top, bottom, left, right int) *Box {
b.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight = top, bottom, left, right
return b
}
// GetBorderPadding returns padding of the box
func (b *Box) GetBorderPadding() (top, bottom, left, right int) {
outerX, outerY, outerWidth, outerHeight := b.GetRect()
innerX, innerY, innerWidth, innerHeight := b.GetInnerRect()
return innerY - outerY, (outerY + outerHeight) - (innerY + innerHeight), innerX - outerX, (outerX + outerWidth) - (innerX + innerWidth)
}
// GetRect returns the current position of the rectangle, x, y, width, and
// height.
func (b *Box) GetRect() (int, int, int, int) {
@ -102,10 +130,19 @@ func (b *Box) GetInnerRect() (int, int, int, int) {
width -= 2
height -= 2
}
return x + b.paddingLeft,
y + b.paddingTop,
width - b.paddingLeft - b.paddingRight,
height - b.paddingTop - b.paddingBottom
x, y, width, height = x+b.paddingLeft,
y+b.paddingTop,
width-b.paddingLeft-b.paddingRight,
height-b.paddingTop-b.paddingBottom
// Percent padding
left := int(float64(width) / 100 * float64(b.paddingPercentLeft))
top := int(float64(height) / 100 * float64(b.paddingPercentTop))
right := int(float64(width) / 100 * float64(b.paddingPercentRight))
bottom := int(float64(height) / 100 * float64(b.paddingPercentBottom))
return x + left, y + top, width - left - right, height - top - bottom
}
// SetRect sets a new position of the primitive.
@ -187,6 +224,11 @@ func (b *Box) SetBorder(show bool) *Box {
return b
}
// GetBorder returns the flag indicating whether or not the box was set
func (b *Box) GetBorder() bool {
return b.border
}
// SetBorderColor sets the box's border color.
func (b *Box) SetBorderColor(color tcell.Color) *Box {
b.borderColor = color
@ -234,19 +276,19 @@ func (b *Box) Draw(screen tcell.Screen) {
border := background.Foreground(b.borderColor)
var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
if b.focus.HasFocus() {
vertical = GraphicsDbVertBar
horizontal = GraphicsDbHorBar
topLeft = GraphicsDbTopLeftCorner
topRight = GraphicsDbTopRightCorner
bottomLeft = GraphicsDbBottomLeftCorner
bottomRight = GraphicsDbBottomRightCorner
horizontal = Styles.GraphicsDbVertBar
vertical = Styles.GraphicsDbHorBar
topLeft = Styles.GraphicsDbTopLeftCorner
topRight = Styles.GraphicsDbTopRightCorner
bottomLeft = Styles.GraphicsDbBottomLeftCorner
bottomRight = Styles.GraphicsDbBottomRightCorner
} else {
vertical = GraphicsHoriBar
horizontal = GraphicsVertBar
topLeft = GraphicsTopLeftCorner
topRight = GraphicsTopRightCorner
bottomLeft = GraphicsBottomLeftCorner
bottomRight = GraphicsBottomRightCorner
horizontal = Styles.GraphicsVertBar
vertical = Styles.GraphicsHoriBar
topLeft = Styles.GraphicsTopLeftCorner
topRight = Styles.GraphicsTopRightCorner
bottomLeft = Styles.GraphicsBottomLeftCorner
bottomRight = Styles.GraphicsBottomRightCorner
}
for x := b.x + 1; x < b.x+b.width-1; x++ {
screen.SetContent(x, b.y, vertical, nil, border)
@ -263,11 +305,12 @@ func (b *Box) Draw(screen tcell.Screen) {
// Draw title.
if b.title != "" && b.width >= 4 {
_, printed := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
if StringWidth(b.title)-printed > 0 && printed > 0 {
title := " " + b.title + " "
_, printed := Print(screen, title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
if StringWidth(title)-printed > 0 && printed > 0 {
_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
fg, _, _ := style.Decompose()
Print(screen, string(GraphicsEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
Print(screen, string(Styles.GraphicsEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
}
}
}

@ -25,6 +25,8 @@ type Button struct {
// An optional function which is called when the button was selected.
selected func()
hidden bool
// An optional function which is called when the user leaves the button. A
// key is provided indicating which key was pressed to leave (tab or backtab).
blur func(tcell.Key)
@ -32,17 +34,28 @@ type Button struct {
// NewButton returns a new input field.
func NewButton(label string) *Button {
box := NewBox().SetBackgroundColor(Styles.ContrastBackgroundColor)
box := NewBox().SetBackgroundColor(Styles.ButtonBackgroundColor)
box.SetRect(0, 0, StringWidth(label)+4, 1)
return &Button{
Box: box,
label: label,
labelColor: Styles.PrimaryTextColor,
labelColor: Styles.ButtonTextColor,
labelColorActivated: Styles.InverseTextColor,
backgroundColorActivated: Styles.PrimaryTextColor,
}
}
// SetHidden hides button
func (b *Button) SetHidden(hidden bool) *Button {
b.hidden = hidden
return b
}
// GetHidden returns state of button
func (b *Button) GetHidden() bool {
return b.hidden
}
// SetLabel sets the button text.
func (b *Button) SetLabel(label string) *Button {
b.label = label
@ -94,6 +107,9 @@ func (b *Button) SetBlurFunc(handler func(key tcell.Key)) *Button {
// Draw draws this primitive onto the screen.
func (b *Button) Draw(screen tcell.Screen) {
if b.hidden {
return
}
// Draw the box.
borderColor := b.borderColor
backgroundColor := b.backgroundColor

@ -1,6 +1,9 @@
package tview
import (
"fmt"
"strings"
"github.com/gdamore/tcell"
)
@ -11,9 +14,21 @@ import (
type Checkbox struct {
*Box
align int
labelFiller string
lockColors bool
// Whether or not this box is checked.
checked bool
// The text to be displayed before the input area.
subLabel string
// The item sub label color.
subLabelColor tcell.Color
// The text to be displayed before the input area.
label string
@ -46,12 +61,16 @@ type Checkbox struct {
// NewCheckbox returns a new input field.
func NewCheckbox() *Checkbox {
return &Checkbox{
checkbox := &Checkbox{
Box: NewBox(),
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
labelColor: Styles.LabelTextColor,
fieldBackgroundColor: Styles.ButtonBackgroundColor,
fieldTextColor: Styles.ButtonTextColor,
align: AlignLeft,
labelFiller: " ",
}
checkbox.height = 1
return checkbox
}
// SetChecked sets the state of the checkbox.
@ -67,6 +86,9 @@ func (c *Checkbox) IsChecked() bool {
// SetLabel sets the text to be displayed before the input area.
func (c *Checkbox) SetLabel(label string) *Checkbox {
if !strings.Contains(label, "%s") {
label += "%s"
}
c.label = label
return c
}
@ -76,6 +98,11 @@ func (c *Checkbox) GetLabel() string {
return c.label
}
// GetLabelWidth returns label width.
func (c *Checkbox) GetLabelWidth() int {
return StringWidth(strings.Replace(c.subLabel+c.label, "%s", "", -1))
}
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func (c *Checkbox) SetLabelWidth(width int) *Checkbox {
@ -89,6 +116,24 @@ func (c *Checkbox) SetLabelColor(color tcell.Color) *Checkbox {
return c
}
// SetFieldAlign sets the input alignment within the checkbox box. This must be
// either AlignLeft, AlignCenter, or AlignRight.
func (c *Checkbox) SetFieldAlign(align int) FormItem {
c.align = align
return c
}
// GetFieldAlign returns the input alignment within the checkbox box.
func (c *Checkbox) GetFieldAlign() (align int) {
return c.align
}
// SetLabelFiller sets a sign which will be fill the label when this one need to stretch
func (c *Checkbox) SetLabelFiller(Filler string) FormItem {
c.labelFiller = Filler
return c
}
// SetFieldBackgroundColor sets the background color of the input area.
func (c *Checkbox) SetFieldBackgroundColor(color tcell.Color) *Checkbox {
c.fieldBackgroundColor = color
@ -101,19 +146,41 @@ func (c *Checkbox) SetFieldTextColor(color tcell.Color) *Checkbox {
return c
}
// SetSubLabel sets the text to be displayed before the input area.
func (c *Checkbox) SetSubLabel(label string) *Checkbox {
c.subLabel = label
return c
}
// SetSubLabelColor sets the color of the subLabel.
func (c *Checkbox) SetSubLabelColor(color tcell.Color) *Checkbox {
c.subLabelColor = color
return c
}
// SetLockColors locks the change of colors by form
func (c *Checkbox) SetLockColors(lock bool) *Checkbox {
c.lockColors = lock
return c
}
// SetFormAttributes sets attributes shared by all form items.
func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
c.labelWidth = labelWidth
c.labelColor = labelColor
c.backgroundColor = bgColor
c.fieldTextColor = fieldTextColor
c.fieldBackgroundColor = fieldBgColor
func (c *Checkbox) SetFormAttributes(labelWidth, fieldWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
if c.labelWidth == 0 {
c.labelWidth = labelWidth
}
if !c.lockColors {
c.labelColor = labelColor
c.backgroundColor = bgColor
c.fieldTextColor = fieldBgColor
c.fieldBackgroundColor = fieldTextColor
}
return c
}
// GetFieldWidth returns this primitive's field width.
func (c *Checkbox) GetFieldWidth() int {
return 1
return StringWidth(Styles.GraphicsCheckboxUnchecked)
}
// SetChangedFunc sets a handler which is called when the checked state of this
@ -154,16 +221,37 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
}
// Draw label.
if c.labelWidth > 0 {
var labels = []struct {
text string
color tcell.Color
}{{
text: c.subLabel,
color: c.subLabelColor,
}, {
text: c.label,
color: c.labelColor,
}}
if len(labels) > 0 {
labelWidth := c.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
Print(screen, c.label, x, y, labelWidth, AlignLeft, c.labelColor)
x += labelWidth
} else {
_, drawnWidth := Print(screen, c.label, x, y, rightLimit-x, AlignLeft, c.labelColor)
x += drawnWidth
addCount := labelWidth - c.GetLabelWidth()
for _, label := range labels {
if addCount > 0 && strings.Contains(label.text, "%s") {
label.text = fmt.Sprintf(label.text, strings.Repeat(c.labelFiller, addCount))
addCount = 0
} else {
label.text = strings.Replace(label.text, "%s", "", -1)
}
labelWidth = StringWidth(label.text)
Print(screen, label.text, x, y, labelWidth, AlignLeft, label.color)
x += labelWidth
}
}
// Draw checkbox.
@ -171,11 +259,15 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
if c.focus.HasFocus() {
fieldStyle = fieldStyle.Background(c.fieldTextColor).Foreground(c.fieldBackgroundColor)
}
checkedRune := 'X'
line := Styles.GraphicsCheckboxChecked
if !c.checked {
checkedRune = ' '
line = Styles.GraphicsCheckboxUnchecked
}
width = c.GetFieldWidth()
for i := 0; i < width; i++ {
screen.SetContent(x+i, y, rune(line[i]), nil, fieldStyle)
}
screen.SetContent(x, y, checkedRune, nil, fieldStyle)
}
// InputHandler returns the handler for this primitive.

@ -1,18 +1,28 @@
package tview
import (
"fmt"
"strings"
"github.com/gdamore/tcell"
runewidth "github.com/mattn/go-runewidth"
)
// dropDownOption is one option that can be selected in a drop-down primitive.
type dropDownOption struct {
// DropDownOption is one option that can be selected in a drop-down primitive.
type DropDownOption struct {
Name string
Text string // The text to be displayed in the drop-down.
Selected func() // The (optional) callback for when this option was selected.
}
// NewDropDownOption returns a new option for dropdown
func NewDropDownOption(name, text string) *DropDownOption {
return &DropDownOption{
Name: name,
Text: text,
}
}
// DropDown implements a selection widget whose options become visible in a
// drop-down list when activated.
//
@ -20,8 +30,12 @@ type dropDownOption struct {
type DropDown struct {
*Box
align int
labelFiller string
// The options from which the user can choose.
options []*dropDownOption
options []*DropDownOption
// The index of the currently selected option. Negative if no option is
// currently selected.
@ -81,17 +95,33 @@ func NewDropDown() *DropDown {
Box: NewBox(),
currentOption: -1,
list: list,
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
labelColor: Styles.LabelTextColor,
fieldBackgroundColor: Styles.FieldBackgroundColor,
fieldTextColor: Styles.FieldTextColor,
prefixTextColor: Styles.ContrastSecondaryTextColor,
align: AlignLeft,
labelFiller: " ",
}
d.height = 1
d.focus = d
return d
}
// SetCurrentOptionByName sets the index of the currently selected option. This may
// be a negative value to indicate that no option is currently selected.
func (d *DropDown) SetCurrentOptionByName(name string) *DropDown {
for i := 0; i < len(d.options); i++ {
if d.options[i].Name == name {
d.currentOption = i
d.list.SetCurrentItem(i)
break
}
}
return d
}
// SetCurrentOption sets the index of the currently selected option. This may
// be a negative value to indicate that no option is currently selected.
func (d *DropDown) SetCurrentOption(index int) *DropDown {
@ -110,6 +140,11 @@ func (d *DropDown) GetCurrentOption() (int, string) {
return d.currentOption, text
}
// GetCurrentOptionName returns the name of the currently selected option.
func (d *DropDown) GetCurrentOptionName() string {
return d.options[d.currentOption].Name
}
// SetLabel sets the text to be displayed before the input area.
func (d *DropDown) SetLabel(label string) *DropDown {
d.label = label
@ -134,6 +169,11 @@ func (d *DropDown) SetLabelColor(color tcell.Color) *DropDown {
return d
}
// GetLabelWidth returns label width.
func (d *DropDown) GetLabelWidth() int {
return StringWidth(strings.Replace(d.label, "%s", "", -1))
}
// SetFieldBackgroundColor sets the background color of the options area.
func (d *DropDown) SetFieldBackgroundColor(color tcell.Color) *DropDown {
d.fieldBackgroundColor = color
@ -155,8 +195,13 @@ func (d *DropDown) SetPrefixTextColor(color tcell.Color) *DropDown {
}
// SetFormAttributes sets attributes shared by all form items.
func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
d.labelWidth = labelWidth
func (d *DropDown) SetFormAttributes(labelWidth, fieldWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
if d.fieldWidth == 0 {
d.fieldWidth = fieldWidth
}
if d.labelWidth == 0 {
d.labelWidth = labelWidth
}
d.labelColor = labelColor
d.backgroundColor = bgColor
d.fieldTextColor = fieldTextColor
@ -186,11 +231,23 @@ func (d *DropDown) GetFieldWidth() int {
return fieldWidth
}
// SetFieldAlign sets the input alignment within the radiobutton box. This must be
// either AlignLeft, AlignCenter, or AlignRight.
func (d *DropDown) SetFieldAlign(align int) FormItem {
d.align = align
return d
}
// GetFieldAlign returns the input alignment within the radiobutton box.
func (d *DropDown) GetFieldAlign() (align int) {
return d.align
}
// AddOption adds a new selectable option to this drop-down. The "selected"
// callback is called when this option was selected. It may be nil.
func (d *DropDown) AddOption(text string, selected func()) *DropDown {
d.options = append(d.options, &dropDownOption{Text: text, Selected: selected})
d.list.AddItem(text, "", 0, selected)
func (d *DropDown) AddOption(option *DropDownOption) *DropDown {
d.options = append(d.options, option)
d.list.AddItem(option.Text, "", 0, option.Selected)
return d
}
@ -198,17 +255,18 @@ func (d *DropDown) AddOption(text string, selected func()) *DropDown {
// one callback function which is called when one of the options is selected.
// It will be called with the option's text and its index into the options
// slice. The "selected" parameter may be nil.
func (d *DropDown) SetOptions(texts []string, selected func(text string, index int)) *DropDown {
func (d *DropDown) SetOptions(options []*DropDownOption, selected func(option *DropDownOption, index int)) *DropDown {
d.list.Clear()
d.options = nil
for index, text := range texts {
func(t string, i int) {
d.AddOption(text, func() {
for index, option := range options {
func(option *DropDownOption, index int) {
option.Selected = func() {
if selected != nil {
selected(t, i)
selected(option, index)
}
})
}(text, index)
}
d.AddOption(option)
}(option, index)
}
return d
}
@ -243,17 +301,29 @@ func (d *DropDown) Draw(screen tcell.Screen) {
}
// Draw label.
if d.labelWidth > 0 {
labelWidth := d.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
if d.label != "" {
if d.labelWidth > 0 {
labelWidth := d.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
label := d.label
if labelWidth != 0 && labelWidth > d.GetLabelWidth() {
if !strings.Contains(label, "%s") {
label += "%s"
}
label = fmt.Sprintf(label, strings.Repeat(d.labelFiller, labelWidth-d.GetLabelWidth()))
}
Print(screen, label, x, y, labelWidth, AlignLeft, d.labelColor)
x += labelWidth
} else {
_, drawnWidth := Print(screen, d.label, x, y, rightLimit-x, AlignLeft, d.labelColor)
x += drawnWidth
}
Print(screen, d.label, x, y, labelWidth, AlignLeft, d.labelColor)
x += labelWidth
} else {
_, drawnWidth := Print(screen, d.label, x, y, rightLimit-x, AlignLeft, d.labelColor)
x += drawnWidth
}
x++
// What's the longest option text?
maxWidth := 0
@ -272,9 +342,9 @@ func (d *DropDown) Draw(screen tcell.Screen) {
if rightLimit-x < fieldWidth {
fieldWidth = rightLimit - x
}
fieldStyle := tcell.StyleDefault.Background(d.fieldBackgroundColor)
fieldStyle := tcell.StyleDefault.Background(d.fieldTextColor)
if d.GetFocusable().HasFocus() && !d.open {
fieldStyle = fieldStyle.Background(d.fieldTextColor)
fieldStyle = fieldStyle.Background(d.fieldBackgroundColor)
}
for index := 0; index < fieldWidth; index++ {
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
@ -291,10 +361,10 @@ func (d *DropDown) Draw(screen tcell.Screen) {
}
} else {
if d.currentOption >= 0 && d.currentOption < len(d.options) {
color := d.fieldTextColor
color := d.fieldBackgroundColor
// Just show the current selection.
if d.GetFocusable().HasFocus() && !d.open {
color = d.fieldBackgroundColor
color = d.fieldTextColor
}
Print(screen, d.options[d.currentOption].Text, x, y, fieldWidth, AlignLeft, color)
}
@ -406,3 +476,8 @@ func (d *DropDown) HasFocus() bool {
}
return d.hasFocus
}
// HasOpen returns whether or not this primitive has open.
func (d *DropDown) HasOpen() bool {
return d.open
}

@ -113,11 +113,22 @@ func (f *Flex) Draw(screen tcell.Screen) {
if f.direction == FlexRow {
distSize = height
}
for _, item := range f.items {
if item.FixedSize > 0 {
distSize -= item.FixedSize
} else {
proportionSum += item.Proportion
for i := 0; i < len(f.items); i++ {
if f.items[i].FixedSize == 0 && f.items[i].Proportion == 0 {
_, _, w, h := f.items[i].Item.GetRect()
if f.direction == FlexRow {
f.items[i].FixedSize = h
} else {
f.items[i].FixedSize = w
}
}
switch {
case f.items[i].FixedSize > 0:
distSize -= f.items[i].FixedSize
default:
proportionSum += f.items[i].Proportion
}
}

@ -2,6 +2,7 @@ package tview
import (
"github.com/gdamore/tcell"
"github.com/rivo/tview"
)
// DefaultFormFieldWidth is the default field screen width of form elements
@ -16,15 +17,23 @@ type FormItem interface {
// GetLabel returns the item's label text.
GetLabel() string
// GetLabelWidth returns the item's label width.
GetLabelWidth() int
// GetFieldWidth returns the item's field width.
GetFieldWidth() int
GetFieldAlign() (align int)
GetBorderPadding() (top, bottom, left, right int)
// SetFormAttributes sets a number of item attributes at once.
SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem
SetFormAttributes(labelWidth, fieldWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem
// GetFieldWidth returns the width of the form item's field (the area which
// is manipulated by the user) in number of screen cells. A value of 0
// indicates the the field width is flexible and may use as much space as
// required.
GetFieldWidth() int
// SetEnteredFunc sets the handler function for when the user finished
// entering data into the item. The handler may receive events for the
@ -55,6 +64,9 @@ type Form struct {
// The alignment of the buttons.
buttonsAlign int
buttonsPaddingTop int
buttonsPaddingSeparate int
// The number of empty rows between items.
itemPadding int
@ -79,6 +91,13 @@ type Form struct {
// An optional function which is called when the user hits Escape.
cancel func()
// The content alignment, one of AlignLeft, AlignCenter, or AlignRight.
align int
itemsColumn []int
columnPadding int
}
// NewForm returns a new form.
@ -86,20 +105,33 @@ func NewForm() *Form {
box := NewBox().SetBorderPadding(1, 1, 1, 1)
f := &Form{
Box: box,
itemPadding: 1,
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
buttonBackgroundColor: Styles.ContrastBackgroundColor,
buttonTextColor: Styles.PrimaryTextColor,
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,
}
f.width = 0
f.height = 0
f.focus = f
return f
}
// SetAlign sets the content alignment within the flex. This must be
// either AlignLeft, AlignCenter, or AlignRight.
func (f *Form) SetAlign(align int) *Form {
f.align = align
return f
}
// SetItemPadding sets the number of empty rows between form items for vertical
// layouts and the number of empty cells between form items for horizontal
// layouts.
@ -108,6 +140,12 @@ func (f *Form) SetItemPadding(padding int) *Form {
return f
}
// SetColumnPadding sets the number of empty rows between form columns.
func (f *Form) SetColumnPadding(padding int) *Form {
f.columnPadding = 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
@ -160,12 +198,13 @@ func (f *Form) SetButtonTextColor(color tcell.Color) *Form {
// accept any text), and an (optional) callback function which is invoked when
// the input field's text has changed.
func (f *Form) AddInputField(label, value string, fieldWidth int, accept func(textToCheck string, lastChar rune) bool, changed func(text string)) *Form {
f.items = append(f.items, NewInputField().
item := NewInputField().
SetLabel(label).
SetText(value).
SetFieldWidth(fieldWidth).
SetAcceptanceFunc(accept).
SetChangedFunc(changed))
SetChangedFunc(changed)
f.items = append(f.items, item)
return f
}
@ -192,7 +231,7 @@ func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune,
// and an (optional) callback function which is invoked when an option was
// selected. The initial option may be a negative value to indicate that no
// option is currently selected.
func (f *Form) AddDropDown(label string, options []string, initialOption int, selected func(option string, optionIndex int)) *Form {
func (f *Form) AddDropDown(label string, options []*DropDownOption, initialOption int, selected func(option *DropDownOption, optionIndex int)) *Form {
f.items = append(f.items, NewDropDown().
SetLabel(label).
SetCurrentOption(initialOption).
@ -200,6 +239,15 @@ func (f *Form) AddDropDown(label string, options []string, initialOption int, se
return f
}
// AddRadioButton adds a radio button element to the form
func (f *Form) AddRadioButton(label string, options []*RadioOption, initialOption int, selected func(option *RadioOption, optionIndex int)) *Form {
f.items = append(f.items, NewRadioButtons().
SetLabel(label).
SetCurrentOption(initialOption).
SetOptions(options))
return f
}
// AddCheckbox adds a checkbox to the form. It has a label, an initial state,
// and an (optional) callback function which is invoked when the state of the
// checkbox was changed by the user.
@ -240,7 +288,13 @@ func (f *Form) Clear(includeButtons bool) *Form {
// - The field text color
// - The field background color
func (f *Form) AddFormItem(item FormItem) *Form {
return f.AddFormItemWithColumn(item, 0)
}
// AddFormItemWithColumn adds a new item to the form and sets the column.
func (f *Form) AddFormItemWithColumn(item FormItem, column int) *Form {
f.items = append(f.items, item)
f.itemsColumn = append(f.itemsColumn, column)
return f
}
@ -270,45 +324,166 @@ func (f *Form) SetCancelFunc(callback func()) *Form {
return f
}
// GetRect returns the current position of the rectangle, x, y, width, and
// height.
func (f *Form) GetRect() (int, int, int, int) {
x, y, width, height := f.Box.GetRect()
maxColumns := f.getColoumnsCount()
if width == 0 {
maxWidth, _, _ := f.getMaxWidthItems()
for column := 0; column < maxColumns; column++ {
if width < maxWidth[column] {
width = maxWidth[column]
}
}
width += f.paddingLeft + f.paddingRight
if f.border {
width += 2
}
}
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
}
if len(f.buttons) > 0 {
height += 1 + f.buttonsPaddingTop
}
if f.border {
height += 2
}
height += f.paddingTop + f.paddingBottom
}
return x, y, width, height
}
func (f *Form) getColoumnsCount() int {
var maxColumns int
for _, num := range f.itemsColumn {
if maxColumns < num {
maxColumns = num
}
}
return maxColumns + 1
}
func (f *Form) getMaxWidthItems() (maxWidth, maxLabelWidth, maxFieldWidth []int) {
maxColumns := f.getColoumnsCount()
// Find the longest label.
maxLabelWidth = make([]int, maxColumns)
maxFieldWidth = make([]int, maxColumns)
for index, item := range f.items {
_, _, leftPadding, rightPadding := item.GetBorderPadding()
labelWidth := item.GetLabelWidth() + leftPadding
fieldWidth := item.GetFieldWidth() + rightPadding
column := f.itemsColumn[index]
if labelWidth > 0 && labelWidth > maxLabelWidth[column]-1 && item.GetFieldAlign() == AlignCenter {
maxLabelWidth[column] = labelWidth + 1
}
if fieldWidth > maxFieldWidth[column] {
maxFieldWidth[column] = fieldWidth
}
}
maxWidth = make([]int, maxColumns)
for index, item := range f.items {
_, _, leftPadding, rightPadding := item.GetBorderPadding()
labelWidth := item.GetLabelWidth() + leftPadding
fieldWidth := item.GetFieldWidth() + rightPadding
column := f.itemsColumn[index]
if labelWidth+fieldWidth > maxWidth[column] {
maxWidth[column] = labelWidth + fieldWidth
}
if item.GetFieldAlign() == AlignCenter {
maxWidth[column] = maxLabelWidth[column] + maxFieldWidth[column]
}
}
return
}
// Draw draws this primitive onto the screen.
func (f *Form) Draw(screen tcell.Screen) {
f.Box.Draw(screen)
// Determine the dimensions.
x, y, width, height := f.GetInnerRect()
x, y, boxWidth, boxHeight := f.GetInnerRect()
topLimit := y
bottomLimit := y + height
rightLimit := x + width
bottomLimit := y + boxHeight
rightLimit := x + boxWidth
startX := x
// Find the longest label.
var maxLabelWidth int
for _, item := range f.items {
labelWidth := StringWidth(item.GetLabel())
if labelWidth > maxLabelWidth {
maxLabelWidth = labelWidth
}
maxColumns := f.getColoumnsCount()
maxColWidth, maxColLabelWidth, _ := f.getMaxWidthItems()
var maxWidth int
for width, column := 0, 0; column < maxColumns; column++ {
width += maxColWidth[column]
maxWidth = width
width += 1 + f.columnPadding
}
switch f.align {
case tview.AlignCenter:
x += (boxWidth - maxWidth) / 2
case tview.AlignRight:
x += boxWidth - maxWidth
}
var colX, colY []int
for width, column := 0, 0; column < maxColumns; column++ {
colX = append(colX, x+width)
colY = append(colY, topLimit)
width += maxColWidth[column] + 1 + f.columnPadding
}
maxLabelWidth++ // Add one space.
// Calculate positions of form items.
positions := make([]struct{ x, y, width, height int }, len(f.items)+len(f.buttons))
var focusedPosition struct{ x, y, width, height int }
for index, item := range f.items {
// Calculate the space needed.
labelWidth := StringWidth(item.GetLabel())
column := f.itemsColumn[index]
x = colX[column]
y = colY[column]
_, _, leftPadding, rightPadding := item.GetBorderPadding()
labelWidth := item.GetLabelWidth() + leftPadding
fieldWidth := item.GetFieldWidth() + rightPadding
var itemWidth int
if f.horizontal {
fieldWidth := item.GetFieldWidth()
if fieldWidth == 0 {
fieldWidth = DefaultFormFieldWidth
}
labelWidth++
itemWidth = labelWidth + fieldWidth
} else {
// We want all fields to align vertically.
labelWidth = maxLabelWidth
itemWidth = width
itemWidth = boxWidth
fieldWidth := item.GetFieldWidth()
// Implement alignment of field
switch item.GetFieldAlign() {
case AlignCenter:
labelWidth = maxColLabelWidth[column] - leftPadding
fieldWidth = maxColWidth[column] - labelWidth - rightPadding
case AlignRight:
labelWidth = maxColWidth[column] - leftPadding - fieldWidth - rightPadding
case AlignLeft:
fieldWidth = maxColWidth[column] - leftPadding - labelWidth - rightPadding
}
}
// Advance to next line if there is no space.
@ -323,8 +498,9 @@ func (f *Form) Draw(screen tcell.Screen) {
}
item.SetFormAttributes(
labelWidth,
fieldWidth,
f.labelColor,
f.backgroundColor,
tcell.ColorBlack,
f.fieldTextColor,
f.fieldBackgroundColor,
)
@ -332,8 +508,8 @@ func (f *Form) Draw(screen tcell.Screen) {
// Save position.
positions[index].x = x
positions[index].y = y
positions[index].width = itemWidth
positions[index].height = 1
positions[index].width = maxColWidth[column]
_, _, _, positions[index].height = item.GetRect()
if item.GetFocusable().HasFocus() {
focusedPosition = positions[index]
}
@ -342,8 +518,9 @@ func (f *Form) Draw(screen tcell.Screen) {
if f.horizontal {
x += itemWidth + f.itemPadding
} else {
y += 1 + f.itemPadding
y += positions[index].height + f.itemPadding
}
colY[column] = y
}
// How wide are the buttons?
@ -352,7 +529,7 @@ func (f *Form) Draw(screen tcell.Screen) {
for index, button := range f.buttons {
w := StringWidth(button.GetLabel()) + 4
buttonWidths[index] = w
buttonsWidth += w + 1
buttonsWidth += w + f.buttonsPaddingSeparate
}
buttonsWidth--
@ -361,15 +538,21 @@ func (f *Form) Draw(screen tcell.Screen) {
if f.buttonsAlign == AlignRight {
x = rightLimit - buttonsWidth
} else if f.buttonsAlign == AlignCenter {
x = (x + rightLimit - buttonsWidth) / 2
x = (boxWidth-buttonsWidth)/2 + startX
}
// In vertical layouts, buttons always appear after an empty line.
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
@ -378,7 +561,7 @@ func (f *Form) Draw(screen tcell.Screen) {
if space < buttonWidth-4 {
x = startX
y += 2
space = width
space = boxWidth
}
} else {
if space < 1 {
@ -403,7 +586,7 @@ func (f *Form) Draw(screen tcell.Screen) {
focusedPosition = positions[buttonIndex]
}
x += buttonWidth + 1
x += buttonWidth + f.buttonsPaddingSeparate
}
// Determine vertical offset based on the position of the focused item.
@ -423,7 +606,7 @@ func (f *Form) Draw(screen tcell.Screen) {
item.SetRect(positions[index].x, y, positions[index].width, height)
// Is this item visible?
if y+height <= topLimit || y >= bottomLimit {
if y+height <= topLimit || (y > bottomLimit) {
continue
}
@ -444,7 +627,7 @@ func (f *Form) Draw(screen tcell.Screen) {
button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
// Is this button visible?
if y+height <= topLimit || y >= bottomLimit {
if y+height <= topLimit || (y >= bottomLimit && boxHeight > 0) {
continue
}
@ -453,6 +636,11 @@ func (f *Form) Draw(screen tcell.Screen) {
}
}
// ResetFocus sets focus on the first element
func (f *Form) ResetFocus() {
f.focusedElement = 0
}
// Focus is called by the application when the primitive receives focus.
func (f *Form) Focus(delegate func(p Primitive)) {
if len(f.items)+len(f.buttons) == 0 {

@ -86,7 +86,7 @@ func (f *Frame) Draw(screen tcell.Screen) {
// Calculate start positions.
x, top, width, height := f.GetInnerRect()
bottom := top + height - 1
bottom := top + height
x += f.left
top += f.top
bottom -= f.bottom

@ -583,11 +583,11 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
by := item.y - 1
if by >= 0 && by < height {
PrintJoinedBorder(screen, x+bx, y+by, GraphicsHoriBar, g.bordersColor)
PrintJoinedBorder(screen, x+bx, y+by, Styles.GraphicsHoriBar, g.bordersColor)
}
by = item.y + item.h
if by >= 0 && by < height {
PrintJoinedBorder(screen, x+bx, y+by, GraphicsHoriBar, g.bordersColor)
PrintJoinedBorder(screen, x+bx, y+by, Styles.GraphicsHoriBar, g.bordersColor)
}
}
for by := item.y; by < item.y+item.h; by++ { // Left/right lines.
@ -596,28 +596,28 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
bx := item.x - 1
if bx >= 0 && bx < width {
PrintJoinedBorder(screen, x+bx, y+by, GraphicsVertBar, g.bordersColor)
PrintJoinedBorder(screen, x+bx, y+by, Styles.GraphicsVertBar, g.bordersColor)
}
bx = item.x + item.w
if bx >= 0 && bx < width {
PrintJoinedBorder(screen, x+bx, y+by, GraphicsVertBar, g.bordersColor)
PrintJoinedBorder(screen, x+bx, y+by, Styles.GraphicsVertBar, g.bordersColor)
}
}
bx, by := item.x-1, item.y-1 // Top-left corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
PrintJoinedBorder(screen, x+bx, y+by, GraphicsTopLeftCorner, g.bordersColor)
PrintJoinedBorder(screen, x+bx, y+by, Styles.GraphicsTopLeftCorner, g.bordersColor)
}
bx, by = item.x+item.w, item.y-1 // Top-right corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
PrintJoinedBorder(screen, x+bx, y+by, GraphicsTopRightCorner, g.bordersColor)
PrintJoinedBorder(screen, x+bx, y+by, Styles.GraphicsTopRightCorner, g.bordersColor)
}
bx, by = item.x-1, item.y+item.h // Bottom-left corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
PrintJoinedBorder(screen, x+bx, y+by, GraphicsBottomLeftCorner, g.bordersColor)
PrintJoinedBorder(screen, x+bx, y+by, Styles.GraphicsBottomLeftCorner, g.bordersColor)
}
bx, by = item.x+item.w, item.y+item.h // Bottom-right corner.
if bx >= 0 && bx < width && by >= 0 && by < height {
PrintJoinedBorder(screen, x+bx, y+by, GraphicsBottomRightCorner, g.bordersColor)
PrintJoinedBorder(screen, x+bx, y+by, Styles.GraphicsBottomRightCorner, g.bordersColor)
}
}
}

@ -1,6 +1,7 @@
package tview
import (
"fmt"
"math"
"regexp"
"strings"
@ -20,12 +21,26 @@ import (
type InputField struct {
*Box
align int
labelFiller string
disable bool
lockColors bool
// The text that was entered.
text string
// The text to be displayed before the input area.
label string
// The text to be displayed before the input area.
subLabel string
// The item sub label color.
subLabelColor tcell.Color
// The text to be displayed in the input area when "text" is empty.
placeholder string
@ -38,6 +53,12 @@ type InputField struct {
// The text color of the input area.
fieldTextColor tcell.Color
// The background color of the input area.
fieldDisableBackgroundColor tcell.Color
// The text color of the input area.
fieldDisableTextColor tcell.Color
// The text color of the placeholder.
placeholderTextColor tcell.Color
@ -71,13 +92,21 @@ type InputField struct {
// NewInputField returns a new input field.
func NewInputField() *InputField {
return &InputField{
Box: NewBox(),
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
placeholderTextColor: Styles.ContrastSecondaryTextColor,
input := &InputField{
Box: NewBox(),
labelColor: Styles.LabelTextColor,
subLabelColor: Styles.LabelTextColor,
fieldBackgroundColor: Styles.FieldBackgroundColor,
fieldTextColor: Styles.FieldTextColor,
placeholderTextColor: Styles.ContrastSecondaryTextColor,
fieldDisableBackgroundColor: Styles.FieldDisableBackgroundColor,
fieldDisableTextColor: Styles.FieldDisableTextColor,
align: AlignLeft,
labelFiller: " ",
}
input.height = 1
return input
}
// SetText sets the current text of the input field.
@ -94,8 +123,17 @@ func (i *InputField) GetText() string {
return i.text
}
// SetLockColors locks the change of colors by form
func (i *InputField) SetLockColors(lock bool) *InputField {
i.lockColors = lock
return i
}
// SetLabel sets the text to be displayed before the input area.
func (i *InputField) SetLabel(label string) *InputField {
if !strings.Contains(label, "%s") {
label += "%s"
}
i.label = label
return i
}
@ -105,6 +143,11 @@ func (i *InputField) GetLabel() string {
return i.label
}
// GetLabelWidth returns label width.
func (i *InputField) GetLabelWidth() int {
return StringWidth(strings.Replace(i.subLabel+i.label, "%s", "", -1))
}
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func (i *InputField) SetLabelWidth(width int) *InputField {
@ -124,6 +167,18 @@ func (i *InputField) SetLabelColor(color tcell.Color) *InputField {
return i
}
// SetSubLabel sets the text to be displayed before the input area.
func (i *InputField) SetSubLabel(label string) *InputField {
i.subLabel = label
return i
}
// SetSubLabelColor sets the color of the subLabel.
func (i *InputField) SetSubLabelColor(color tcell.Color) *InputField {
i.subLabelColor = color
return i
}
// SetFieldBackgroundColor sets the background color of the input area.
func (i *InputField) SetFieldBackgroundColor(color tcell.Color) *InputField {
i.fieldBackgroundColor = color
@ -143,15 +198,34 @@ func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {
}
// SetFormAttributes sets attributes shared by all form items.
func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
i.labelWidth = labelWidth
i.labelColor = labelColor
i.backgroundColor = bgColor
i.fieldTextColor = fieldTextColor
i.fieldBackgroundColor = fieldBgColor
func (i *InputField) SetFormAttributes(labelWidth, fieldWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
if i.fieldWidth == 0 {
i.fieldWidth = fieldWidth
}
if i.labelWidth == 0 {
i.labelWidth = labelWidth
}
if !i.lockColors {
i.labelColor = labelColor
i.backgroundColor = bgColor
i.fieldTextColor = fieldTextColor
i.fieldBackgroundColor = fieldBgColor
}
return i
}
// SetFieldAlign sets the input alignment within the radiobutton box. This must be
// either AlignLeft, AlignCenter, or AlignRight.
func (i *InputField) SetFieldAlign(align int) FormItem {
i.align = align
return i
}
// GetFieldAlign returns the input alignment within the radiobutton box.
func (i *InputField) GetFieldAlign() (align int) {
return i.align
}
// SetFieldWidth sets the screen width of the input area. A value of 0 means
// extend as much as possible.
func (i *InputField) SetFieldWidth(width int) *InputField {
@ -164,6 +238,12 @@ func (i *InputField) GetFieldWidth() int {
return i.fieldWidth
}
// SetDisable sets an input field like disabled
func (i *InputField) SetDisable(disable bool) *InputField {
i.disable = disable
return i
}
// SetMaskCharacter sets a character that masks user input on a screen. A value
// of 0 disables masking.
func (i *InputField) SetMaskCharacter(mask rune) *InputField {
@ -211,6 +291,14 @@ func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
func (i *InputField) Draw(screen tcell.Screen) {
i.Box.Draw(screen)
fieldBackgroundColor := i.fieldBackgroundColor
fieldTextColor := i.fieldTextColor
if i.disable {
fieldBackgroundColor = i.fieldDisableBackgroundColor
fieldTextColor = i.fieldDisableTextColor
}
// Prepare
x, y, width, height := i.GetInnerRect()
rightLimit := x + width
@ -218,17 +306,41 @@ func (i *InputField) Draw(screen tcell.Screen) {
return
}
//fmt.Println("-", i.label, i.labelWidth, i.fieldWidth)
// Draw label.
if i.labelWidth > 0 {
var labels = []struct {
text string
color tcell.Color
}{{
text: i.subLabel,
color: i.subLabelColor,
}, {
text: i.label,
color: i.labelColor,
}}
if len(labels) > 0 {
labelWidth := i.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
Print(screen, i.label, x, y, labelWidth, AlignLeft, i.labelColor)
x += labelWidth
} else {
_, drawnWidth := Print(screen, i.label, x, y, rightLimit-x, AlignLeft, i.labelColor)
x += drawnWidth
addCount := labelWidth - i.GetLabelWidth()
for _, label := range labels {
if addCount > 0 && strings.Contains(label.text, "%s") {
label.text = fmt.Sprintf(label.text, strings.Repeat(i.labelFiller, addCount))
addCount = 0
} else {
label.text = strings.Replace(label.text, "%s", "", -1)
}
labelWidth = StringWidth(label.text)
Print(screen, label.text, x, y, labelWidth, AlignLeft, label.color)
x += labelWidth
}
x++
}
// Draw input area.
@ -239,7 +351,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
if rightLimit-x < fieldWidth {
fieldWidth = rightLimit - x
}
fieldStyle := tcell.StyleDefault.Background(i.fieldBackgroundColor)
fieldStyle := tcell.StyleDefault.Background(fieldBackgroundColor)
for index := 0; index < fieldWidth; index++ {
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
}
@ -264,7 +376,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
break
}
_, _, style, _ := screen.GetContent(x+fieldWidth-w, y)
style = style.Foreground(i.fieldTextColor)
style = style.Foreground(fieldTextColor)
for w > 0 {
fieldWidth--
screen.SetContent(x+fieldWidth, y, ch, nil, style)
@ -276,7 +388,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
for _, ch := range text {
w := runewidth.RuneWidth(ch)
_, _, style, _ := screen.GetContent(x+pos, y)
style = style.Foreground(i.fieldTextColor)
style = style.Foreground(fieldTextColor)
for w > 0 {
screen.SetContent(x+pos, y, ch, nil, style)
pos++
@ -286,15 +398,15 @@ func (i *InputField) Draw(screen tcell.Screen) {
}
// Set cursor.
if i.focus.HasFocus() {
if !i.disable && i.focus.HasFocus() {
i.setCursor(screen)
}
}
// setCursor sets the cursor position.
func (i *InputField) setCursor(screen tcell.Screen) {
x := i.x
y := i.y
x := i.x + i.paddingLeft
y := i.y + i.paddingTop
rightLimit := x + i.width
if i.border {
x++
@ -306,9 +418,9 @@ func (i *InputField) setCursor(screen tcell.Screen) {
fieldWidth = i.fieldWidth - 1
}
if i.labelWidth > 0 {
x += i.labelWidth + fieldWidth
x += i.labelWidth + 1 + fieldWidth
} else {
x += StringWidth(i.label) + fieldWidth
x += StringWidth(i.subLabel+i.label) + 1 + fieldWidth
}
if x >= rightLimit {
x = rightLimit - 1
@ -358,3 +470,12 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
}
})
}
// Focus is called when this primitive receives focus.
func (i *InputField) Focus(delegate func(p Primitive)) {
if i.disable && i.finished != nil {
i.finished(tcell.KeyTAB)
return
}
i.hasFocus = true
}

@ -0,0 +1,459 @@
//
// Copyright (c) 2018 Litmus Automation Inc.
// Author: Levko Burburas <levko.burburas.external@litmus.cloud>
//
package tview
import (
"fmt"
"strings"
"github.com/gdamore/tcell"
)
// listBoxItem represents one item in a ListBox.
type listBoxItem struct {
MainText string // The main text of the list item.
SecondaryText string // A secondary text to be shown underneath the main text.
Shortcut rune // The key to select the list item directly, 0 if there is no shortcut.
Selected func() // The optional function which is called when the item is selected.
}
// ListBox displays rows of items, each of which can be selected.
//
// See https://github.com/rivo/tview/wiki/ListBox for an example.
type ListBox struct {
*Box
align int
labelFiller string
// The items of the list.
items []*listBoxItem
// The text to be displayed before the input area.
label string
// The screen width of the label area. A value of 0 means use the width of
// the label text.
labelWidth int
// The screen width of the input area. A value of 0 means extend as much as
// possible.
fieldWidth 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
// The index of the currently selected item.
currentItem int
// Whether or not to show the secondary item texts.
showSecondaryText bool
// The item main text color.
mainTextColor tcell.Color
// The item secondary text color.
secondaryTextColor tcell.Color
// The item shortcut text color.
shortcutColor tcell.Color
// The text color for selected items.
selectedTextColor tcell.Color
// The background color for selected items.
selectedBackgroundColor tcell.Color
// An optional function which is called when the user has navigated to a list
// item.
changed func(index int, mainText, secondaryText string, shortcut rune)
// An optional function which is called when a list item was selected. This
// function will be called even if the list item defines its own callback.
selected func(index int, mainText, secondaryText string, shortcut rune)
// An optional function which is called when the user presses the Escape key.
done func(tcell.Key)
}
// NewListBox returns a new form.
func NewListBox() *ListBox {
l := &ListBox{
Box: NewBox(),
mainTextColor: Styles.PrimaryTextColor,
secondaryTextColor: Styles.TertiaryTextColor,
shortcutColor: Styles.SecondaryTextColor,
selectedTextColor: Styles.PrimitiveBackgroundColor,
selectedBackgroundColor: Styles.PrimaryTextColor,
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
align: AlignLeft,
labelFiller: " ",
}
l.focus = l
return l
}
// SetCurrentItem sets the currently selected item by its index. This triggers
// a "changed" event.
func (l *ListBox) SetCurrentItem(index int) *ListBox {
l.currentItem = index
if l.currentItem < len(l.items) && l.changed != nil {
item := l.items[l.currentItem]
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
}
return l
}
// SetCurrentItemByText sets the currently selected item by its text. This triggers
// a "changed" event.
func (l *ListBox) SetCurrentItemByText(text string) *ListBox {
for i := 0; i < len(l.items); i++ {
if l.items[i].MainText == text {
l.SetCurrentItem(i)
break
}
}
return l
}
// GetCurrentItem returns the index of the currently selected list item.
func (l *ListBox) GetCurrentItem() int {
return l.currentItem
}
// SetMainTextColor sets the color of the items' main text.
func (l *ListBox) SetMainTextColor(color tcell.Color) *ListBox {
l.mainTextColor = color
return l
}
// SetSecondaryTextColor sets the color of the items' secondary text.
func (l *ListBox) SetSecondaryTextColor(color tcell.Color) *ListBox {
l.secondaryTextColor = color
return l
}
// SetShortcutColor sets the color of the items' shortcut.
func (l *ListBox) SetShortcutColor(color tcell.Color) *ListBox {
l.shortcutColor = color
return l
}
// SetSelectedTextColor sets the text color of selected items.
func (l *ListBox) SetSelectedTextColor(color tcell.Color) *ListBox {
l.selectedTextColor = color
return l
}
// SetSelectedBackgroundColor sets the background color of selected items.
func (l *ListBox) SetSelectedBackgroundColor(color tcell.Color) *ListBox {
l.selectedBackgroundColor = color
return l
}
// ShowSecondaryText determines whether or not to show secondary item texts.
func (l *ListBox) ShowSecondaryText(show bool) *ListBox {
l.showSecondaryText = show
return l
}
// SetChangedFunc sets the function which is called when the user navigates to
// a list item. The function receives the item's index in the list of items
// (starting with 0), its main text, secondary text, and its shortcut rune.
//
// This function is also called when the first item is added or when
// SetCurrentItem() is called.
func (l *ListBox) SetChangedFunc(handler func(int, string, string, rune)) *ListBox {
l.changed = handler
return l
}
// SetSelectedFunc sets the function which is called when the user selects a
// list item by pressing Enter on the current selection. The function receives
// the item's index in the list of items (starting with 0), its main text,
// secondary text, and its shortcut rune.
func (l *ListBox) SetSelectedFunc(handler func(int, string, string, rune)) *ListBox {
l.selected = handler
return l
}
// SetDoneFunc sets a function which is called when the user presses the Escape
// key.
func (l *ListBox) SetDoneFunc(handler func(tcell.Key)) *ListBox {
l.done = handler
return l
}
// AddItem adds a new item to the list. An item has a main text which will be
// highlighted when selected. It also has a secondary text which is shown
// underneath the main text (if it is set to visible) but which may remain
// empty.
//
// The shortcut is a key binding. If the specified rune is entered, the item
// is selected immediately. Set to 0 for no binding.
//
// The "selected" callback will be invoked when the user selects the item. You
// may provide nil if no such item is needed or if all events are handled
// through the selected callback set with SetSelectedFunc().
func (l *ListBox) AddItem(mainText, secondaryText string, shortcut rune, selected func()) *ListBox {
l.items = append(l.items, &listBoxItem{
MainText: mainText,
SecondaryText: secondaryText,
Shortcut: shortcut,
Selected: selected,
})
if len(l.items) == 1 && l.changed != nil {
item := l.items[0]
l.changed(0, item.MainText, item.SecondaryText, item.Shortcut)
}
return l
}
// Clear removes all items from the list.
func (l *ListBox) Clear() *ListBox {
l.items = nil
l.currentItem = 0
return l
}
// Draw draws this primitive onto the screen.
func (l *ListBox) Draw(screen tcell.Screen) {
l.Box.Draw(screen)
// Determine the dimensions.
x, y, width, height := l.GetInnerRect()
bottomLimit := y + height
// Do we show any shortcuts?
var showShortcuts bool
for _, item := range l.items {
if item.Shortcut != 0 {
showShortcuts = true
x += 4
width -= 4
break
}
}
// 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)
}
} else {
if l.currentItem >= height {
offset = l.currentItem + 1 - height
}
}
// Draw the list items.
for index, item := range l.items {
if index < offset {
continue
}
if y >= bottomLimit {
break
}
// Shortcuts.
if showShortcuts && item.Shortcut != 0 {
Print(screen, fmt.Sprintf("(%s)", string(item.Shortcut)), x-5, y, 4, AlignRight, l.shortcutColor)
}
// Main text.
Print(screen, item.MainText, x, y, width, AlignLeft, l.mainTextColor)
// Background color of selected text.
if index == l.currentItem {
textWidth := StringWidth(item.MainText)
for bx := 0; bx < textWidth && bx < width; bx++ {
m, c, style, _ := screen.GetContent(x+bx, y)
fg, _, _ := style.Decompose()
if fg == l.mainTextColor {
fg = l.selectedTextColor
}
style = style.Background(l.selectedBackgroundColor).Foreground(fg)
screen.SetContent(x+bx, y, m, c, style)
}
}
y++
if y >= bottomLimit {
break
}
// Secondary text.
if l.showSecondaryText {
Print(screen, item.SecondaryText, x, y, width, AlignLeft, l.secondaryTextColor)
y++
}
}
}
// InputHandler returns the handler for this primitive.
func (l *ListBox) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
_, _, _, height := l.GetInnerRect()
return l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
previousItem := l.currentItem
switch key := event.Key(); key {
case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight:
l.currentItem++
case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft:
l.currentItem--
case tcell.KeyHome:
l.currentItem = 0
case tcell.KeyEnd:
l.currentItem = len(l.items) - 1
case tcell.KeyPgDn:
l.currentItem += height
case tcell.KeyPgUp:
l.currentItem -= height
case tcell.KeyEnter:
item := l.items[l.currentItem]
if item.Selected != nil {
item.Selected()
}
if l.selected != nil {
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
}
case tcell.KeyEscape:
if l.done != nil {
l.done(key)
}
case tcell.KeyRune:
ch := event.Rune()
if ch != ' ' {
// It's not a space bar. Is it a shortcut?
var found bool
for index, item := range l.items {
if item.Shortcut == ch {
// We have a shortcut.
found = true
l.currentItem = index
break
}
}
if !found {
break
}
}
item := l.items[l.currentItem]
if item.Selected != nil {
item.Selected()
}
if l.selected != nil {
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
}
}
if l.currentItem < 0 {
l.currentItem = len(l.items) - 1
} else if l.currentItem >= len(l.items) {
l.currentItem = 0
}
if l.currentItem != previousItem && l.currentItem < len(l.items) && l.changed != nil {
item := l.items[l.currentItem]
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
}
})
}
// SetFieldAlign sets the input alignment within the inputfield. This must be
// either AlignLeft, AlignCenter, or AlignRight.
func (l *ListBox) SetFieldAlign(align int) FormItem {
l.align = align
return l
}
// GetFieldAlign returns the input alignment within the inputfield.
func (l *ListBox) GetFieldAlign() (align int) {
return l.align
}
// SetLabelFiller sets a sign which will be fill the label when this one need to stretch
func (l *ListBox) SetLabelFiller(labelFiller string) FormItem {
l.labelFiller = labelFiller
return l
}
// GetLabelWidth returns label width.
func (l *ListBox) GetLabelWidth() int {
return StringWidth(strings.Replace(l.label, "%s", "", -1))
}
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func (l *ListBox) SetLabelWidth(width int) *ListBox {
l.labelWidth = width
return l
}
// GetLabelFiller gets a sign which uses for stretching
func (l *ListBox) GetLabelFiller() (labelFiller string) {
return l.labelFiller
}
// SetFieldWidth sets the screen width of the input area. A value of 0 means
// extend as much as possible.
func (l *ListBox) SetFieldWidth(width int) FormItem {
l.fieldWidth = width
return l
}
// GetFieldWidth returns this primitive's field width.
func (l *ListBox) GetFieldWidth() int {
if l.fieldWidth == 0 {
_, _, l.fieldWidth, _ = l.GetInnerRect()
}
return l.fieldWidth
}
// SetLabel sets the text to be displayed before the input area.
func (l *ListBox) SetLabel(label string) *ListBox {
l.label = label
return l
}
// GetLabel returns the text to be displayed before the input area.
func (l *ListBox) GetLabel() string {
return l.label
}
// SetFinishedFunc calls SetDoneFunc().
func (l *ListBox) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return l.SetDoneFunc(handler)
}
// SetFormAttributes sets attributes shared by all form items.
func (l *ListBox) SetFormAttributes(labelWidth, fieldWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
if l.fieldWidth == 0 {
l.fieldWidth = fieldWidth
}
if l.labelWidth == 0 {
l.labelWidth = labelWidth
}
l.labelColor = labelColor
l.SetBackgroundColor(bgColor)
l.fieldTextColor = fieldTextColor
l.fieldBackgroundColor = fieldBgColor
return l
}

@ -21,6 +21,8 @@ type Modal struct {
// The message text (original, not word-wrapped).
text string
minWidth int
// The text color.
textColor tcell.Color
@ -34,6 +36,7 @@ func NewModal() *Modal {
m := &Modal{
Box: NewBox(),
textColor: Styles.PrimaryTextColor,
minWidth: 50,
}
m.form = NewForm().
SetButtonsAlign(AlignCenter).
@ -42,7 +45,7 @@ func NewModal() *Modal {
m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)
m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
m.frame.SetBorder(true).
SetBackgroundColor(Styles.ContrastBackgroundColor).
SetBackgroundColor(Styles.ModalBackgroundColor).
SetBorderPadding(1, 1, 1, 1)
m.focus = m
return m
@ -106,9 +109,14 @@ func (m *Modal) Draw(screen tcell.Screen) {
buttonsWidth -= 2
screenWidth, screenHeight := screen.Size()
width := screenWidth / 3
if width < m.minWidth {
width = m.minWidth
}
if width < buttonsWidth {
width = buttonsWidth
}
// width is now without the box border.
// Reset the text and find out how wide it is.
@ -119,7 +127,11 @@ func (m *Modal) Draw(screen tcell.Screen) {
}
// Set the modal's position and size.
height := len(lines) + 6
height := len(lines) + 4
if len(m.form.buttons) > 0 {
height += 2
}
width += 4
x := (screenWidth - width) / 2
y := (screenHeight - height) / 2

@ -0,0 +1,462 @@
//
// Copyright (c) 2018 Litmus Automation Inc.
// Author: Levko Burburas <levko.burburas.external@litmus.cloud>
//
package tview
import (
"fmt"
"math"
"strings"
"github.com/gdamore/tcell"
)
// RadioOption holds key and title for radio option
type RadioOption struct {
Name string
Title string
}
// NewRadioOption returns a new option for RadioOption
func NewRadioOption(name, text string) *RadioOption {
return &RadioOption{
Name: name,
Title: text,
}
}
// RadioButtons implements a simple primitive for radio button selections.
type RadioButtons struct {
*Box
options []*RadioOption
currentOption int
selectedOption int
itemPadding int
align int
lockColors bool
// The text to be displayed before the input area.
subLabel string
// The item sub label color.
subLabelColor tcell.Color
// The text to be displayed before the input area.
label string
labelFiller 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 item main text color.
mainTextColor tcell.Color
// The item secondary text color.
secondaryTextColor tcell.Color
// The text color for selected items.
selectedTextColor tcell.Color
// The background color for selected items.
selectedBackgroundColor tcell.Color
// The screen width of the input area. A value of 0 means extend as much as
// possible.
fieldWidth int
labelWidth 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)
// If set to true, instead of position items and buttons from top to bottom,
// they are positioned from left to right.
horizontal bool
horizontalSeparator string
// An optional function which is called when the user has navigated to a list
// item.
changed func(*RadioOption)
}
// NewRadioButtons returns a new radio button primitive.
func NewRadioButtons() *RadioButtons {
r := &RadioButtons{
Box: NewBox(),
labelColor: tcell.ColorWhite,
fieldBackgroundColor: tcell.ColorBlack,
fieldTextColor: tcell.ColorWhite,
mainTextColor: Styles.PrimaryTextColor,
secondaryTextColor: Styles.TertiaryTextColor,
selectedTextColor: Styles.PrimitiveBackgroundColor,
selectedBackgroundColor: Styles.PrimaryTextColor,
align: AlignLeft,
labelFiller: " ",
}
r.focus = r
return r
}
// SetOptions replaces all current options with the ones provided
func (r *RadioButtons) SetOptions(options []*RadioOption) *RadioButtons {
r.options = options
return r
}
// SetItemPadding sets the number of empty rows between form items for vertical
// layouts and the number of empty cells between form items for horizontal
// layouts.
func (r *RadioButtons) SetItemPadding(padding int) *RadioButtons {
r.itemPadding = padding
return r
}
// SetAlign sets the radiobox alignment within the box. This must be
// either AlignLeft, AlignCenter, or AlignRight.
func (r *RadioButtons) SetAlign(align int) *RadioButtons {
r.align = align
return r
}
// InputHandler returns the handler for this primitive.
func (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return r.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
switch key := event.Key(); key {
case tcell.KeyUp, tcell.KeyLeft:
r.currentOption--
if r.currentOption < 0 {
r.currentOption = len(r.options) - 1 - int(math.Mod(float64(len(r.options)), float64(r.currentOption)))
}
case tcell.KeyDown, tcell.KeyRight:
r.currentOption++
if r.currentOption >= len(r.options) {
r.currentOption = int(math.Mod(float64(len(r.options)), float64(r.currentOption)))
}
case tcell.KeyEnter, tcell.KeyRune: // We're done.
r.selectedOption = r.currentOption
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)
}
}
})
}
// GetRect returns the current position of the rectangle, x, y, width, and
// height.
func (r *RadioButtons) GetRect() (int, int, int, int) {
x, y, width, _ := r.Box.GetRect()
optionsCount := len(r.options)
if optionsCount == 0 {
optionsCount = 1
}
height := 1
if !r.horizontal {
height = (optionsCount * (r.itemPadding + 1)) - r.itemPadding
}
if height > 0 && r.Box.GetBorder() {
height++
}
return x, y, width, height
}
// GetLabelWidth returns label width.
func (r *RadioButtons) GetLabelWidth() int {
return StringWidth(strings.Replace(r.subLabel+r.label, "%s", "", -1))
}
// GetFieldWidth returns field width.
func (r *RadioButtons) GetFieldWidth() int {
if r.fieldWidth > 0 {
return r.fieldWidth
}
var maxWidth int
for i := 0; i < len(r.options); i++ {
line := fmt.Sprintf(`%s[white] %s`, Styles.GraphicsRadioUnchecked, r.options[i].Title)
if r.horizontal {
maxWidth += StringWidth(line)
if i < len(r.options)-1 {
maxWidth += r.itemPadding + 1
}
} else if maxWidth < StringWidth(line) {
maxWidth = StringWidth(line)
}
}
return maxWidth
}
// SetFieldWidth sets the screen width of the options area. A value of 0 means
// extend to as long as the longest option text.
func (r *RadioButtons) SetFieldWidth(width int) FormItem {
r.fieldWidth = width
return r
}
// SetFormAttributes sets attributes shared by all form items.
func (r *RadioButtons) SetFormAttributes(labelWidth, fieldWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
if r.fieldWidth == 0 {
r.fieldWidth = fieldWidth
}
if r.labelWidth == 0 {
r.labelWidth = labelWidth
}
if !r.lockColors {
r.labelColor = labelColor
r.backgroundColor = bgColor
r.fieldTextColor = fieldTextColor
r.fieldBackgroundColor = fieldBgColor
}
return r
}
// SetFieldAlign sets the input alignment within the radiobutton box. This must be
// either AlignLeft, AlignCenter, or AlignRight.
func (r *RadioButtons) SetFieldAlign(align int) FormItem {
r.align = align
return r
}
// SetLabelFiller sets a sign which will be fill the label when this one need to stretch
func (r *RadioButtons) SetLabelFiller(filler string) FormItem {
r.labelFiller = filler
return r
}
// GetFieldAlign returns the input alignment within the radiobutton box.
func (r *RadioButtons) GetFieldAlign() (align int) {
return r.align
}
// SetDoneFunc sets a handler which is called when the user is done selecting
// options. The callback function is provided with the key that was pressed,
// which is one of the following:
//
// - KeyEscape: Abort selection.
// - KeyTab: Move to the next field.
// - KeyBacktab: Move to the previous field.
func (r *RadioButtons) SetDoneFunc(handler func(key tcell.Key)) *RadioButtons {
r.done = handler
return r
}
// SetFinishedFunc calls SetDoneFunc().
func (r *RadioButtons) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
return r.SetDoneFunc(handler)
}
// SetLabel sets the text to be displayed before the input area.
func (r *RadioButtons) SetLabel(label string) *RadioButtons {
if !strings.Contains(label, "%s") {
label += "%s"
}
r.label = label
return r
}
// GetLabel returns the text to be displayed before the input area.
func (r *RadioButtons) GetLabel() string {
return r.label
}
// SetLockColors locks the change of colors by form
func (r *RadioButtons) SetLockColors(lock bool) *RadioButtons {
r.lockColors = lock
return r
}
// SetLabelColor sets the color of the label.
func (r *RadioButtons) SetLabelColor(color tcell.Color) *RadioButtons {
r.labelColor = color
return r
}
// SetSubLabel sets the text to be displayed before the input area.
func (r *RadioButtons) SetSubLabel(label string) *RadioButtons {
r.subLabel = label
return r
}
// SetSubLabelColor sets the color of the subLabel.
func (r *RadioButtons) SetSubLabelColor(color tcell.Color) *RadioButtons {
r.subLabelColor = color
return r
}
// SetFieldBackgroundColor sets the background color of the options area.
func (r *RadioButtons) SetFieldBackgroundColor(color tcell.Color) *RadioButtons {
r.fieldBackgroundColor = color
return r
}
// SetFieldTextColor sets the text color of the options area.
func (r *RadioButtons) SetFieldTextColor(color tcell.Color) *RadioButtons {
r.fieldTextColor = color
return r
}
// 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
// enough space.
func (r *RadioButtons) SetHorizontal(horizontal bool) *RadioButtons {
r.horizontal = horizontal
return r
}
// SetCurrentOptionByName 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) SetCurrentOptionByName(name string) *RadioButtons {
for i := 0; i < len(r.options); i++ {
if r.options[i].Name == name {
r.selectedOption = i
if r.changed != nil {
r.changed(r.options[r.selectedOption])
}
break
}
}
return r
}
// 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
if r.changed != nil {
r.changed(r.options[r.selectedOption])
}
return r
}
// 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]
}
// GetCurrentOptionName returns the name of the currently selected option.
func (r *RadioButtons) GetCurrentOptionName() string {
return r.options[r.selectedOption].Name
}
// SetChangedFunc sets the function which is called when the user navigates to
// a list item. The function receives the item's index in the list of items
// (starting with 0), its main text, secondary text, and its shortcut rune.
//
// This function is also called when the first item is added or when
// SetCurrentItem() is called.
func (r *RadioButtons) SetChangedFunc(handler func(*RadioOption)) *RadioButtons {
r.changed = handler
return r
}
// Draw draws this primitive onto the screen.
func (r *RadioButtons) Draw(screen tcell.Screen) {
r.Box.Draw(screen)
x, y, width, height := r.GetInnerRect()
rightLimit := x + width
if height < 1 || rightLimit <= x {
return
}
// Draw label.
var labels = []struct {
text string
color tcell.Color
}{{
text: r.subLabel,
color: r.subLabelColor,
}, {
text: r.label,
color: r.labelColor,
}}
if len(labels) > 0 {
labelWidth := r.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
addCount := labelWidth - r.GetLabelWidth()
for _, label := range labels {
if addCount > 0 && strings.Contains(label.text, "%s") {
label.text = fmt.Sprintf(label.text, strings.Repeat(r.labelFiller, addCount))
addCount = 0
} else {
label.text = strings.Replace(label.text, "%s", "", -1)
}
labelWidth = StringWidth(label.text)
Print(screen, label.text, x, y, labelWidth, AlignLeft, label.color)
x += labelWidth
}
}
var lineWidth int
for index, option := range r.options {
if index >= height && !r.horizontal {
break
}
radioButton := Styles.GraphicsRadioUnchecked // Unchecked.
if index == r.selectedOption {
radioButton = Styles.GraphicsRadioChecked // Checked.
}
line := fmt.Sprintf(`%s[white] %s`, radioButton, option.Title)
if r.horizontal {
Print(screen, line, x+lineWidth, y, width, AlignLeft, tcell.ColorWhite)
} else {
Print(screen, line, x, y+(index*(r.itemPadding+1)), width, AlignLeft, tcell.ColorWhite)
}
// Background color of selected text.
if r.HasFocus() && index == r.currentOption {
textWidth := StringWidth(line)
for bx := 0; bx < textWidth && bx < width; bx++ {
if r.horizontal {
m, c, style, _ := screen.GetContent(x+lineWidth+bx, y)
fg, _, _ := style.Decompose()
if fg == r.mainTextColor {
fg = r.selectedTextColor
}
style = style.Background(r.selectedBackgroundColor).Foreground(fg)
screen.SetContent(x+lineWidth+bx, y, m, c, style)
} else {
m, c, style, _ := screen.GetContent(x+bx, y+(index*(r.itemPadding+1)))
fg, _, _ := style.Decompose()
if fg == r.mainTextColor {
fg = r.selectedTextColor
}
style = style.Background(r.selectedBackgroundColor).Foreground(fg)
screen.SetContent(x+bx, y+(index*(r.itemPadding+1)), m, c, style)
}
}
}
if r.horizontal {
lineWidth += StringWidth(line) + (r.itemPadding + 1)
}
}
}

@ -8,6 +8,14 @@ import "github.com/gdamore/tcell"
// The default is for applications with a black background and basic colors:
// black, white, yellow, green, and blue.
var Styles = struct {
ModalBackgroundColor tcell.Color
LabelTextColor tcell.Color
FieldBackgroundColor tcell.Color
FieldTextColor tcell.Color
FieldDisableBackgroundColor tcell.Color
FieldDisableTextColor tcell.Color
ButtonBackgroundColor tcell.Color
ButtonTextColor tcell.Color
PrimitiveBackgroundColor tcell.Color // Main background color for primitives.
ContrastBackgroundColor tcell.Color // Background color for contrasting elements.
MoreContrastBackgroundColor tcell.Color // Background color for even more contrasting elements.
@ -19,7 +27,41 @@ var Styles = struct {
TertiaryTextColor tcell.Color // Tertiary text (e.g. subtitles, notes).
InverseTextColor tcell.Color // Text on primary-colored backgrounds.
ContrastSecondaryTextColor tcell.Color // Secondary text on ContrastBackgroundColor-colored backgrounds.
// Semigraphical runes.
GraphicsHoriBar rune
GraphicsVertBar rune
GraphicsTopLeftCorner rune
GraphicsTopRightCorner rune
GraphicsBottomLeftCorner rune
GraphicsBottomRightCorner rune
GraphicsLeftT rune
GraphicsRightT rune
GraphicsTopT rune
GraphicsBottomT rune
GraphicsCross rune
GraphicsDbVertBar rune
GraphicsDbHorBar rune
GraphicsDbTopLeftCorner rune
GraphicsDbTopRightCorner rune
GraphicsDbBottomRightCorner rune
GraphicsDbBottomLeftCorner rune
GraphicsEllipsis rune
GraphicsRadioChecked string
GraphicsRadioUnchecked string
GraphicsCheckboxChecked string
GraphicsCheckboxUnchecked string
}{
ModalBackgroundColor: tcell.ColorBlack,
LabelTextColor: tcell.ColorWhite,
FieldBackgroundColor: tcell.ColorGrey,
FieldTextColor: tcell.ColorBlack,
FieldDisableBackgroundColor: tcell.ColorBlack,
FieldDisableTextColor: tcell.ColorWhite,
ButtonBackgroundColor: tcell.ColorBlack,
ButtonTextColor: tcell.ColorWhite,
PrimitiveBackgroundColor: tcell.ColorBlack,
ContrastBackgroundColor: tcell.ColorBlue,
MoreContrastBackgroundColor: tcell.ColorGreen,
@ -31,4 +73,29 @@ var Styles = struct {
TertiaryTextColor: tcell.ColorGreen,
InverseTextColor: tcell.ColorBlue,
ContrastSecondaryTextColor: tcell.ColorDarkCyan,
GraphicsHoriBar: '\u2500',
GraphicsVertBar: '\u2502',
GraphicsTopLeftCorner: '\u250c',
GraphicsTopRightCorner: '\u2510',
GraphicsBottomLeftCorner: '\u2514',
GraphicsBottomRightCorner: '\u2518',
GraphicsLeftT: '\u251c',
GraphicsRightT: '\u2524',
GraphicsTopT: '\u252c',
GraphicsBottomT: '\u2534',
GraphicsCross: '\u253c',
GraphicsDbVertBar: '\u2550',
GraphicsDbHorBar: '\u2551',
GraphicsDbTopLeftCorner: '\u2554',
GraphicsDbTopRightCorner: '\u2557',
GraphicsDbBottomRightCorner: '\u255d',
GraphicsDbBottomLeftCorner: '\u255a',
GraphicsEllipsis: '\u2026',
GraphicsRadioChecked: "\u25c9",
GraphicsRadioUnchecked: "\u25ef",
GraphicsCheckboxChecked: "X",
GraphicsCheckboxUnchecked: " ",
}

@ -409,6 +409,11 @@ func (t *Table) GetColumnCount() int {
return t.lastColumn + 1
}
// GetPageCount returns quantity of pages
func (t *Table) GetPageCount() int {
return t.GetRowCount() / t.visibleRows
}
// ScrollToBeginning scrolls the table to the beginning to that the top left
// corner of the table is shown. Note that this position may be corrected if
// there is a selection.
@ -649,24 +654,24 @@ ColumnLoop:
// Draw borders.
rowY *= 2
for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
drawBorder(columnX+pos+1, rowY, GraphicsHoriBar)
drawBorder(columnX+pos+1, rowY, Styles.GraphicsHoriBar)
}
ch := GraphicsCross
ch := Styles.GraphicsCross
if columnIndex == 0 {
if rowY == 0 {
ch = GraphicsTopLeftCorner
ch = Styles.GraphicsTopLeftCorner
} else {
ch = GraphicsLeftT
ch = Styles.GraphicsLeftT
}
} else if rowY == 0 {
ch = GraphicsTopT
ch = Styles.GraphicsTopT
}
drawBorder(columnX, rowY, ch)
rowY++
if rowY >= height {
break // No space for the text anymore.
}
drawBorder(columnX, rowY, GraphicsVertBar)
drawBorder(columnX, rowY, Styles.GraphicsVertBar)
} else if columnIndex > 0 {
// Draw separator.
drawBorder(columnX, rowY, t.separator)
@ -688,18 +693,18 @@ ColumnLoop:
if StringWidth(cell.Text)-printed > 0 && printed > 0 {
_, _, style, _ := screen.GetContent(x+columnX+1+finalWidth-1, y+rowY)
fg, _, _ := style.Decompose()
Print(screen, string(GraphicsEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, fg)
Print(screen, string(Styles.GraphicsEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, fg)
}
}
// Draw bottom border.
if rowY := 2 * len(rows); t.borders && rowY < height {
for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
drawBorder(columnX+pos+1, rowY, GraphicsHoriBar)
drawBorder(columnX+pos+1, rowY, Styles.GraphicsHoriBar)
}
ch := GraphicsBottomT
ch := Styles.GraphicsBottomT
if columnIndex == 0 {
ch = GraphicsBottomLeftCorner
ch = Styles.GraphicsBottomLeftCorner
}
drawBorder(columnX, rowY, ch)
}
@ -712,16 +717,16 @@ ColumnLoop:
for rowY := range rows {
rowY *= 2
if rowY+1 < height {
drawBorder(columnX, rowY+1, GraphicsVertBar)
drawBorder(columnX, rowY+1, Styles.GraphicsVertBar)
}
ch := GraphicsRightT
ch := Styles.GraphicsRightT
if rowY == 0 {
ch = GraphicsTopRightCorner
ch = Styles.GraphicsTopRightCorner
}
drawBorder(columnX, rowY, ch)
}
if rowY := 2 * len(rows); rowY < height {
drawBorder(columnX, rowY, GraphicsBottomRightCorner)
drawBorder(columnX, rowY, Styles.GraphicsBottomRightCorner)
}
}

@ -158,6 +158,8 @@ type TextView struct {
// An optional function which is called when the user presses one of the
// following keys: Escape, Enter, Tab, Backtab.
done func(tcell.Key)
scrollEnded func(screen tcell.Screen)
}
// NewTextView returns a new text view.
@ -270,6 +272,14 @@ func (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {
return t
}
// SetScrollEndedFunc sets a handler function which is called when the text of the
// text view has changed. This is typically used to cause the application to
// redraw the screen.
func (t *TextView) SetScrollEndedFunc(handler func(screen tcell.Screen)) *TextView {
t.scrollEnded = handler
return t
}
// ScrollToBeginning scrolls to the top left corner of the text if the text view
// is scrollable.
func (t *TextView) ScrollToBeginning() *TextView {
@ -718,6 +728,10 @@ func (t *TextView) Draw(screen tcell.Screen) {
// Draw the buffer.
defaultStyle := tcell.StyleDefault.Foreground(t.textColor)
for line := t.lineOffset; line < len(t.index); line++ {
if t.scrollEnded != nil && line == len(t.index)-1 {
t.scrollEnded(screen)
}
// Are we done?
if line-t.lineOffset >= height {
break

@ -19,88 +19,66 @@ const (
AlignRight
)
// Semigraphical runes.
const (
GraphicsHoriBar = '\u2500'
GraphicsVertBar = '\u2502'
GraphicsTopLeftCorner = '\u250c'
GraphicsTopRightCorner = '\u2510'
GraphicsBottomLeftCorner = '\u2514'
GraphicsBottomRightCorner = '\u2518'
GraphicsLeftT = '\u251c'
GraphicsRightT = '\u2524'
GraphicsTopT = '\u252c'
GraphicsBottomT = '\u2534'
GraphicsCross = '\u253c'
GraphicsDbVertBar = '\u2550'
GraphicsDbHorBar = '\u2551'
GraphicsDbTopLeftCorner = '\u2554'
GraphicsDbTopRightCorner = '\u2557'
GraphicsDbBottomRightCorner = '\u255d'
GraphicsDbBottomLeftCorner = '\u255a'
GraphicsEllipsis = '\u2026'
)
// joints maps combinations of two graphical runes to the rune that results
// when joining the two in the same screen cell. The keys of this map are
// two-rune strings where the value of the first rune is lower than the value
// of the second rune. Identical runes are not contained.
var joints = map[string]rune{
"\u2500\u2502": GraphicsCross,
"\u2500\u250c": GraphicsTopT,
"\u2500\u2510": GraphicsTopT,
"\u2500\u2514": GraphicsBottomT,
"\u2500\u2518": GraphicsBottomT,
"\u2500\u251c": GraphicsCross,
"\u2500\u2524": GraphicsCross,
"\u2500\u252c": GraphicsTopT,
"\u2500\u2534": GraphicsBottomT,
"\u2500\u253c": GraphicsCross,
"\u2502\u250c": GraphicsLeftT,
"\u2502\u2510": GraphicsRightT,
"\u2502\u2514": GraphicsLeftT,
"\u2502\u2518": GraphicsRightT,
"\u2502\u251c": GraphicsLeftT,
"\u2502\u2524": GraphicsRightT,
"\u2502\u252c": GraphicsCross,
"\u2502\u2534": GraphicsCross,
"\u2502\u253c": GraphicsCross,
"\u250c\u2510": GraphicsTopT,
"\u250c\u2514": GraphicsLeftT,
"\u250c\u2518": GraphicsCross,
"\u250c\u251c": GraphicsLeftT,
"\u250c\u2524": GraphicsCross,
"\u250c\u252c": GraphicsTopT,
"\u250c\u2534": GraphicsCross,
"\u250c\u253c": GraphicsCross,
"\u2510\u2514": GraphicsCross,
"\u2510\u2518": GraphicsRightT,
"\u2510\u251c": GraphicsCross,
"\u2510\u2524": GraphicsRightT,
"\u2510\u252c": GraphicsTopT,
"\u2510\u2534": GraphicsCross,
"\u2510\u253c": GraphicsCross,
"\u2514\u2518": GraphicsBottomT,
"\u2514\u251c": GraphicsLeftT,
"\u2514\u2524": GraphicsCross,
"\u2514\u252c": GraphicsCross,
"\u2514\u2534": GraphicsBottomT,
"\u2514\u253c": GraphicsCross,
"\u2518\u251c": GraphicsCross,
"\u2518\u2524": GraphicsRightT,
"\u2518\u252c": GraphicsCross,
"\u2518\u2534": GraphicsBottomT,
"\u2518\u253c": GraphicsCross,
"\u251c\u2524": GraphicsCross,
"\u251c\u252c": GraphicsCross,
"\u251c\u2534": GraphicsCross,
"\u251c\u253c": GraphicsCross,
"\u2524\u252c": GraphicsCross,
"\u2524\u2534": GraphicsCross,
"\u2524\u253c": GraphicsCross,
"\u252c\u2534": GraphicsCross,
"\u252c\u253c": GraphicsCross,
"\u2534\u253c": GraphicsCross,
"\u2500\u2502": Styles.GraphicsCross,
"\u2500\u250c": Styles.GraphicsTopT,
"\u2500\u2510": Styles.GraphicsTopT,
"\u2500\u2514": Styles.GraphicsBottomT,
"\u2500\u2518": Styles.GraphicsBottomT,
"\u2500\u251c": Styles.GraphicsCross,
"\u2500\u2524": Styles.GraphicsCross,
"\u2500\u252c": Styles.GraphicsTopT,
"\u2500\u2534": Styles.GraphicsBottomT,
"\u2500\u253c": Styles.GraphicsCross,
"\u2502\u250c": Styles.GraphicsLeftT,
"\u2502\u2510": Styles.GraphicsRightT,
"\u2502\u2514": Styles.GraphicsLeftT,
"\u2502\u2518": Styles.GraphicsRightT,
"\u2502\u251c": Styles.GraphicsLeftT,
"\u2502\u2524": Styles.GraphicsRightT,
"\u2502\u252c": Styles.GraphicsCross,
"\u2502\u2534": Styles.GraphicsCross,
"\u2502\u253c": Styles.GraphicsCross,
"\u250c\u2510": Styles.GraphicsTopT,
"\u250c\u2514": Styles.GraphicsLeftT,
"\u250c\u2518": Styles.GraphicsCross,
"\u250c\u251c": Styles.GraphicsLeftT,
"\u250c\u2524": Styles.GraphicsCross,
"\u250c\u252c": Styles.GraphicsTopT,
"\u250c\u2534": Styles.GraphicsCross,
"\u250c\u253c": Styles.GraphicsCross,
"\u2510\u2514": Styles.GraphicsCross,
"\u2510\u2518": Styles.GraphicsRightT,
"\u2510\u251c": Styles.GraphicsCross,
"\u2510\u2524": Styles.GraphicsRightT,
"\u2510\u252c": Styles.GraphicsTopT,
"\u2510\u2534": Styles.GraphicsCross,
"\u2510\u253c": Styles.GraphicsCross,
"\u2514\u2518": Styles.GraphicsBottomT,
"\u2514\u251c": Styles.GraphicsLeftT,
"\u2514\u2524": Styles.GraphicsCross,
"\u2514\u252c": Styles.GraphicsCross,
"\u2514\u2534": Styles.GraphicsBottomT,
"\u2514\u253c": Styles.GraphicsCross,
"\u2518\u251c": Styles.GraphicsCross,
"\u2518\u2524": Styles.GraphicsRightT,
"\u2518\u252c": Styles.GraphicsCross,
"\u2518\u2534": Styles.GraphicsBottomT,
"\u2518\u253c": Styles.GraphicsCross,
"\u251c\u2524": Styles.GraphicsCross,
"\u251c\u252c": Styles.GraphicsCross,
"\u251c\u2534": Styles.GraphicsCross,
"\u251c\u253c": Styles.GraphicsCross,
"\u2524\u252c": Styles.GraphicsCross,
"\u2524\u2534": Styles.GraphicsCross,
"\u2524\u253c": Styles.GraphicsCross,
"\u252c\u2534": Styles.GraphicsCross,
"\u252c\u253c": Styles.GraphicsCross,
"\u2534\u253c": Styles.GraphicsCross,
}
// Common regular expressions.

Loading…
Cancel
Save