nt

A sensible note-taking program
git clone git://git.laack.co/nt.git
Log | Files | Refs | README

form.go (26903B)


      1 package tview
      2 
      3 import (
      4 	"image"
      5 
      6 	"github.com/gdamore/tcell/v2"
      7 )
      8 
      9 var (
     10 	// DefaultFormFieldWidth is the default field screen width of form elements
     11 	// whose field width is flexible (0). This is used in the Form class for
     12 	// horizontal layouts.
     13 	DefaultFormFieldWidth = 10
     14 
     15 	// DefaultFormFieldHeight is the default field height of multi-line form
     16 	// elements whose field height is flexible (0).
     17 	DefaultFormFieldHeight = 5
     18 )
     19 
     20 // FormItem is the interface all form items must implement to be able to be
     21 // included in a form.
     22 type FormItem interface {
     23 	Primitive
     24 
     25 	// GetLabel returns the item's label text.
     26 	GetLabel() string
     27 
     28 	// SetFormAttributes sets a number of item attributes at once.
     29 	SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem
     30 
     31 	// GetFieldWidth returns the width of the form item's field (the area which
     32 	// is manipulated by the user) in number of screen cells. A value of 0
     33 	// indicates the field width is flexible and may use as much space as
     34 	// required.
     35 	GetFieldWidth() int
     36 
     37 	// GetFieldHeight returns the height of the form item's field (the area which
     38 	// is manipulated by the user). This value must be greater than 0.
     39 	GetFieldHeight() int
     40 
     41 	// SetFinishedFunc sets the handler function for when the user finished
     42 	// entering data into the item. The handler may receive events for the
     43 	// Enter key (we're done), the Escape key (cancel input), the Tab key (move
     44 	// to next field), the Backtab key (move to previous field), or a negative
     45 	// value, indicating that the action for the last known key should be
     46 	// repeated.
     47 	SetFinishedFunc(handler func(key tcell.Key)) FormItem
     48 
     49 	// SetDisabled sets whether or not the item is disabled / read-only. A form
     50 	// must have at least one item that is not disabled.
     51 	SetDisabled(disabled bool) FormItem
     52 }
     53 
     54 // Form allows you to combine multiple one-line form elements into a vertical
     55 // or horizontal layout. Form elements include types such as InputField or
     56 // Checkbox. These elements can be optionally followed by one or more buttons
     57 // for which you can define form-wide actions (e.g. Save, Clear, Cancel).
     58 //
     59 // See https://github.com/rivo/tview/wiki/Form for an example.
     60 type Form struct {
     61 	*Box
     62 
     63 	// The items of the form (one row per item).
     64 	items []FormItem
     65 
     66 	// The buttons of the form.
     67 	buttons []*Button
     68 
     69 	// If set to true, instead of position items and buttons from top to bottom,
     70 	// they are positioned from left to right.
     71 	horizontal bool
     72 
     73 	// The alignment of the buttons.
     74 	buttonsAlign int
     75 
     76 	// The number of empty cells between items.
     77 	itemPadding int
     78 
     79 	// The index of the item or button which has focus. (Items are counted first,
     80 	// buttons are counted last.) This is only used when the form itself receives
     81 	// focus so that the last element that had focus keeps it.
     82 	focusedElement int
     83 
     84 	// The label color.
     85 	labelColor tcell.Color
     86 
     87 	// The style of the input area.
     88 	fieldStyle tcell.Style
     89 
     90 	// The style of the buttons when they are not focused.
     91 	buttonStyle tcell.Style
     92 
     93 	// The style of the buttons when they are focused.
     94 	buttonActivatedStyle tcell.Style
     95 
     96 	// The style of the buttons when they are disabled.
     97 	buttonDisabledStyle tcell.Style
     98 
     99 	// The last (valid) key that wsa sent to a "finished" handler or -1 if no
    100 	// such key is known yet.
    101 	lastFinishedKey tcell.Key
    102 
    103 	// An optional function which is called when the user hits Escape.
    104 	cancel func()
    105 }
    106 
    107 // NewForm returns a new form.
    108 func NewForm() *Form {
    109 	box := NewBox().SetBorderPadding(1, 1, 1, 1)
    110 
    111 	f := &Form{
    112 		Box:                  box,
    113 		itemPadding:          1,
    114 		labelColor:           Styles.SecondaryTextColor,
    115 		fieldStyle:           tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),
    116 		buttonStyle:          tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),
    117 		buttonActivatedStyle: tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),
    118 		buttonDisabledStyle:  tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.ContrastSecondaryTextColor),
    119 		lastFinishedKey:      tcell.KeyTab, // To skip over inactive elements at the beginning of the form.
    120 	}
    121 
    122 	return f
    123 }
    124 
    125 // SetItemPadding sets the number of empty rows between form items for vertical
    126 // layouts and the number of empty cells between form items for horizontal
    127 // layouts.
    128 func (f *Form) SetItemPadding(padding int) *Form {
    129 	f.itemPadding = padding
    130 	return f
    131 }
    132 
    133 // SetHorizontal sets the direction the form elements are laid out. If set to
    134 // true, instead of positioning them from top to bottom (the default), they are
    135 // positioned from left to right, moving into the next row if there is not
    136 // enough space.
    137 func (f *Form) SetHorizontal(horizontal bool) *Form {
    138 	f.horizontal = horizontal
    139 	return f
    140 }
    141 
    142 // SetLabelColor sets the color of the labels.
    143 func (f *Form) SetLabelColor(color tcell.Color) *Form {
    144 	f.labelColor = color
    145 	return f
    146 }
    147 
    148 // SetFieldBackgroundColor sets the background color of the input areas.
    149 func (f *Form) SetFieldBackgroundColor(color tcell.Color) *Form {
    150 	f.fieldStyle = f.fieldStyle.Background(color)
    151 	return f
    152 }
    153 
    154 // SetFieldTextColor sets the text color of the input areas.
    155 func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
    156 	f.fieldStyle = f.fieldStyle.Foreground(color)
    157 	return f
    158 }
    159 
    160 // SetFieldStyle sets the style of the input areas. Attributes are currently
    161 // still ignored to maintain backwards compatibility.
    162 func (f *Form) SetFieldStyle(style tcell.Style) *Form {
    163 	f.fieldStyle = style
    164 	return f
    165 }
    166 
    167 // SetButtonsAlign sets how the buttons align horizontally, one of AlignLeft
    168 // (the default), AlignCenter, and AlignRight. This is only
    169 func (f *Form) SetButtonsAlign(align int) *Form {
    170 	f.buttonsAlign = align
    171 	return f
    172 }
    173 
    174 // SetButtonBackgroundColor sets the background color of the buttons. This is
    175 // also the text color of the buttons when they are focused.
    176 func (f *Form) SetButtonBackgroundColor(color tcell.Color) *Form {
    177 	f.buttonStyle = f.buttonStyle.Background(color)
    178 	f.buttonActivatedStyle = f.buttonActivatedStyle.Foreground(color)
    179 	return f
    180 }
    181 
    182 // SetButtonTextColor sets the color of the button texts. This is also the
    183 // background of the buttons when they are focused.
    184 func (f *Form) SetButtonTextColor(color tcell.Color) *Form {
    185 	f.buttonStyle = f.buttonStyle.Foreground(color)
    186 	f.buttonActivatedStyle = f.buttonActivatedStyle.Background(color)
    187 	return f
    188 }
    189 
    190 // SetButtonStyle sets the style of the buttons when they are not focused.
    191 func (f *Form) SetButtonStyle(style tcell.Style) *Form {
    192 	f.buttonStyle = style
    193 	return f
    194 }
    195 
    196 // SetButtonActivatedStyle sets the style of the buttons when they are focused.
    197 func (f *Form) SetButtonActivatedStyle(style tcell.Style) *Form {
    198 	f.buttonActivatedStyle = style
    199 	return f
    200 }
    201 
    202 // SetButtonDisabledStyle sets the style of the buttons when they are disabled.
    203 func (f *Form) SetButtonDisabledStyle(style tcell.Style) *Form {
    204 	f.buttonDisabledStyle = style
    205 	return f
    206 }
    207 
    208 // SetFocus shifts the focus to the form element with the given index, counting
    209 // non-button items first and buttons last. Note that this index is only used
    210 // when the form itself receives focus.
    211 func (f *Form) SetFocus(index int) *Form {
    212 	var current, future int
    213 	for itemIndex, item := range f.items {
    214 		if itemIndex == index {
    215 			future = itemIndex
    216 		}
    217 		if item.HasFocus() {
    218 			current = itemIndex
    219 		}
    220 	}
    221 	for buttonIndex, button := range f.buttons {
    222 		if buttonIndex+len(f.items) == index {
    223 			future = buttonIndex + len(f.items)
    224 		}
    225 		if button.HasFocus() {
    226 			current = buttonIndex + len(f.items)
    227 		}
    228 	}
    229 	var focus func(p Primitive)
    230 	focus = func(p Primitive) {
    231 		p.Focus(focus)
    232 	}
    233 	if current != future {
    234 		if current >= 0 && current < len(f.items) {
    235 			f.items[current].Blur()
    236 		} else if current >= len(f.items) && current < len(f.items)+len(f.buttons) {
    237 			f.buttons[current-len(f.items)].Blur()
    238 		}
    239 		if future >= 0 && future < len(f.items) {
    240 			focus(f.items[future])
    241 		} else if future >= len(f.items) && future < len(f.items)+len(f.buttons) {
    242 			focus(f.buttons[future-len(f.items)])
    243 		}
    244 	}
    245 	f.focusedElement = future
    246 	return f
    247 }
    248 
    249 // AddTextArea adds a text area to the form. It has a label, an optional initial
    250 // text, a size (width and height) referring to the actual input area (a
    251 // fieldWidth of 0 extends it as far right as possible, a fieldHeight of 0 will
    252 // cause it to be [DefaultFormFieldHeight]), and a maximum number of bytes of
    253 // text allowed (0 means no limit).
    254 //
    255 // The optional callback function is invoked when the content of the text area
    256 // has changed. Note that especially for larger texts, this is an expensive
    257 // operation due to technical constraints of the [TextArea] primitive (every key
    258 // stroke leads to a new reallocation of the entire text).
    259 func (f *Form) AddTextArea(label, text string, fieldWidth, fieldHeight, maxLength int, changed func(text string)) *Form {
    260 	if fieldHeight == 0 {
    261 		fieldHeight = DefaultFormFieldHeight
    262 	}
    263 	textArea := NewTextArea().
    264 		SetLabel(label).
    265 		SetSize(fieldHeight, fieldWidth).
    266 		SetMaxLength(maxLength)
    267 	if text != "" {
    268 		textArea.SetText(text, true)
    269 	}
    270 	if changed != nil {
    271 		textArea.SetChangedFunc(func() {
    272 			changed(textArea.GetText())
    273 		})
    274 	}
    275 	f.items = append(f.items, textArea)
    276 	return f
    277 }
    278 
    279 // AddTextView adds a text view to the form. It has a label and text, a size
    280 // (width and height) referring to the actual text element (a fieldWidth of 0
    281 // extends it as far right as possible, a fieldHeight of 0 will cause it to be
    282 // [DefaultFormFieldHeight]), a flag to turn on/off dynamic colors, and a flag
    283 // to turn on/off scrolling. If scrolling is turned off, the text view will not
    284 // receive focus.
    285 func (f *Form) AddTextView(label, text string, fieldWidth, fieldHeight int, dynamicColors, scrollable bool) *Form {
    286 	if fieldHeight == 0 {
    287 		fieldHeight = DefaultFormFieldHeight
    288 	}
    289 	textArea := NewTextView().
    290 		SetLabel(label).
    291 		SetSize(fieldHeight, fieldWidth).
    292 		SetDynamicColors(dynamicColors).
    293 		SetScrollable(scrollable).
    294 		SetText(text)
    295 	f.items = append(f.items, textArea)
    296 	return f
    297 }
    298 
    299 // AddInputField adds an input field to the form. It has a label, an optional
    300 // initial value, a field width (a value of 0 extends it as far as possible),
    301 // an optional accept function to validate the item's value (set to nil to
    302 // accept any text), and an (optional) callback function which is invoked when
    303 // the input field's text has changed.
    304 func (f *Form) AddInputField(label, value string, fieldWidth int, accept func(textToCheck string, lastChar rune) bool, changed func(text string)) *Form {
    305 	f.items = append(f.items, NewInputField().
    306 		SetLabel(label).
    307 		SetText(value).
    308 		SetFieldWidth(fieldWidth).
    309 		SetAcceptanceFunc(accept).
    310 		SetChangedFunc(changed))
    311 	return f
    312 }
    313 
    314 // AddPasswordField adds a password field to the form. This is similar to an
    315 // input field except that the user's input not shown. Instead, a "mask"
    316 // character is displayed. The password field has a label, an optional initial
    317 // value, a field width (a value of 0 extends it as far as possible), and an
    318 // (optional) callback function which is invoked when the input field's text has
    319 // changed.
    320 func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune, changed func(text string)) *Form {
    321 	if mask == 0 {
    322 		mask = '*'
    323 	}
    324 	f.items = append(f.items, NewInputField().
    325 		SetLabel(label).
    326 		SetText(value).
    327 		SetFieldWidth(fieldWidth).
    328 		SetMaskCharacter(mask).
    329 		SetChangedFunc(changed))
    330 	return f
    331 }
    332 
    333 // AddDropDown adds a drop-down element to the form. It has a label, options,
    334 // and an (optional) callback function which is invoked when an option was
    335 // selected. The initial option may be a negative value to indicate that no
    336 // option is currently selected.
    337 func (f *Form) AddDropDown(label string, options []string, initialOption int, selected func(option string, optionIndex int)) *Form {
    338 	f.items = append(f.items, NewDropDown().
    339 		SetLabel(label).
    340 		SetOptions(options, selected).
    341 		SetCurrentOption(initialOption))
    342 	return f
    343 }
    344 
    345 // AddCheckbox adds a checkbox to the form. It has a label, an initial state,
    346 // and an (optional) callback function which is invoked when the state of the
    347 // checkbox was changed by the user.
    348 func (f *Form) AddCheckbox(label string, checked bool, changed func(checked bool)) *Form {
    349 	f.items = append(f.items, NewCheckbox().
    350 		SetLabel(label).
    351 		SetChecked(checked).
    352 		SetChangedFunc(changed))
    353 	return f
    354 }
    355 
    356 // AddImage adds an image to the form. It has a label and the image will fit in
    357 // the specified width and height (its aspect ratio is preserved). See
    358 // [Image.SetColors] for a description of the "colors" parameter. Images are not
    359 // interactive and are skipped over in a form. The "width" value may be 0
    360 // (adjust dynamically) but "height" should generally be a positive value.
    361 func (f *Form) AddImage(label string, image image.Image, width, height, colors int) *Form {
    362 	f.items = append(f.items, NewImage().
    363 		SetLabel(label).
    364 		SetImage(image).
    365 		SetSize(height, width).
    366 		SetAlign(AlignTop, AlignLeft).
    367 		SetColors(colors))
    368 	return f
    369 }
    370 
    371 // AddButton adds a new button to the form. The "selected" function is called
    372 // when the user selects this button. It may be nil.
    373 func (f *Form) AddButton(label string, selected func()) *Form {
    374 	f.buttons = append(f.buttons, NewButton(label).SetSelectedFunc(selected))
    375 	return f
    376 }
    377 
    378 // GetButton returns the button at the specified 0-based index. Note that
    379 // buttons have been specially prepared for this form and modifying some of
    380 // their attributes may have unintended side effects.
    381 func (f *Form) GetButton(index int) *Button {
    382 	return f.buttons[index]
    383 }
    384 
    385 // RemoveButton removes the button at the specified position, starting with 0
    386 // for the button that was added first.
    387 func (f *Form) RemoveButton(index int) *Form {
    388 	f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
    389 	return f
    390 }
    391 
    392 // GetButtonCount returns the number of buttons in this form.
    393 func (f *Form) GetButtonCount() int {
    394 	return len(f.buttons)
    395 }
    396 
    397 // GetButtonIndex returns the index of the button with the given label, starting
    398 // with 0 for the button that was added first. If no such label was found, -1
    399 // is returned.
    400 func (f *Form) GetButtonIndex(label string) int {
    401 	for index, button := range f.buttons {
    402 		if button.GetLabel() == label {
    403 			return index
    404 		}
    405 	}
    406 	return -1
    407 }
    408 
    409 // Clear removes all input elements from the form, including the buttons if
    410 // specified.
    411 func (f *Form) Clear(includeButtons bool) *Form {
    412 	f.items = nil
    413 	if includeButtons {
    414 		f.ClearButtons()
    415 	}
    416 	f.focusedElement = 0
    417 	return f
    418 }
    419 
    420 // ClearButtons removes all buttons from the form.
    421 func (f *Form) ClearButtons() *Form {
    422 	f.buttons = nil
    423 	return f
    424 }
    425 
    426 // AddFormItem adds a new item to the form. This can be used to add your own
    427 // objects to the form. Note, however, that the Form class will override some
    428 // of its attributes to make it work in the form context. Specifically, these
    429 // are:
    430 //
    431 //   - The label width
    432 //   - The label color
    433 //   - The background color
    434 //   - The field text color
    435 //   - The field background color
    436 func (f *Form) AddFormItem(item FormItem) *Form {
    437 	f.items = append(f.items, item)
    438 	return f
    439 }
    440 
    441 // GetFormItemCount returns the number of items in the form (not including the
    442 // buttons).
    443 func (f *Form) GetFormItemCount() int {
    444 	return len(f.items)
    445 }
    446 
    447 // GetFormItem returns the form item at the given position, starting with index
    448 // 0. Elements are referenced in the order they were added. Buttons are not
    449 // included.
    450 func (f *Form) GetFormItem(index int) FormItem {
    451 	return f.items[index]
    452 }
    453 
    454 // RemoveFormItem removes the form element at the given position, starting with
    455 // index 0. Elements are referenced in the order they were added. Buttons are
    456 // not included.
    457 func (f *Form) RemoveFormItem(index int) *Form {
    458 	f.items = append(f.items[:index], f.items[index+1:]...)
    459 	return f
    460 }
    461 
    462 // GetFormItemByLabel returns the first form element with the given label. If
    463 // no such element is found, nil is returned. Buttons are not searched and will
    464 // therefore not be returned.
    465 func (f *Form) GetFormItemByLabel(label string) FormItem {
    466 	for _, item := range f.items {
    467 		if item.GetLabel() == label {
    468 			return item
    469 		}
    470 	}
    471 	return nil
    472 }
    473 
    474 // GetFormItemIndex returns the index of the first form element with the given
    475 // label. If no such element is found, -1 is returned. Buttons are not searched
    476 // and will therefore not be returned.
    477 func (f *Form) GetFormItemIndex(label string) int {
    478 	for index, item := range f.items {
    479 		if item.GetLabel() == label {
    480 			return index
    481 		}
    482 	}
    483 	return -1
    484 }
    485 
    486 // GetFocusedItemIndex returns the indices of the form element or button which
    487 // currently has focus. If they don't, -1 is returned respectively.
    488 func (f *Form) GetFocusedItemIndex() (formItem, button int) {
    489 	index := f.focusIndex()
    490 	if index < 0 {
    491 		return -1, -1
    492 	}
    493 	if index < len(f.items) {
    494 		return index, -1
    495 	}
    496 	return -1, index - len(f.items)
    497 }
    498 
    499 // SetCancelFunc sets a handler which is called when the user hits the Escape
    500 // key.
    501 func (f *Form) SetCancelFunc(callback func()) *Form {
    502 	f.cancel = callback
    503 	return f
    504 }
    505 
    506 // Draw draws this primitive onto the screen.
    507 func (f *Form) Draw(screen tcell.Screen) {
    508 	f.Box.DrawForSubclass(screen, f)
    509 
    510 	// Determine the actual item that has focus.
    511 	if index := f.focusIndex(); index >= 0 {
    512 		f.focusedElement = index
    513 	}
    514 
    515 	// Determine the dimensions.
    516 	x, y, width, height := f.GetInnerRect()
    517 	topLimit := y
    518 	bottomLimit := y + height
    519 	rightLimit := x + width
    520 	startX := x
    521 
    522 	// Find the longest label.
    523 	var maxLabelWidth int
    524 	for _, item := range f.items {
    525 		labelWidth := TaggedStringWidth(item.GetLabel())
    526 		if labelWidth > maxLabelWidth {
    527 			maxLabelWidth = labelWidth
    528 		}
    529 	}
    530 	maxLabelWidth++ // Add one space.
    531 
    532 	// Calculate positions of form items.
    533 	type position struct{ x, y, width, height int }
    534 	positions := make([]position, len(f.items)+len(f.buttons))
    535 	var (
    536 		focusedPosition position
    537 		lineHeight      = 1
    538 	)
    539 	for index, item := range f.items {
    540 		// Calculate the space needed.
    541 		labelWidth := TaggedStringWidth(item.GetLabel())
    542 		var itemWidth int
    543 		if f.horizontal {
    544 			fieldWidth := item.GetFieldWidth()
    545 			if fieldWidth <= 0 {
    546 				fieldWidth = DefaultFormFieldWidth
    547 			}
    548 			labelWidth++
    549 			itemWidth = labelWidth + fieldWidth
    550 		} else {
    551 			// We want all fields to align vertically.
    552 			labelWidth = maxLabelWidth
    553 			itemWidth = width
    554 		}
    555 		itemHeight := item.GetFieldHeight()
    556 		if itemHeight <= 0 {
    557 			itemHeight = DefaultFormFieldHeight
    558 		}
    559 
    560 		// Advance to next line if there is no space.
    561 		if f.horizontal && x+labelWidth+1 >= rightLimit {
    562 			x = startX
    563 			y += lineHeight + 1
    564 			lineHeight = itemHeight
    565 		}
    566 
    567 		// Update line height.
    568 		if itemHeight > lineHeight {
    569 			lineHeight = itemHeight
    570 		}
    571 
    572 		// Adjust the item's attributes.
    573 		if x+itemWidth >= rightLimit {
    574 			itemWidth = rightLimit - x
    575 		}
    576 		fieldTextColor, fieldBackgroundColor, _ := f.fieldStyle.Decompose()
    577 		item.SetFormAttributes(
    578 			labelWidth,
    579 			f.labelColor,
    580 			f.backgroundColor,
    581 			fieldTextColor,
    582 			fieldBackgroundColor,
    583 		)
    584 
    585 		// Save position.
    586 		positions[index].x = x
    587 		positions[index].y = y
    588 		positions[index].width = itemWidth
    589 		positions[index].height = itemHeight
    590 		if item.HasFocus() {
    591 			focusedPosition = positions[index]
    592 		}
    593 
    594 		// Advance to next item.
    595 		if f.horizontal {
    596 			x += itemWidth + f.itemPadding
    597 		} else {
    598 			y += itemHeight + f.itemPadding
    599 		}
    600 	}
    601 
    602 	// How wide are the buttons?
    603 	buttonWidths := make([]int, len(f.buttons))
    604 	buttonsWidth := 0
    605 	for index, button := range f.buttons {
    606 		w := TaggedStringWidth(button.GetLabel()) + 4
    607 		buttonWidths[index] = w
    608 		buttonsWidth += w + 1
    609 	}
    610 	buttonsWidth--
    611 
    612 	// Where do we place them?
    613 	if !f.horizontal && x+buttonsWidth < rightLimit {
    614 		if f.buttonsAlign == AlignRight {
    615 			x = rightLimit - buttonsWidth
    616 		} else if f.buttonsAlign == AlignCenter {
    617 			x = (x + rightLimit - buttonsWidth) / 2
    618 		}
    619 
    620 		// In vertical layouts, buttons always appear after an empty line.
    621 		if f.itemPadding == 0 {
    622 			y++
    623 		}
    624 	}
    625 
    626 	// Calculate positions of buttons.
    627 	for index, button := range f.buttons {
    628 		space := rightLimit - x
    629 		buttonWidth := buttonWidths[index]
    630 		if f.horizontal {
    631 			if space < buttonWidth-4 {
    632 				x = startX
    633 				y += lineHeight + 1
    634 				space = width
    635 				lineHeight = 1
    636 			}
    637 		} else {
    638 			if space < 1 {
    639 				break // No space for this button anymore.
    640 			}
    641 		}
    642 		if buttonWidth > space {
    643 			buttonWidth = space
    644 		}
    645 		button.SetStyle(f.buttonStyle).
    646 			SetActivatedStyle(f.buttonActivatedStyle).
    647 			SetDisabledStyle(f.buttonDisabledStyle)
    648 
    649 		buttonIndex := index + len(f.items)
    650 		positions[buttonIndex].x = x
    651 		positions[buttonIndex].y = y
    652 		positions[buttonIndex].width = buttonWidth
    653 		positions[buttonIndex].height = 1
    654 
    655 		if button.HasFocus() {
    656 			focusedPosition = positions[buttonIndex]
    657 		}
    658 
    659 		x += buttonWidth + 1
    660 	}
    661 
    662 	// Determine vertical offset based on the position of the focused item.
    663 	var offset int
    664 	if focusedPosition.y+focusedPosition.height > bottomLimit {
    665 		offset = focusedPosition.y + focusedPosition.height - bottomLimit
    666 		if focusedPosition.y-offset < topLimit {
    667 			offset = focusedPosition.y - topLimit
    668 		}
    669 	}
    670 
    671 	// Draw items.
    672 	for index, item := range f.items {
    673 		// Set position.
    674 		y := positions[index].y - offset
    675 		height := positions[index].height
    676 		item.SetRect(positions[index].x, y, positions[index].width, height)
    677 
    678 		// Is this item visible?
    679 		if y+height <= topLimit || y >= bottomLimit {
    680 			continue
    681 		}
    682 
    683 		// Draw items with focus last (in case of overlaps).
    684 		if item.HasFocus() {
    685 			defer item.Draw(screen)
    686 		} else {
    687 			item.Draw(screen)
    688 		}
    689 	}
    690 
    691 	// Draw buttons.
    692 	for index, button := range f.buttons {
    693 		// Set position.
    694 		buttonIndex := index + len(f.items)
    695 		y := positions[buttonIndex].y - offset
    696 		height := positions[buttonIndex].height
    697 		button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
    698 
    699 		// Is this button visible?
    700 		if y+height <= topLimit || y >= bottomLimit {
    701 			continue
    702 		}
    703 
    704 		// Draw button.
    705 		button.Draw(screen)
    706 	}
    707 }
    708 
    709 // Focus is called by the application when the primitive receives focus.
    710 func (f *Form) Focus(delegate func(p Primitive)) {
    711 	// Hand on the focus to one of our child elements.
    712 	if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
    713 		f.focusedElement = 0
    714 	}
    715 	var handler func(key tcell.Key)
    716 	handler = func(key tcell.Key) {
    717 		if key >= 0 {
    718 			f.lastFinishedKey = key
    719 		}
    720 		switch key {
    721 		case tcell.KeyTab, tcell.KeyEnter:
    722 			f.focusedElement++
    723 			f.Focus(delegate)
    724 		case tcell.KeyBacktab:
    725 			f.focusedElement--
    726 			if f.focusedElement < 0 {
    727 				f.focusedElement = len(f.items) + len(f.buttons) - 1
    728 			}
    729 			f.Focus(delegate)
    730 		case tcell.KeyEscape:
    731 			if f.cancel != nil {
    732 				f.cancel()
    733 			} else {
    734 				f.focusedElement = 0
    735 				f.Focus(delegate)
    736 			}
    737 		default:
    738 			if key < 0 && f.lastFinishedKey >= 0 {
    739 				// Repeat the last action.
    740 				handler(f.lastFinishedKey)
    741 			}
    742 		}
    743 	}
    744 
    745 	// Track whether a form item has focus.
    746 	var itemFocused bool
    747 	f.hasFocus = false
    748 
    749 	// Set the handler and focus for all items and buttons.
    750 	for index, button := range f.buttons {
    751 		button.SetExitFunc(handler)
    752 		if f.focusedElement == index+len(f.items) {
    753 			if button.IsDisabled() {
    754 				f.focusedElement++
    755 				if f.focusedElement >= len(f.items)+len(f.buttons) {
    756 					f.focusedElement = 0
    757 				}
    758 				continue
    759 			}
    760 
    761 			itemFocused = true
    762 			func(b *Button) { // Wrapping might not be necessary anymore in future Go versions.
    763 				defer delegate(b)
    764 			}(button)
    765 		}
    766 	}
    767 	for index, item := range f.items {
    768 		item.SetFinishedFunc(handler)
    769 		if f.focusedElement == index {
    770 			itemFocused = true
    771 			func(i FormItem) { // Wrapping might not be necessary anymore in future Go versions.
    772 				defer delegate(i)
    773 			}(item)
    774 		}
    775 	}
    776 
    777 	// If no item was focused, focus the form itself.
    778 	if !itemFocused {
    779 		f.Box.Focus(delegate)
    780 	}
    781 }
    782 
    783 // HasFocus returns whether or not this primitive has focus.
    784 func (f *Form) HasFocus() bool {
    785 	if f.focusIndex() >= 0 {
    786 		return true
    787 	}
    788 	return f.Box.HasFocus()
    789 }
    790 
    791 // focusIndex returns the index of the currently focused item, counting form
    792 // items first, then buttons. A negative value indicates that no containeed item
    793 // has focus.
    794 func (f *Form) focusIndex() int {
    795 	for index, item := range f.items {
    796 		if item.HasFocus() {
    797 			return index
    798 		}
    799 	}
    800 	for index, button := range f.buttons {
    801 		if button.HasFocus() {
    802 			return len(f.items) + index
    803 		}
    804 	}
    805 	return -1
    806 }
    807 
    808 // MouseHandler returns the mouse handler for this primitive.
    809 func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
    810 	return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
    811 		// At the end, update f.focusedElement and prepare current item/button.
    812 		defer func() {
    813 			if consumed {
    814 				index := f.focusIndex()
    815 				if index >= 0 {
    816 					f.focusedElement = index
    817 				}
    818 			}
    819 		}()
    820 
    821 		// Determine items to pass mouse events to.
    822 		for _, item := range f.items {
    823 			// Exclude TextView items from mouse-down events as they are
    824 			// read-only items and thus should not be focused.
    825 			if _, ok := item.(*TextView); ok && action == MouseLeftDown {
    826 				continue
    827 			}
    828 
    829 			consumed, capture = item.MouseHandler()(action, event, setFocus)
    830 			if consumed {
    831 				return
    832 			}
    833 		}
    834 		for _, button := range f.buttons {
    835 			consumed, capture = button.MouseHandler()(action, event, setFocus)
    836 			if consumed {
    837 				return
    838 			}
    839 		}
    840 
    841 		// A mouse down anywhere else will return the focus to the last selected
    842 		// element.
    843 		if action == MouseLeftDown && f.InRect(event.Position()) {
    844 			f.Focus(setFocus)
    845 			consumed = true
    846 		}
    847 
    848 		return
    849 	})
    850 }
    851 
    852 // InputHandler returns the handler for this primitive.
    853 func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
    854 	return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
    855 		for _, item := range f.items {
    856 			if item != nil && item.HasFocus() {
    857 				if handler := item.InputHandler(); handler != nil {
    858 					handler(event, setFocus)
    859 					return
    860 				}
    861 			}
    862 		}
    863 
    864 		for _, button := range f.buttons {
    865 			if button.HasFocus() {
    866 				if handler := button.InputHandler(); handler != nil {
    867 					handler(event, setFocus)
    868 					return
    869 				}
    870 			}
    871 		}
    872 	})
    873 }
    874 
    875 // PasteHandler returns the handler for this primitive.
    876 func (f *Form) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {
    877 	return f.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {
    878 		for _, item := range f.items {
    879 			if item != nil && item.HasFocus() {
    880 				if handler := item.PasteHandler(); handler != nil {
    881 					handler(pastedText, setFocus)
    882 					return
    883 				}
    884 			}
    885 		}
    886 
    887 		for _, button := range f.buttons {
    888 			if button.HasFocus() {
    889 				if handler := button.PasteHandler(); handler != nil {
    890 					handler(pastedText, setFocus)
    891 					return
    892 				}
    893 			}
    894 		}
    895 	})
    896 }