chore(deps): 🚀 update module dependencies
* Add new dependencies for terminal handling and color management. * Include updates for tcell, go-colorful, tview, and uniseg. * Update golang.org/x/sys and golang.org/x/term for improved compatibility. * Ensure all dependencies are explicitly listed with their versions.
This commit is contained in:
896
vendor/github.com/rivo/tview/form.go
generated
vendored
Normal file
896
vendor/github.com/rivo/tview/form.go
generated
vendored
Normal file
@@ -0,0 +1,896 @@
|
||||
package tview
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// 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.
|
||||
DefaultFormFieldWidth = 10
|
||||
|
||||
// DefaultFormFieldHeight is the default field height of multi-line form
|
||||
// elements whose field height is flexible (0).
|
||||
DefaultFormFieldHeight = 5
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// SetFormAttributes sets a number of item attributes at once.
|
||||
SetFormAttributes(labelWidth 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 field width is flexible and may use as much space as
|
||||
// required.
|
||||
GetFieldWidth() int
|
||||
|
||||
// GetFieldHeight returns the height of the form item's field (the area which
|
||||
// is manipulated by the user). This value must be greater than 0.
|
||||
GetFieldHeight() int
|
||||
|
||||
// SetFinishedFunc 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), the Backtab key (move to previous field), or a negative
|
||||
// value, indicating that the action for the last known key should be
|
||||
// repeated.
|
||||
SetFinishedFunc(handler func(key tcell.Key)) FormItem
|
||||
|
||||
// SetDisabled sets whether or not the item is disabled / read-only. A form
|
||||
// must have at least one item that is not disabled.
|
||||
SetDisabled(disabled bool) FormItem
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// The number of empty cells between items.
|
||||
itemPadding int
|
||||
|
||||
// The index of the item or button which has focus. (Items are counted first,
|
||||
// buttons are counted last.) This is only used when the form itself receives
|
||||
// focus so that the last element that had focus keeps it.
|
||||
focusedElement int
|
||||
|
||||
// The label color.
|
||||
labelColor tcell.Color
|
||||
|
||||
// The style of the input area.
|
||||
fieldStyle tcell.Style
|
||||
|
||||
// The style of the buttons when they are not focused.
|
||||
buttonStyle tcell.Style
|
||||
|
||||
// The style of the buttons when they are focused.
|
||||
buttonActivatedStyle tcell.Style
|
||||
|
||||
// The style of the buttons when they are disabled.
|
||||
buttonDisabledStyle tcell.Style
|
||||
|
||||
// The last (valid) key that wsa sent to a "finished" handler or -1 if no
|
||||
// such key is known yet.
|
||||
lastFinishedKey tcell.Key
|
||||
|
||||
// An optional function which is called when the user hits Escape.
|
||||
cancel func()
|
||||
}
|
||||
|
||||
// NewForm returns a new form.
|
||||
func NewForm() *Form {
|
||||
box := NewBox().SetBorderPadding(1, 1, 1, 1)
|
||||
|
||||
f := &Form{
|
||||
Box: box,
|
||||
itemPadding: 1,
|
||||
labelColor: Styles.SecondaryTextColor,
|
||||
fieldStyle: tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),
|
||||
buttonStyle: tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),
|
||||
buttonActivatedStyle: tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),
|
||||
buttonDisabledStyle: tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.ContrastSecondaryTextColor),
|
||||
lastFinishedKey: tcell.KeyTab, // To skip over inactive elements at the beginning of the form.
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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.fieldStyle = f.fieldStyle.Background(color)
|
||||
return f
|
||||
}
|
||||
|
||||
// SetFieldTextColor sets the text color of the input areas.
|
||||
func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
|
||||
f.fieldStyle = f.fieldStyle.Foreground(color)
|
||||
return f
|
||||
}
|
||||
|
||||
// SetFieldStyle sets the style of the input areas. Attributes are currently
|
||||
// still ignored to maintain backwards compatibility.
|
||||
func (f *Form) SetFieldStyle(style tcell.Style) *Form {
|
||||
f.fieldStyle = style
|
||||
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. This is
|
||||
// also the text color of the buttons when they are focused.
|
||||
func (f *Form) SetButtonBackgroundColor(color tcell.Color) *Form {
|
||||
f.buttonStyle = f.buttonStyle.Background(color)
|
||||
f.buttonActivatedStyle = f.buttonActivatedStyle.Foreground(color)
|
||||
return f
|
||||
}
|
||||
|
||||
// SetButtonTextColor sets the color of the button texts. This is also the
|
||||
// background of the buttons when they are focused.
|
||||
func (f *Form) SetButtonTextColor(color tcell.Color) *Form {
|
||||
f.buttonStyle = f.buttonStyle.Foreground(color)
|
||||
f.buttonActivatedStyle = f.buttonActivatedStyle.Background(color)
|
||||
return f
|
||||
}
|
||||
|
||||
// SetButtonStyle sets the style of the buttons when they are not focused.
|
||||
func (f *Form) SetButtonStyle(style tcell.Style) *Form {
|
||||
f.buttonStyle = style
|
||||
return f
|
||||
}
|
||||
|
||||
// SetButtonActivatedStyle sets the style of the buttons when they are focused.
|
||||
func (f *Form) SetButtonActivatedStyle(style tcell.Style) *Form {
|
||||
f.buttonActivatedStyle = style
|
||||
return f
|
||||
}
|
||||
|
||||
// SetButtonDisabledStyle sets the style of the buttons when they are disabled.
|
||||
func (f *Form) SetButtonDisabledStyle(style tcell.Style) *Form {
|
||||
f.buttonDisabledStyle = style
|
||||
return f
|
||||
}
|
||||
|
||||
// SetFocus shifts the focus to the form element with the given index, counting
|
||||
// non-button items first and buttons last. Note that this index is only used
|
||||
// when the form itself receives focus.
|
||||
func (f *Form) SetFocus(index int) *Form {
|
||||
var current, future int
|
||||
for itemIndex, item := range f.items {
|
||||
if itemIndex == index {
|
||||
future = itemIndex
|
||||
}
|
||||
if item.HasFocus() {
|
||||
current = itemIndex
|
||||
}
|
||||
}
|
||||
for buttonIndex, button := range f.buttons {
|
||||
if buttonIndex+len(f.items) == index {
|
||||
future = buttonIndex + len(f.items)
|
||||
}
|
||||
if button.HasFocus() {
|
||||
current = buttonIndex + len(f.items)
|
||||
}
|
||||
}
|
||||
var focus func(p Primitive)
|
||||
focus = func(p Primitive) {
|
||||
p.Focus(focus)
|
||||
}
|
||||
if current != future {
|
||||
if current >= 0 && current < len(f.items) {
|
||||
f.items[current].Blur()
|
||||
} else if current >= len(f.items) && current < len(f.items)+len(f.buttons) {
|
||||
f.buttons[current-len(f.items)].Blur()
|
||||
}
|
||||
if future >= 0 && future < len(f.items) {
|
||||
focus(f.items[future])
|
||||
} else if future >= len(f.items) && future < len(f.items)+len(f.buttons) {
|
||||
focus(f.buttons[future-len(f.items)])
|
||||
}
|
||||
}
|
||||
f.focusedElement = future
|
||||
return f
|
||||
}
|
||||
|
||||
// AddTextArea adds a text area to the form. It has a label, an optional initial
|
||||
// text, a size (width and height) referring to the actual input area (a
|
||||
// fieldWidth of 0 extends it as far right as possible, a fieldHeight of 0 will
|
||||
// cause it to be [DefaultFormFieldHeight]), and a maximum number of bytes of
|
||||
// text allowed (0 means no limit).
|
||||
//
|
||||
// The optional callback function is invoked when the content of the text area
|
||||
// has changed. Note that especially for larger texts, this is an expensive
|
||||
// operation due to technical constraints of the [TextArea] primitive (every key
|
||||
// stroke leads to a new reallocation of the entire text).
|
||||
func (f *Form) AddTextArea(label, text string, fieldWidth, fieldHeight, maxLength int, changed func(text string)) *Form {
|
||||
if fieldHeight == 0 {
|
||||
fieldHeight = DefaultFormFieldHeight
|
||||
}
|
||||
textArea := NewTextArea().
|
||||
SetLabel(label).
|
||||
SetSize(fieldHeight, fieldWidth).
|
||||
SetMaxLength(maxLength)
|
||||
if text != "" {
|
||||
textArea.SetText(text, true)
|
||||
}
|
||||
if changed != nil {
|
||||
textArea.SetChangedFunc(func() {
|
||||
changed(textArea.GetText())
|
||||
})
|
||||
}
|
||||
f.items = append(f.items, textArea)
|
||||
return f
|
||||
}
|
||||
|
||||
// AddTextView adds a text view to the form. It has a label and text, a size
|
||||
// (width and height) referring to the actual text element (a fieldWidth of 0
|
||||
// extends it as far right as possible, a fieldHeight of 0 will cause it to be
|
||||
// [DefaultFormFieldHeight]), a flag to turn on/off dynamic colors, and a flag
|
||||
// to turn on/off scrolling. If scrolling is turned off, the text view will not
|
||||
// receive focus.
|
||||
func (f *Form) AddTextView(label, text string, fieldWidth, fieldHeight int, dynamicColors, scrollable bool) *Form {
|
||||
if fieldHeight == 0 {
|
||||
fieldHeight = DefaultFormFieldHeight
|
||||
}
|
||||
textArea := NewTextView().
|
||||
SetLabel(label).
|
||||
SetSize(fieldHeight, fieldWidth).
|
||||
SetDynamicColors(dynamicColors).
|
||||
SetScrollable(scrollable).
|
||||
SetText(text)
|
||||
f.items = append(f.items, textArea)
|
||||
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 {
|
||||
f.items = append(f.items, NewInputField().
|
||||
SetLabel(label).
|
||||
SetText(value).
|
||||
SetFieldWidth(fieldWidth).
|
||||
SetAcceptanceFunc(accept).
|
||||
SetChangedFunc(changed))
|
||||
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 []string, initialOption int, selected func(option string, optionIndex int)) *Form {
|
||||
f.items = append(f.items, NewDropDown().
|
||||
SetLabel(label).
|
||||
SetOptions(options, selected).
|
||||
SetCurrentOption(initialOption))
|
||||
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
|
||||
}
|
||||
|
||||
// AddImage adds an image to the form. It has a label and the image will fit in
|
||||
// the specified width and height (its aspect ratio is preserved). See
|
||||
// [Image.SetColors] for a description of the "colors" parameter. Images are not
|
||||
// interactive and are skipped over in a form. The "width" value may be 0
|
||||
// (adjust dynamically) but "height" should generally be a positive value.
|
||||
func (f *Form) AddImage(label string, image image.Image, width, height, colors int) *Form {
|
||||
f.items = append(f.items, NewImage().
|
||||
SetLabel(label).
|
||||
SetImage(image).
|
||||
SetSize(height, width).
|
||||
SetAlign(AlignTop, AlignLeft).
|
||||
SetColors(colors))
|
||||
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
|
||||
}
|
||||
|
||||
// GetButton returns the button at the specified 0-based index. Note that
|
||||
// buttons have been specially prepared for this form and modifying some of
|
||||
// their attributes may have unintended side effects.
|
||||
func (f *Form) GetButton(index int) *Button {
|
||||
return f.buttons[index]
|
||||
}
|
||||
|
||||
// RemoveButton removes the button at the specified position, starting with 0
|
||||
// for the button that was added first.
|
||||
func (f *Form) RemoveButton(index int) *Form {
|
||||
f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
|
||||
return f
|
||||
}
|
||||
|
||||
// GetButtonCount returns the number of buttons in this form.
|
||||
func (f *Form) GetButtonCount() int {
|
||||
return len(f.buttons)
|
||||
}
|
||||
|
||||
// GetButtonIndex returns the index of the button with the given label, starting
|
||||
// with 0 for the button that was added first. If no such label was found, -1
|
||||
// is returned.
|
||||
func (f *Form) GetButtonIndex(label string) int {
|
||||
for index, button := range f.buttons {
|
||||
if button.GetLabel() == label {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// 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.ClearButtons()
|
||||
}
|
||||
f.focusedElement = 0
|
||||
return f
|
||||
}
|
||||
|
||||
// ClearButtons removes all buttons from the form.
|
||||
func (f *Form) ClearButtons() *Form {
|
||||
f.buttons = nil
|
||||
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 {
|
||||
f.items = append(f.items, item)
|
||||
return f
|
||||
}
|
||||
|
||||
// GetFormItemCount returns the number of items in the form (not including the
|
||||
// buttons).
|
||||
func (f *Form) GetFormItemCount() int {
|
||||
return len(f.items)
|
||||
}
|
||||
|
||||
// GetFormItem returns the form item 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]
|
||||
}
|
||||
|
||||
// RemoveFormItem removes 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) RemoveFormItem(index int) *Form {
|
||||
f.items = append(f.items[:index], f.items[index+1:]...)
|
||||
return f
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// GetFormItemIndex returns the index of the first form element with the given
|
||||
// label. If no such element is found, -1 is returned. Buttons are not searched
|
||||
// and will therefore not be returned.
|
||||
func (f *Form) GetFormItemIndex(label string) int {
|
||||
for index, item := range f.items {
|
||||
if item.GetLabel() == label {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetFocusedItemIndex returns the indices of the form element or button which
|
||||
// currently has focus. If they don't, -1 is returned respectively.
|
||||
func (f *Form) GetFocusedItemIndex() (formItem, button int) {
|
||||
index := f.focusIndex()
|
||||
if index < 0 {
|
||||
return -1, -1
|
||||
}
|
||||
if index < len(f.items) {
|
||||
return index, -1
|
||||
}
|
||||
return -1, index - len(f.items)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (f *Form) Draw(screen tcell.Screen) {
|
||||
f.Box.DrawForSubclass(screen, f)
|
||||
|
||||
// Determine the actual item that has focus.
|
||||
if index := f.focusIndex(); index >= 0 {
|
||||
f.focusedElement = index
|
||||
}
|
||||
|
||||
// Determine the dimensions.
|
||||
x, y, width, height := f.GetInnerRect()
|
||||
topLimit := y
|
||||
bottomLimit := y + height
|
||||
rightLimit := x + width
|
||||
startX := x
|
||||
|
||||
// Find the longest label.
|
||||
var maxLabelWidth int
|
||||
for _, item := range f.items {
|
||||
labelWidth := TaggedStringWidth(item.GetLabel())
|
||||
if labelWidth > maxLabelWidth {
|
||||
maxLabelWidth = labelWidth
|
||||
}
|
||||
}
|
||||
maxLabelWidth++ // Add one space.
|
||||
|
||||
// Calculate positions of form items.
|
||||
type position struct{ x, y, width, height int }
|
||||
positions := make([]position, len(f.items)+len(f.buttons))
|
||||
var (
|
||||
focusedPosition position
|
||||
lineHeight = 1
|
||||
)
|
||||
for index, item := range f.items {
|
||||
// Calculate the space needed.
|
||||
labelWidth := TaggedStringWidth(item.GetLabel())
|
||||
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
|
||||
}
|
||||
itemHeight := item.GetFieldHeight()
|
||||
if itemHeight <= 0 {
|
||||
itemHeight = DefaultFormFieldHeight
|
||||
}
|
||||
|
||||
// Advance to next line if there is no space.
|
||||
if f.horizontal && x+labelWidth+1 >= rightLimit {
|
||||
x = startX
|
||||
y += lineHeight + 1
|
||||
lineHeight = itemHeight
|
||||
}
|
||||
|
||||
// Update line height.
|
||||
if itemHeight > lineHeight {
|
||||
lineHeight = itemHeight
|
||||
}
|
||||
|
||||
// Adjust the item's attributes.
|
||||
if x+itemWidth >= rightLimit {
|
||||
itemWidth = rightLimit - x
|
||||
}
|
||||
fieldTextColor, fieldBackgroundColor, _ := f.fieldStyle.Decompose()
|
||||
item.SetFormAttributes(
|
||||
labelWidth,
|
||||
f.labelColor,
|
||||
f.backgroundColor,
|
||||
fieldTextColor,
|
||||
fieldBackgroundColor,
|
||||
)
|
||||
|
||||
// Save position.
|
||||
positions[index].x = x
|
||||
positions[index].y = y
|
||||
positions[index].width = itemWidth
|
||||
positions[index].height = itemHeight
|
||||
if item.HasFocus() {
|
||||
focusedPosition = positions[index]
|
||||
}
|
||||
|
||||
// Advance to next item.
|
||||
if f.horizontal {
|
||||
x += itemWidth + f.itemPadding
|
||||
} else {
|
||||
y += itemHeight + f.itemPadding
|
||||
}
|
||||
}
|
||||
|
||||
// How wide are the buttons?
|
||||
buttonWidths := make([]int, len(f.buttons))
|
||||
buttonsWidth := 0
|
||||
for index, button := range f.buttons {
|
||||
w := TaggedStringWidth(button.GetLabel()) + 4
|
||||
buttonWidths[index] = w
|
||||
buttonsWidth += w + 1
|
||||
}
|
||||
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 = (x + rightLimit - buttonsWidth) / 2
|
||||
}
|
||||
|
||||
// In vertical layouts, buttons always appear after an empty line.
|
||||
if f.itemPadding == 0 {
|
||||
y++
|
||||
}
|
||||
}
|
||||
|
||||
// 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 += lineHeight + 1
|
||||
space = width
|
||||
lineHeight = 1
|
||||
}
|
||||
} else {
|
||||
if space < 1 {
|
||||
break // No space for this button anymore.
|
||||
}
|
||||
}
|
||||
if buttonWidth > space {
|
||||
buttonWidth = space
|
||||
}
|
||||
button.SetStyle(f.buttonStyle).
|
||||
SetActivatedStyle(f.buttonActivatedStyle).
|
||||
SetDisabledStyle(f.buttonDisabledStyle)
|
||||
|
||||
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 + 1
|
||||
}
|
||||
|
||||
// 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.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 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Draw button.
|
||||
button.Draw(screen)
|
||||
}
|
||||
}
|
||||
|
||||
// Focus is called by the application when the primitive receives focus.
|
||||
func (f *Form) Focus(delegate func(p Primitive)) {
|
||||
// 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
|
||||
}
|
||||
var handler func(key tcell.Key)
|
||||
handler = func(key tcell.Key) {
|
||||
if key >= 0 {
|
||||
f.lastFinishedKey = key
|
||||
}
|
||||
switch key {
|
||||
case tcell.KeyTab, tcell.KeyEnter:
|
||||
f.focusedElement++
|
||||
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)
|
||||
}
|
||||
default:
|
||||
if key < 0 && f.lastFinishedKey >= 0 {
|
||||
// Repeat the last action.
|
||||
handler(f.lastFinishedKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track whether a form item has focus.
|
||||
var itemFocused bool
|
||||
f.hasFocus = false
|
||||
|
||||
// Set the handler and focus for all items and buttons.
|
||||
for index, button := range f.buttons {
|
||||
button.SetExitFunc(handler)
|
||||
if f.focusedElement == index+len(f.items) {
|
||||
if button.IsDisabled() {
|
||||
f.focusedElement++
|
||||
if f.focusedElement >= len(f.items)+len(f.buttons) {
|
||||
f.focusedElement = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
itemFocused = true
|
||||
func(b *Button) { // Wrapping might not be necessary anymore in future Go versions.
|
||||
defer delegate(b)
|
||||
}(button)
|
||||
}
|
||||
}
|
||||
for index, item := range f.items {
|
||||
item.SetFinishedFunc(handler)
|
||||
if f.focusedElement == index {
|
||||
itemFocused = true
|
||||
func(i FormItem) { // Wrapping might not be necessary anymore in future Go versions.
|
||||
defer delegate(i)
|
||||
}(item)
|
||||
}
|
||||
}
|
||||
|
||||
// If no item was focused, focus the form itself.
|
||||
if !itemFocused {
|
||||
f.Box.Focus(delegate)
|
||||
}
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (f *Form) HasFocus() bool {
|
||||
if f.focusIndex() >= 0 {
|
||||
return true
|
||||
}
|
||||
return f.Box.HasFocus()
|
||||
}
|
||||
|
||||
// focusIndex returns the index of the currently focused item, counting form
|
||||
// items first, then buttons. A negative value indicates that no containeed item
|
||||
// has focus.
|
||||
func (f *Form) focusIndex() int {
|
||||
for index, item := range f.items {
|
||||
if item.HasFocus() {
|
||||
return index
|
||||
}
|
||||
}
|
||||
for index, button := range f.buttons {
|
||||
if button.HasFocus() {
|
||||
return len(f.items) + index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
// At the end, update f.focusedElement and prepare current item/button.
|
||||
defer func() {
|
||||
if consumed {
|
||||
index := f.focusIndex()
|
||||
if index >= 0 {
|
||||
f.focusedElement = index
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Determine items to pass mouse events to.
|
||||
for _, item := range f.items {
|
||||
// Exclude TextView items from mouse-down events as they are
|
||||
// read-only items and thus should not be focused.
|
||||
if _, ok := item.(*TextView); ok && action == MouseLeftDown {
|
||||
continue
|
||||
}
|
||||
|
||||
consumed, capture = item.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, button := range f.buttons {
|
||||
consumed, capture = button.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// A mouse down anywhere else will return the focus to the last selected
|
||||
// element.
|
||||
if action == MouseLeftDown && f.InRect(event.Position()) {
|
||||
f.Focus(setFocus)
|
||||
consumed = true
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// InputHandler returns the handler for this primitive.
|
||||
func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
for _, item := range f.items {
|
||||
if item != nil && item.HasFocus() {
|
||||
if handler := item.InputHandler(); handler != nil {
|
||||
handler(event, setFocus)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, button := range f.buttons {
|
||||
if button.HasFocus() {
|
||||
if handler := button.InputHandler(); handler != nil {
|
||||
handler(event, setFocus)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// PasteHandler returns the handler for this primitive.
|
||||
func (f *Form) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {
|
||||
return f.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {
|
||||
for _, item := range f.items {
|
||||
if item != nil && item.HasFocus() {
|
||||
if handler := item.PasteHandler(); handler != nil {
|
||||
handler(pastedText, setFocus)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, button := range f.buttons {
|
||||
if button.HasFocus() {
|
||||
if handler := button.PasteHandler(); handler != nil {
|
||||
handler(pastedText, setFocus)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user