mirror of https://github.com/rivo/tview
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
779 lines
21 KiB
Go
779 lines
21 KiB
Go
package tview
|
|
|
|
import (
|
|
"github.com/gdamore/tcell"
|
|
)
|
|
|
|
// DefaultFormFieldWidth is the default field screen width of form elements
|
|
// whose field width is flexible (0). This is used in the Form class for
|
|
// horizontal layouts.
|
|
var DefaultFormFieldWidth = 10
|
|
|
|
// FormItem is the interface all form items must implement to be able to be
|
|
// included in a form.
|
|
type FormItem interface {
|
|
Primitive
|
|
|
|
// 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, 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.
|
|
|
|
// SetEnteredFunc sets the handler function for when the user finished
|
|
// entering data into the item. The handler may receive events for the
|
|
// 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
|
|
// or horizontal layout. Form elements include types such as InputField or
|
|
// Checkbox. These elements can be optionally followed by one or more buttons
|
|
// for which you can define form-wide actions (e.g. Save, Clear, Cancel).
|
|
//
|
|
// See https://github.com/rivo/tview/wiki/Form for an example.
|
|
type Form struct {
|
|
*Box
|
|
|
|
// The items of the form (one row per item).
|
|
items []FormItem
|
|
|
|
// The buttons of the form.
|
|
buttons []*Button
|
|
|
|
// If set to true, instead of position items and buttons from top to bottom,
|
|
// they are positioned from left to right.
|
|
horizontal bool
|
|
|
|
// The alignment of the buttons.
|
|
buttonsAlign int
|
|
|
|
buttonsPaddingTop int
|
|
buttonsIndent int
|
|
|
|
// The number of empty rows between items.
|
|
itemPadding int
|
|
|
|
// The index of the item or button which has focus. (Items are counted first,
|
|
// buttons are counted last.)
|
|
focusedElement 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 background color of the buttons.
|
|
buttonBackgroundColor tcell.Color
|
|
|
|
// The color of the button text.
|
|
buttonTextColor tcell.Color
|
|
|
|
// 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.
|
|
func NewForm() *Form {
|
|
box := NewBox().SetBorderPadding(1, 1, 1, 1)
|
|
|
|
f := &Form{
|
|
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
|
|
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.
|
|
func (f *Form) SetItemPadding(padding int) *Form {
|
|
f.itemPadding = padding
|
|
return f
|
|
}
|
|
|
|
// SetColumnPadding sets the number of empty rows between form columns.
|
|
func (f *Form) SetColumnPadding(padding int) *Form {
|
|
f.columnPadding = padding
|
|
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
|
|
// enough space.
|
|
func (f *Form) SetHorizontal(horizontal bool) *Form {
|
|
f.horizontal = horizontal
|
|
return f
|
|
}
|
|
|
|
// SetLabelColor sets the color of the labels.
|
|
func (f *Form) SetLabelColor(color tcell.Color) *Form {
|
|
f.labelColor = color
|
|
return f
|
|
}
|
|
|
|
// SetFieldBackgroundColor sets the background color of the input areas.
|
|
func (f *Form) SetFieldBackgroundColor(color tcell.Color) *Form {
|
|
f.fieldBackgroundColor = color
|
|
return f
|
|
}
|
|
|
|
// SetFieldTextColor sets the text color of the input areas.
|
|
func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
|
|
f.fieldTextColor = color
|
|
return f
|
|
}
|
|
|
|
// SetButtonsAlign sets how the buttons align horizontally, one of AlignLeft
|
|
// (the default), AlignCenter, and AlignRight. This is only
|
|
func (f *Form) SetButtonsAlign(align int) *Form {
|
|
f.buttonsAlign = align
|
|
return f
|
|
}
|
|
|
|
// SetButtonBackgroundColor sets the background color of the buttons.
|
|
func (f *Form) SetButtonBackgroundColor(color tcell.Color) *Form {
|
|
f.buttonBackgroundColor = color
|
|
return f
|
|
}
|
|
|
|
// SetButtonTextColor sets the color of the button texts.
|
|
func (f *Form) SetButtonTextColor(color tcell.Color) *Form {
|
|
f.buttonTextColor = color
|
|
return f
|
|
}
|
|
|
|
// AddInputField adds an input field to the form. It has a label, an optional
|
|
// initial value, a field width (a value of 0 extends it as far as possible),
|
|
// an optional accept function to validate the item's value (set to nil to
|
|
// 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 {
|
|
item := NewInputField().
|
|
SetLabel(label).
|
|
SetText(value).
|
|
SetFieldWidth(fieldWidth).
|
|
SetAcceptanceFunc(accept).
|
|
SetChangedFunc(changed)
|
|
f.items = append(f.items, item)
|
|
return f
|
|
}
|
|
|
|
// AddPasswordField adds a password field to the form. This is similar to an
|
|
// input field except that the user's input not shown. Instead, a "mask"
|
|
// character is displayed. The password field has a label, an optional initial
|
|
// value, a field width (a value of 0 extends it as far as possible), and an
|
|
// (optional) callback function which is invoked when the input field's text has
|
|
// changed.
|
|
func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune, changed func(text string)) *Form {
|
|
if mask == 0 {
|
|
mask = '*'
|
|
}
|
|
f.items = append(f.items, NewInputField().
|
|
SetLabel(label).
|
|
SetText(value).
|
|
SetFieldWidth(fieldWidth).
|
|
SetMaskCharacter(mask).
|
|
SetChangedFunc(changed))
|
|
return f
|
|
}
|
|
|
|
// AddDropDown adds a drop-down element to the form. It has a label, options,
|
|
// 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 []*DropDownOption, initialOption int, selected func(option *DropDownOption, optionIndex int)) *Form {
|
|
f.items = append(f.items, NewDropDown().
|
|
SetLabel(label).
|
|
SetCurrentOption(initialOption).
|
|
SetOptions(options, selected))
|
|
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.
|
|
func (f *Form) AddCheckbox(label string, checked bool, changed func(checked bool)) *Form {
|
|
f.items = append(f.items, NewCheckbox().
|
|
SetLabel(label).
|
|
SetChecked(checked).
|
|
SetChangedFunc(changed))
|
|
return f
|
|
}
|
|
|
|
// AddButton adds a new button to the form. The "selected" function is called
|
|
// when the user selects this button. It may be nil.
|
|
func (f *Form) AddButton(label string, selected func()) *Form {
|
|
f.buttons = append(f.buttons, NewButton(label).SetSelectedFunc(selected))
|
|
return f
|
|
}
|
|
|
|
// Clear removes all input elements from the form, including the buttons if
|
|
// specified.
|
|
func (f *Form) Clear(includeButtons bool) *Form {
|
|
f.items = nil
|
|
if includeButtons {
|
|
f.buttons = nil
|
|
}
|
|
f.focusedElement = 0
|
|
return f
|
|
}
|
|
|
|
// AddFormItem adds a new item to the form. This can be used to add your own
|
|
// objects to the form. Note, however, that the Form class will override some
|
|
// of its attributes to make it work in the form context. Specifically, these
|
|
// are:
|
|
//
|
|
// - The label width
|
|
// - The label color
|
|
// - The background color
|
|
// - 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
|
|
}
|
|
|
|
// GetFormItem 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) 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.
|
|
func (f *Form) GetFormItemByLabel(label string) FormItem {
|
|
for _, item := range f.items {
|
|
if item.GetLabel() == label {
|
|
return item
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetCancelFunc sets a handler which is called when the user hits the Escape
|
|
// key.
|
|
func (f *Form) SetCancelFunc(callback func()) *Form {
|
|
f.cancel = callback
|
|
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 {
|
|
height = f.getMaxHeightColumn()
|
|
|
|
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) 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()
|
|
|
|
// 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, boxWidth, boxHeight := f.GetInnerRect()
|
|
topLimit := y
|
|
bottomLimit := y + boxHeight
|
|
rightLimit := x + boxWidth
|
|
startX := x
|
|
|
|
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 AlignCenter:
|
|
x += (boxWidth - maxWidth) / 2
|
|
case 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
|
|
}
|
|
|
|
// 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 {
|
|
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 {
|
|
if fieldWidth == 0 {
|
|
fieldWidth = DefaultFormFieldWidth
|
|
}
|
|
itemWidth = labelWidth + fieldWidth
|
|
} else {
|
|
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.
|
|
if f.horizontal && x+labelWidth+1 >= rightLimit {
|
|
x = startX
|
|
y += 2
|
|
}
|
|
|
|
// Adjust the item's attributes.
|
|
if x+itemWidth >= rightLimit {
|
|
itemWidth = rightLimit - x
|
|
}
|
|
item.SetFormAttributes(
|
|
labelWidth,
|
|
fieldWidth,
|
|
f.labelColor,
|
|
tcell.ColorBlack,
|
|
f.fieldTextColor,
|
|
f.fieldBackgroundColor,
|
|
)
|
|
|
|
// Save position.
|
|
positions[index].x = x
|
|
positions[index].y = y
|
|
positions[index].width = maxColWidth[column]
|
|
_, _, _, positions[index].height = item.GetRect()
|
|
if item.GetFocusable().HasFocus() {
|
|
focusedPosition = positions[index]
|
|
}
|
|
|
|
// Advance to next item.
|
|
if f.horizontal {
|
|
x += itemWidth + f.itemPadding
|
|
} else {
|
|
y += positions[index].height + f.itemPadding
|
|
}
|
|
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.buttonsIndent
|
|
}
|
|
buttonsWidth--
|
|
|
|
// Where do we place them?
|
|
if !f.horizontal && x+buttonsWidth < rightLimit {
|
|
if f.buttonsAlign == AlignRight {
|
|
x = rightLimit - buttonsWidth
|
|
} else if f.buttonsAlign == AlignCenter {
|
|
x = (boxWidth-buttonsWidth)/2 + startX
|
|
}
|
|
|
|
// In vertical layouts, buttons always appear after an empty line.
|
|
// if f.itemPadding == 0 {
|
|
// y++
|
|
// }
|
|
|
|
}
|
|
|
|
if len(f.buttons) > 0 {
|
|
y += f.buttonsPaddingTop
|
|
}
|
|
|
|
// Calculate positions of buttons.
|
|
for index, button := range f.buttons {
|
|
space := rightLimit - x
|
|
buttonWidth := buttonWidths[index]
|
|
if f.horizontal {
|
|
if space < buttonWidth-4 {
|
|
x = startX
|
|
y += 2
|
|
space = boxWidth
|
|
}
|
|
} else {
|
|
if space < 1 {
|
|
break // No space for this button anymore.
|
|
}
|
|
}
|
|
if buttonWidth > space {
|
|
buttonWidth = space
|
|
}
|
|
button.SetLabelColor(f.buttonTextColor).
|
|
SetLabelColorActivated(f.buttonBackgroundColor).
|
|
SetBackgroundColorActivated(f.buttonTextColor).
|
|
SetBackgroundColor(f.buttonBackgroundColor)
|
|
|
|
buttonIndex := index + len(f.items)
|
|
positions[buttonIndex].x = x
|
|
positions[buttonIndex].y = y
|
|
positions[buttonIndex].width = buttonWidth
|
|
positions[buttonIndex].height = 1
|
|
|
|
if button.HasFocus() {
|
|
focusedPosition = positions[buttonIndex]
|
|
}
|
|
|
|
x += buttonWidth + f.buttonsIndent
|
|
}
|
|
|
|
// Determine vertical offset based on the position of the focused item.
|
|
var offset int
|
|
if focusedPosition.y+focusedPosition.height > bottomLimit {
|
|
offset = focusedPosition.y + focusedPosition.height - bottomLimit
|
|
if focusedPosition.y-offset < topLimit {
|
|
offset = focusedPosition.y - topLimit
|
|
}
|
|
}
|
|
|
|
// Draw items.
|
|
for index, item := range f.items {
|
|
// Set position.
|
|
y := positions[index].y - offset
|
|
height := positions[index].height
|
|
item.SetRect(positions[index].x, y, positions[index].width, height)
|
|
|
|
// Is this item visible?
|
|
if y+height <= topLimit || (y > bottomLimit) {
|
|
continue
|
|
}
|
|
|
|
// Draw items with focus last (in case of overlaps).
|
|
if item.GetFocusable().HasFocus() {
|
|
defer item.Draw(screen)
|
|
} else {
|
|
item.Draw(screen)
|
|
}
|
|
}
|
|
|
|
// Draw buttons.
|
|
for index, button := range f.buttons {
|
|
// Set position.
|
|
buttonIndex := index + len(f.items)
|
|
y := positions[buttonIndex].y - offset
|
|
height := positions[buttonIndex].height
|
|
button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
|
|
|
|
// Is this button visible?
|
|
if y+height <= topLimit || (y >= bottomLimit && boxHeight > 0) {
|
|
continue
|
|
}
|
|
|
|
// Draw button.
|
|
button.Draw(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 {
|
|
return
|
|
}
|
|
|
|
// Hand on the focus to one of our child elements.
|
|
if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
|
|
f.focusedElement = 0
|
|
}
|
|
|
|
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--
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
if f.focusedElement < len(f.items) {
|
|
// We're selecting an item.
|
|
item := f.items[f.focusedElement]
|
|
item.SetFinishedFunc(itemHandler)
|
|
delegate(item)
|
|
} else {
|
|
// We're selecting a button.
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// HasFocus returns whether or not this primitive has focus.
|
|
func (f *Form) HasFocus() bool {
|
|
for _, item := range f.items {
|
|
if item.GetFocusable().HasFocus() {
|
|
return true
|
|
}
|
|
}
|
|
for _, button := range f.buttons {
|
|
if button.focus.HasFocus() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|