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 }