aboutsummaryrefslogtreecommitdiff
path: root/vendor/maunium.net/go/tview/form.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/maunium.net/go/tview/form.go')
-rw-r--r--vendor/maunium.net/go/tview/form.go499
1 files changed, 499 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/tview/form.go b/vendor/maunium.net/go/tview/form.go
new file mode 100644
index 0000000..b1e8f5e
--- /dev/null
+++ b/vendor/maunium.net/go/tview/form.go
@@ -0,0 +1,499 @@
+package tview
+
+import (
+ "strings"
+
+ "maunium.net/go/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
+
+ // SetFormAttributes sets a number of item attributes at once.
+ SetFormAttributes(label string, 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
+ // 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
+}
+
+// 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 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()
+}
+
+// NewForm returns a new form.
+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,
+ }
+
+ f.focus = f
+
+ 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.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 {
+ 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).
+ SetCurrentOption(initialOption).
+ SetOptions(options, selected))
+ 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.
+func (f *Form) AddFormItem(item FormItem) *Form {
+ f.items = append(f.items, item)
+ 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]
+}
+
+// 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.Draw(screen)
+
+ // 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 {
+ label := strings.TrimSpace(item.GetLabel())
+ labelWidth := StringWidth(label)
+ if labelWidth > maxLabelWidth {
+ maxLabelWidth = labelWidth
+ }
+ }
+ 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.
+ label := strings.TrimSpace(item.GetLabel())
+ labelWidth := StringWidth(label)
+ var itemWidth int
+ if f.horizontal {
+ fieldWidth := item.GetFieldWidth()
+ if fieldWidth == 0 {
+ fieldWidth = DefaultFormFieldWidth
+ }
+ label += " "
+ labelWidth++
+ itemWidth = labelWidth + fieldWidth
+ } else {
+ // We want all fields to align vertically.
+ label += strings.Repeat(" ", maxLabelWidth-labelWidth)
+ itemWidth = width
+ }
+
+ // 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(
+ label,
+ f.labelColor,
+ f.backgroundColor,
+ f.fieldTextColor,
+ f.fieldBackgroundColor,
+ )
+
+ // Save position.
+ positions[index].x = x
+ positions[index].y = y
+ positions[index].width = itemWidth
+ positions[index].height = 1
+ if item.GetFocusable().HasFocus() {
+ focusedPosition = positions[index]
+ }
+
+ // Advance to next item.
+ if f.horizontal {
+ x += itemWidth + f.itemPadding
+ } else {
+ y += 1 + f.itemPadding
+ }
+ }
+
+ // 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 + 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 += 2
+ space = width
+ }
+ } 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 + 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.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 {
+ 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)) {
+ 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
+ }
+ handler := func(key tcell.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)
+ }
+ }
+ }
+
+ if f.focusedElement < len(f.items) {
+ // We're selecting an item.
+ item := f.items[f.focusedElement]
+ item.SetFinishedFunc(handler)
+ delegate(item)
+ } else {
+ // We're selecting a button.
+ button := f.buttons[f.focusedElement-len(f.items)]
+ button.SetBlurFunc(handler)
+ 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
+}