form.go 24 KB

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