form.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. package tview
  2. import (
  3. "github.com/gdamore/tcell"
  4. )
  5. // DefaultFormFieldWidth is the default field screen width of form elements
  6. // whose field width is flexible (0). This is used in the Form class for
  7. // horizontal layouts.
  8. var DefaultFormFieldWidth = 10
  9. // FormItem is the interface all form items must implement to be able to be
  10. // included in a form.
  11. type FormItem interface {
  12. Primitive
  13. // GetLabel returns the item's label text.
  14. GetLabel() string
  15. // SetFormAttributes sets a number of item attributes at once.
  16. SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem
  17. // GetFieldWidth returns the width of the form item's field (the area which
  18. // is manipulated by the user) in number of screen cells. A value of 0
  19. // indicates the the field width is flexible and may use as much space as
  20. // required.
  21. GetFieldWidth() int
  22. // SetFinishedFunc sets the handler function for when the user finished
  23. // entering data into the item. The handler may receive events for the
  24. // Enter key (we're done), the Escape key (cancel input), the Tab key (move to
  25. // next field), and the Backtab key (move to previous field).
  26. SetFinishedFunc(handler func(key tcell.Key)) FormItem
  27. }
  28. // Form allows you to combine multiple one-line form elements into a vertical
  29. // or horizontal layout. Form elements include types such as InputField or
  30. // Checkbox. These elements can be optionally followed by one or more buttons
  31. // for which you can define form-wide actions (e.g. Save, Clear, Cancel).
  32. //
  33. // See https://github.com/rivo/tview/wiki/Form for an example.
  34. type Form struct {
  35. *Box
  36. // The items of the form (one row per item).
  37. items []FormItem
  38. // The buttons of the form.
  39. buttons []*Button
  40. // If set to true, instead of position items and buttons from top to bottom,
  41. // they are positioned from left to right.
  42. horizontal bool
  43. // The alignment of the buttons.
  44. buttonsAlign int
  45. // The number of empty rows between items.
  46. itemPadding int
  47. // The index of the item or button which has focus. (Items are counted first,
  48. // buttons are counted last.) This is only used when the form itself receives
  49. // focus so that the last element that had focus keeps it.
  50. focusedElement int
  51. // The label color.
  52. labelColor tcell.Color
  53. // The background color of the input area.
  54. fieldBackgroundColor tcell.Color
  55. // The text color of the input area.
  56. fieldTextColor tcell.Color
  57. // The background color of the buttons.
  58. buttonBackgroundColor tcell.Color
  59. // The color of the button text.
  60. buttonTextColor tcell.Color
  61. // An optional function which is called when the user hits Escape.
  62. cancel func()
  63. }
  64. // NewForm returns a new form.
  65. func NewForm() *Form {
  66. box := NewBox().SetBorderPadding(1, 1, 1, 1)
  67. f := &Form{
  68. Box: box,
  69. itemPadding: 1,
  70. labelColor: Styles.SecondaryTextColor,
  71. fieldBackgroundColor: Styles.ContrastBackgroundColor,
  72. fieldTextColor: Styles.PrimaryTextColor,
  73. buttonBackgroundColor: Styles.ContrastBackgroundColor,
  74. buttonTextColor: Styles.PrimaryTextColor,
  75. }
  76. f.focus = f
  77. return f
  78. }
  79. // SetItemPadding sets the number of empty rows between form items for vertical
  80. // layouts and the number of empty cells between form items for horizontal
  81. // layouts.
  82. func (f *Form) SetItemPadding(padding int) *Form {
  83. f.itemPadding = padding
  84. return f
  85. }
  86. // SetHorizontal sets the direction the form elements are laid out. If set to
  87. // true, instead of positioning them from top to bottom (the default), they are
  88. // positioned from left to right, moving into the next row if there is not
  89. // enough space.
  90. func (f *Form) SetHorizontal(horizontal bool) *Form {
  91. f.horizontal = horizontal
  92. return f
  93. }
  94. // SetLabelColor sets the color of the labels.
  95. func (f *Form) SetLabelColor(color tcell.Color) *Form {
  96. f.labelColor = color
  97. return f
  98. }
  99. // SetFieldBackgroundColor sets the background color of the input areas.
  100. func (f *Form) SetFieldBackgroundColor(color tcell.Color) *Form {
  101. f.fieldBackgroundColor = color
  102. return f
  103. }
  104. // SetFieldTextColor sets the text color of the input areas.
  105. func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
  106. f.fieldTextColor = color
  107. return f
  108. }
  109. // SetButtonsAlign sets how the buttons align horizontally, one of AlignLeft
  110. // (the default), AlignCenter, and AlignRight. This is only
  111. func (f *Form) SetButtonsAlign(align int) *Form {
  112. f.buttonsAlign = align
  113. return f
  114. }
  115. // SetButtonBackgroundColor sets the background color of the buttons.
  116. func (f *Form) SetButtonBackgroundColor(color tcell.Color) *Form {
  117. f.buttonBackgroundColor = color
  118. return f
  119. }
  120. // SetButtonTextColor sets the color of the button texts.
  121. func (f *Form) SetButtonTextColor(color tcell.Color) *Form {
  122. f.buttonTextColor = color
  123. return f
  124. }
  125. // SetFocus shifts the focus to the form element with the given index, counting
  126. // non-button items first and buttons last. Note that this index is only used
  127. // when the form itself receives focus.
  128. func (f *Form) SetFocus(index int) *Form {
  129. if index < 0 {
  130. f.focusedElement = 0
  131. } else if index >= len(f.items)+len(f.buttons) {
  132. f.focusedElement = len(f.items) + len(f.buttons)
  133. } else {
  134. f.focusedElement = index
  135. }
  136. return f
  137. }
  138. // AddInputField adds an input field to the form. It has a label, an optional
  139. // initial value, a field width (a value of 0 extends it as far as possible),
  140. // an optional accept function to validate the item's value (set to nil to
  141. // accept any text), and an (optional) callback function which is invoked when
  142. // the input field's text has changed.
  143. func (f *Form) AddInputField(label, value string, fieldWidth int, accept func(textToCheck string, lastChar rune) bool, changed func(text string)) *Form {
  144. f.items = append(f.items, NewInputField().
  145. SetLabel(label).
  146. SetText(value).
  147. SetFieldWidth(fieldWidth).
  148. SetAcceptanceFunc(accept).
  149. SetChangedFunc(changed))
  150. return f
  151. }
  152. // AddPasswordField adds a password field to the form. This is similar to an
  153. // input field except that the user's input not shown. Instead, a "mask"
  154. // character is displayed. The password field has a label, an optional initial
  155. // value, a field width (a value of 0 extends it as far as possible), and an
  156. // (optional) callback function which is invoked when the input field's text has
  157. // changed.
  158. func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune, changed func(text string)) *Form {
  159. if mask == 0 {
  160. mask = '*'
  161. }
  162. f.items = append(f.items, NewInputField().
  163. SetLabel(label).
  164. SetText(value).
  165. SetFieldWidth(fieldWidth).
  166. SetMaskCharacter(mask).
  167. SetChangedFunc(changed))
  168. return f
  169. }
  170. // AddDropDown adds a drop-down element to the form. It has a label, options,
  171. // and an (optional) callback function which is invoked when an option was
  172. // selected. The initial option may be a negative value to indicate that no
  173. // option is currently selected.
  174. func (f *Form) AddDropDown(label string, options []string, initialOption int, selected func(option string, optionIndex int)) *Form {
  175. f.items = append(f.items, NewDropDown().
  176. SetLabel(label).
  177. SetOptions(options, selected).
  178. SetCurrentOption(initialOption))
  179. return f
  180. }
  181. // AddCheckbox adds a checkbox to the form. It has a label, an initial state,
  182. // and an (optional) callback function which is invoked when the state of the
  183. // checkbox was changed by the user.
  184. func (f *Form) AddCheckbox(label string, checked bool, changed func(checked bool)) *Form {
  185. f.items = append(f.items, NewCheckbox().
  186. SetLabel(label).
  187. SetChecked(checked).
  188. SetChangedFunc(changed))
  189. return f
  190. }
  191. // AddButton adds a new button to the form. The "selected" function is called
  192. // when the user selects this button. It may be nil.
  193. func (f *Form) AddButton(label string, selected func()) *Form {
  194. f.buttons = append(f.buttons, NewButton(label).SetSelectedFunc(selected))
  195. return f
  196. }
  197. // GetButton returns the button at the specified 0-based index. Note that
  198. // buttons have been specially prepared for this form and modifying some of
  199. // their attributes may have unintended side effects.
  200. func (f *Form) GetButton(index int) *Button {
  201. return f.buttons[index]
  202. }
  203. // RemoveButton removes the button at the specified position, starting with 0
  204. // for the button that was added first.
  205. func (f *Form) RemoveButton(index int) *Form {
  206. f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
  207. return f
  208. }
  209. // GetButtonCount returns the number of buttons in this form.
  210. func (f *Form) GetButtonCount() int {
  211. return len(f.buttons)
  212. }
  213. // GetButtonIndex returns the index of the button with the given label, starting
  214. // with 0 for the button that was added first. If no such label was found, -1
  215. // is returned.
  216. func (f *Form) GetButtonIndex(label string) int {
  217. for index, button := range f.buttons {
  218. if button.GetLabel() == label {
  219. return index
  220. }
  221. }
  222. return -1
  223. }
  224. // Clear removes all input elements from the form, including the buttons if
  225. // specified.
  226. func (f *Form) Clear(includeButtons bool) *Form {
  227. f.items = nil
  228. if includeButtons {
  229. f.ClearButtons()
  230. }
  231. f.focusedElement = 0
  232. return f
  233. }
  234. // ClearButtons removes all buttons from the form.
  235. func (f *Form) ClearButtons() *Form {
  236. f.buttons = nil
  237. return f
  238. }
  239. // AddFormItem adds a new item to the form. This can be used to add your own
  240. // objects to the form. Note, however, that the Form class will override some
  241. // of its attributes to make it work in the form context. Specifically, these
  242. // are:
  243. //
  244. // - The label width
  245. // - The label color
  246. // - The background color
  247. // - The field text color
  248. // - The field background color
  249. func (f *Form) AddFormItem(item FormItem) *Form {
  250. f.items = append(f.items, item)
  251. return f
  252. }
  253. // GetFormItemCount returns the number of items in the form (not including the
  254. // buttons).
  255. func (f *Form) GetFormItemCount() int {
  256. return len(f.items)
  257. }
  258. // GetFormItem returns the form item at the given position, starting with index
  259. // 0. Elements are referenced in the order they were added. Buttons are not
  260. // included.
  261. func (f *Form) GetFormItem(index int) FormItem {
  262. return f.items[index]
  263. }
  264. // RemoveFormItem removes the form element at the given position, starting with
  265. // index 0. Elements are referenced in the order they were added. Buttons are
  266. // not included.
  267. func (f *Form) RemoveFormItem(index int) *Form {
  268. f.items = append(f.items[:index], f.items[index+1:]...)
  269. return f
  270. }
  271. // GetFormItemByLabel returns the first form element with the given label. If
  272. // no such element is found, nil is returned. Buttons are not searched and will
  273. // therefore not be returned.
  274. func (f *Form) GetFormItemByLabel(label string) FormItem {
  275. for _, item := range f.items {
  276. if item.GetLabel() == label {
  277. return item
  278. }
  279. }
  280. return nil
  281. }
  282. // GetFormItemIndex returns the index of the first form element with the given
  283. // label. If no such element is found, -1 is returned. Buttons are not searched
  284. // and will therefore not be returned.
  285. func (f *Form) GetFormItemIndex(label string) int {
  286. for index, item := range f.items {
  287. if item.GetLabel() == label {
  288. return index
  289. }
  290. }
  291. return -1
  292. }
  293. // GetFocusedItemIndex returns the indices of the form element or button which
  294. // currently has focus. If they don't, -1 is returned resepectively.
  295. func (f *Form) GetFocusedItemIndex() (formItem, button int) {
  296. index := f.focusIndex()
  297. if index < 0 {
  298. return -1, -1
  299. }
  300. if index < len(f.items) {
  301. return index, -1
  302. }
  303. return -1, index - len(f.items)
  304. }
  305. // SetCancelFunc sets a handler which is called when the user hits the Escape
  306. // key.
  307. func (f *Form) SetCancelFunc(callback func()) *Form {
  308. f.cancel = callback
  309. return f
  310. }
  311. // Draw draws this primitive onto the screen.
  312. func (f *Form) Draw(screen tcell.Screen) {
  313. f.Box.Draw(screen)
  314. // Determine the actual item that has focus.
  315. if index := f.focusIndex(); index >= 0 {
  316. f.focusedElement = index
  317. }
  318. // Determine the dimensions.
  319. x, y, width, height := f.GetInnerRect()
  320. topLimit := y
  321. bottomLimit := y + height
  322. rightLimit := x + width
  323. startX := x
  324. // Find the longest label.
  325. var maxLabelWidth int
  326. for _, item := range f.items {
  327. labelWidth := TaggedStringWidth(item.GetLabel())
  328. if labelWidth > maxLabelWidth {
  329. maxLabelWidth = labelWidth
  330. }
  331. }
  332. maxLabelWidth++ // Add one space.
  333. // Calculate positions of form items.
  334. positions := make([]struct{ x, y, width, height int }, len(f.items)+len(f.buttons))
  335. var focusedPosition struct{ x, y, width, height int }
  336. for index, item := range f.items {
  337. // Calculate the space needed.
  338. labelWidth := TaggedStringWidth(item.GetLabel())
  339. var itemWidth int
  340. if f.horizontal {
  341. fieldWidth := item.GetFieldWidth()
  342. if fieldWidth == 0 {
  343. fieldWidth = DefaultFormFieldWidth
  344. }
  345. labelWidth++
  346. itemWidth = labelWidth + fieldWidth
  347. } else {
  348. // We want all fields to align vertically.
  349. labelWidth = maxLabelWidth
  350. itemWidth = width
  351. }
  352. // Advance to next line if there is no space.
  353. if f.horizontal && x+labelWidth+1 >= rightLimit {
  354. x = startX
  355. y += 2
  356. }
  357. // Adjust the item's attributes.
  358. if x+itemWidth >= rightLimit {
  359. itemWidth = rightLimit - x
  360. }
  361. item.SetFormAttributes(
  362. labelWidth,
  363. f.labelColor,
  364. f.backgroundColor,
  365. f.fieldTextColor,
  366. f.fieldBackgroundColor,
  367. )
  368. // Save position.
  369. positions[index].x = x
  370. positions[index].y = y
  371. positions[index].width = itemWidth
  372. positions[index].height = 1
  373. if item.GetFocusable().HasFocus() {
  374. focusedPosition = positions[index]
  375. }
  376. // Advance to next item.
  377. if f.horizontal {
  378. x += itemWidth + f.itemPadding
  379. } else {
  380. y += 1 + f.itemPadding
  381. }
  382. }
  383. // How wide are the buttons?
  384. buttonWidths := make([]int, len(f.buttons))
  385. buttonsWidth := 0
  386. for index, button := range f.buttons {
  387. w := TaggedStringWidth(button.GetLabel()) + 4
  388. buttonWidths[index] = w
  389. buttonsWidth += w + 1
  390. }
  391. buttonsWidth--
  392. // Where do we place them?
  393. if !f.horizontal && x+buttonsWidth < rightLimit {
  394. if f.buttonsAlign == AlignRight {
  395. x = rightLimit - buttonsWidth
  396. } else if f.buttonsAlign == AlignCenter {
  397. x = (x + rightLimit - buttonsWidth) / 2
  398. }
  399. // In vertical layouts, buttons always appear after an empty line.
  400. if f.itemPadding == 0 {
  401. y++
  402. }
  403. }
  404. // Calculate positions of buttons.
  405. for index, button := range f.buttons {
  406. space := rightLimit - x
  407. buttonWidth := buttonWidths[index]
  408. if f.horizontal {
  409. if space < buttonWidth-4 {
  410. x = startX
  411. y += 2
  412. space = width
  413. }
  414. } else {
  415. if space < 1 {
  416. break // No space for this button anymore.
  417. }
  418. }
  419. if buttonWidth > space {
  420. buttonWidth = space
  421. }
  422. button.SetLabelColor(f.buttonTextColor).
  423. SetLabelColorActivated(f.buttonBackgroundColor).
  424. SetBackgroundColorActivated(f.buttonTextColor).
  425. SetBackgroundColor(f.buttonBackgroundColor)
  426. buttonIndex := index + len(f.items)
  427. positions[buttonIndex].x = x
  428. positions[buttonIndex].y = y
  429. positions[buttonIndex].width = buttonWidth
  430. positions[buttonIndex].height = 1
  431. if button.HasFocus() {
  432. focusedPosition = positions[buttonIndex]
  433. }
  434. x += buttonWidth + 1
  435. }
  436. // Determine vertical offset based on the position of the focused item.
  437. var offset int
  438. if focusedPosition.y+focusedPosition.height > bottomLimit {
  439. offset = focusedPosition.y + focusedPosition.height - bottomLimit
  440. if focusedPosition.y-offset < topLimit {
  441. offset = focusedPosition.y - topLimit
  442. }
  443. }
  444. // Draw items.
  445. for index, item := range f.items {
  446. // Set position.
  447. y := positions[index].y - offset
  448. height := positions[index].height
  449. item.SetRect(positions[index].x, y, positions[index].width, height)
  450. // Is this item visible?
  451. if y+height <= topLimit || y >= bottomLimit {
  452. continue
  453. }
  454. // Draw items with focus last (in case of overlaps).
  455. if item.GetFocusable().HasFocus() {
  456. defer item.Draw(screen)
  457. } else {
  458. item.Draw(screen)
  459. }
  460. }
  461. // Draw buttons.
  462. for index, button := range f.buttons {
  463. // Set position.
  464. buttonIndex := index + len(f.items)
  465. y := positions[buttonIndex].y - offset
  466. height := positions[buttonIndex].height
  467. button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
  468. // Is this button visible?
  469. if y+height <= topLimit || y >= bottomLimit {
  470. continue
  471. }
  472. // Draw button.
  473. button.Draw(screen)
  474. }
  475. }
  476. // Focus is called by the application when the primitive receives focus.
  477. func (f *Form) Focus(delegate func(p Primitive)) {
  478. if len(f.items)+len(f.buttons) == 0 {
  479. f.hasFocus = true
  480. return
  481. }
  482. f.hasFocus = false
  483. // Hand on the focus to one of our child elements.
  484. if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
  485. f.focusedElement = 0
  486. }
  487. handler := func(key tcell.Key) {
  488. switch key {
  489. case tcell.KeyTab, tcell.KeyEnter:
  490. f.focusedElement++
  491. f.Focus(delegate)
  492. case tcell.KeyBacktab:
  493. f.focusedElement--
  494. if f.focusedElement < 0 {
  495. f.focusedElement = len(f.items) + len(f.buttons) - 1
  496. }
  497. f.Focus(delegate)
  498. case tcell.KeyEscape:
  499. if f.cancel != nil {
  500. f.cancel()
  501. } else {
  502. f.focusedElement = 0
  503. f.Focus(delegate)
  504. }
  505. }
  506. }
  507. if f.focusedElement < len(f.items) {
  508. // We're selecting an item.
  509. item := f.items[f.focusedElement]
  510. item.SetFinishedFunc(handler)
  511. delegate(item)
  512. } else {
  513. // We're selecting a button.
  514. button := f.buttons[f.focusedElement-len(f.items)]
  515. button.SetBlurFunc(handler)
  516. delegate(button)
  517. }
  518. }
  519. // HasFocus returns whether or not this primitive has focus.
  520. func (f *Form) HasFocus() bool {
  521. if f.hasFocus {
  522. return true
  523. }
  524. return f.focusIndex() >= 0
  525. }
  526. // focusIndex returns the index of the currently focused item, counting form
  527. // items first, then buttons. A negative value indicates that no containeed item
  528. // has focus.
  529. func (f *Form) focusIndex() int {
  530. for index, item := range f.items {
  531. if item.GetFocusable().HasFocus() {
  532. return index
  533. }
  534. }
  535. for index, button := range f.buttons {
  536. if button.focus.HasFocus() {
  537. return len(f.items) + index
  538. }
  539. }
  540. return -1
  541. }
  542. // MouseHandler returns the mouse handler for this primitive.
  543. func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  544. return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  545. if !f.InRect(event.Position()) {
  546. return false, nil
  547. }
  548. // At the end, update f.focusedElement and prepare current item/button.
  549. defer func() {
  550. if consumed {
  551. index := f.focusIndex()
  552. if index >= 0 {
  553. f.focusedElement = index
  554. }
  555. f.Focus(setFocus)
  556. }
  557. }()
  558. // Determine items to pass mouse events to.
  559. for _, item := range f.items {
  560. consumed, capture = item.MouseHandler()(action, event, setFocus)
  561. if consumed {
  562. return
  563. }
  564. }
  565. for _, button := range f.buttons {
  566. consumed, capture = button.MouseHandler()(action, event, setFocus)
  567. if consumed {
  568. return
  569. }
  570. }
  571. // A mouse click anywhere else will return the focus to the last selected
  572. // element.
  573. if action == MouseLeftClick {
  574. consumed = true
  575. }
  576. return
  577. })
  578. }