...

Source file src/codeberg.org/tslocum/cview/form.go

Documentation: codeberg.org/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"reflect"
     5  	"sync"
     6  
     7  	"github.com/gdamore/tcell/v3"
     8  )
     9  
    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  var DefaultFormFieldWidth = 10
    14  
    15  // FormItemAttributes is a set of attributes to be applied.
    16  type FormItemAttributes struct {
    17  	// The screen width of the label. A value of 0 will cause the primitive to
    18  	// use the width of the label string.
    19  	LabelWidth int
    20  
    21  	BackgroundColor             tcell.Color
    22  	LabelColor                  tcell.Color
    23  	LabelColorFocused           tcell.Color
    24  	FieldBackgroundColor        tcell.Color
    25  	FieldBackgroundColorFocused tcell.Color
    26  	FieldTextColor              tcell.Color
    27  	FieldTextColorFocused       tcell.Color
    28  
    29  	FinishedFunc func(key tcell.Key)
    30  }
    31  
    32  // FormItem is the interface all form items must implement to be able to be
    33  // included in a form.
    34  type FormItem interface {
    35  	Primitive
    36  
    37  	// GetLabel returns the item's label text.
    38  	GetLabel() string
    39  
    40  	// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
    41  	// primitive to use the width of the label string.
    42  	SetLabelWidth(int)
    43  
    44  	// SetLabelColor sets the color of the label.
    45  	SetLabelColor(tcell.Color)
    46  
    47  	// SetLabelColor sets the color of the label when focused.
    48  	SetLabelColorFocused(tcell.Color)
    49  
    50  	// GetFieldWidth returns the width of the form item's field (the area which
    51  	// is manipulated by the user) in number of screen cells. A value of 0
    52  	// indicates the the field width is flexible and may use as much space as
    53  	// required.
    54  	GetFieldWidth() int
    55  
    56  	// GetFieldHeight returns the height of the form item.
    57  	GetFieldHeight() int
    58  
    59  	// SetFieldTextColor sets the text color of the input area.
    60  	SetFieldTextColor(tcell.Color)
    61  
    62  	// SetFieldTextColorFocused sets the text color of the input area when focused.
    63  	SetFieldTextColorFocused(tcell.Color)
    64  
    65  	// SetFieldBackgroundColor sets the background color of the input area.
    66  	SetFieldBackgroundColor(tcell.Color)
    67  
    68  	// SetFieldBackgroundColor sets the background color of the input area when focused.
    69  	SetFieldBackgroundColorFocused(tcell.Color)
    70  
    71  	// SetBackgroundColor sets the background color of the form item.
    72  	SetBackgroundColor(tcell.Color)
    73  
    74  	// SetFinishedFunc sets a callback invoked when the user leaves the form item.
    75  	SetFinishedFunc(func(key tcell.Key))
    76  }
    77  
    78  // Form allows you to combine multiple one-line form elements into a vertical
    79  // or horizontal layout. Form elements include types such as InputField or
    80  // CheckBox. These elements can be optionally followed by one or more buttons
    81  // for which you can define form-wide actions (e.g. Save, Clear, Cancel).
    82  type Form struct {
    83  	*Box
    84  
    85  	// The items of the form (one row per item).
    86  	items []FormItem
    87  
    88  	// The buttons of the form.
    89  	buttons []*Button
    90  
    91  	// If set to true, instead of position items and buttons from top to bottom,
    92  	// they are positioned from left to right.
    93  	horizontal bool
    94  
    95  	// The alignment of the buttons.
    96  	buttonsAlign int
    97  
    98  	// The number of empty rows between items.
    99  	itemPadding int
   100  
   101  	// The index of the item or button which has focus. (Items are counted first,
   102  	// buttons are counted last.) This is only used when the form itself receives
   103  	// focus so that the last element that had focus keeps it.
   104  	focusedElement int
   105  
   106  	// Whether or not navigating the form will wrap around.
   107  	wrapAround bool
   108  
   109  	// The label color.
   110  	labelColor tcell.Color
   111  
   112  	// The label color when focused.
   113  	labelColorFocused tcell.Color
   114  
   115  	// The background color of the input area.
   116  	fieldBackgroundColor tcell.Color
   117  
   118  	// The background color of the input area when focused.
   119  	fieldBackgroundColorFocused tcell.Color
   120  
   121  	// The text color of the input area.
   122  	fieldTextColor tcell.Color
   123  
   124  	// The text color of the input area when focused.
   125  	fieldTextColorFocused tcell.Color
   126  
   127  	// The background color of the buttons.
   128  	buttonBackgroundColor tcell.Color
   129  
   130  	// The background color of the buttons when focused.
   131  	buttonBackgroundColorFocused tcell.Color
   132  
   133  	// The color of the button text.
   134  	buttonTextColor tcell.Color
   135  
   136  	// The color of the button text when focused.
   137  	buttonTextColorFocused tcell.Color
   138  
   139  	// An optional function which is called when the user hits Escape.
   140  	cancel func()
   141  
   142  	sync.RWMutex
   143  }
   144  
   145  // NewForm returns a new form.
   146  func NewForm() *Form {
   147  	box := NewBox()
   148  	box.SetPadding(1, 1, 1, 1)
   149  
   150  	f := &Form{
   151  		Box:                          box,
   152  		itemPadding:                  1,
   153  		labelColor:                   Styles.SecondaryTextColor,
   154  		fieldBackgroundColor:         Styles.MoreContrastBackgroundColor,
   155  		fieldBackgroundColorFocused:  Styles.ContrastBackgroundColor,
   156  		fieldTextColor:               Styles.PrimaryTextColor,
   157  		fieldTextColorFocused:        Styles.PrimaryTextColor,
   158  		buttonBackgroundColor:        Styles.MoreContrastBackgroundColor,
   159  		buttonBackgroundColorFocused: Styles.ContrastBackgroundColor,
   160  		buttonTextColor:              Styles.PrimaryTextColor,
   161  		buttonTextColorFocused:       Styles.PrimaryTextColor,
   162  		labelColorFocused:            ColorUnset,
   163  	}
   164  
   165  	f.focus = f
   166  	return f
   167  }
   168  
   169  // SetItemPadding sets the number of empty rows between form items for vertical
   170  // layouts and the number of empty cells between form items for horizontal
   171  // layouts.
   172  func (f *Form) SetItemPadding(padding int) {
   173  	f.Lock()
   174  	defer f.Unlock()
   175  
   176  	f.itemPadding = padding
   177  }
   178  
   179  // SetHorizontal sets the direction the form elements are laid out. If set to
   180  // true, instead of positioning them from top to bottom (the default), they are
   181  // positioned from left to right, moving into the next row if there is not
   182  // enough space.
   183  func (f *Form) SetHorizontal(horizontal bool) {
   184  	f.Lock()
   185  	defer f.Unlock()
   186  
   187  	f.horizontal = horizontal
   188  }
   189  
   190  // SetLabelColor sets the color of the labels.
   191  func (f *Form) SetLabelColor(color tcell.Color) {
   192  	f.Lock()
   193  	defer f.Unlock()
   194  
   195  	f.labelColor = color
   196  }
   197  
   198  // SetLabelColorFocused sets the color of the labels when focused.
   199  func (f *Form) SetLabelColorFocused(color tcell.Color) {
   200  	f.Lock()
   201  	defer f.Unlock()
   202  
   203  	f.labelColorFocused = color
   204  }
   205  
   206  // SetFieldBackgroundColor sets the background color of the input areas.
   207  func (f *Form) SetFieldBackgroundColor(color tcell.Color) {
   208  	f.Lock()
   209  	defer f.Unlock()
   210  
   211  	f.fieldBackgroundColor = color
   212  }
   213  
   214  // SetFieldBackgroundColorFocused sets the background color of the input areas when focused.
   215  func (f *Form) SetFieldBackgroundColorFocused(color tcell.Color) {
   216  	f.Lock()
   217  	defer f.Unlock()
   218  
   219  	f.fieldBackgroundColorFocused = color
   220  }
   221  
   222  // SetFieldTextColor sets the text color of the input areas.
   223  func (f *Form) SetFieldTextColor(color tcell.Color) {
   224  	f.Lock()
   225  	defer f.Unlock()
   226  
   227  	f.fieldTextColor = color
   228  }
   229  
   230  // SetFieldTextColorFocused sets the text color of the input areas when focused.
   231  func (f *Form) SetFieldTextColorFocused(color tcell.Color) {
   232  	f.Lock()
   233  	defer f.Unlock()
   234  
   235  	f.fieldTextColorFocused = color
   236  }
   237  
   238  // SetButtonsAlign sets how the buttons align horizontally, one of AlignLeft
   239  // (the default), AlignCenter, and AlignRight. This is only
   240  func (f *Form) SetButtonsAlign(align int) {
   241  	f.Lock()
   242  	defer f.Unlock()
   243  
   244  	f.buttonsAlign = align
   245  }
   246  
   247  // SetButtonBackgroundColor sets the background color of the buttons.
   248  func (f *Form) SetButtonBackgroundColor(color tcell.Color) {
   249  	f.Lock()
   250  	defer f.Unlock()
   251  
   252  	f.buttonBackgroundColor = color
   253  }
   254  
   255  // SetButtonBackgroundColorFocused sets the background color of the buttons when focused.
   256  func (f *Form) SetButtonBackgroundColorFocused(color tcell.Color) {
   257  	f.Lock()
   258  	defer f.Unlock()
   259  
   260  	f.buttonBackgroundColorFocused = color
   261  }
   262  
   263  // SetButtonTextColor sets the color of the button texts.
   264  func (f *Form) SetButtonTextColor(color tcell.Color) {
   265  	f.Lock()
   266  	defer f.Unlock()
   267  
   268  	f.buttonTextColor = color
   269  }
   270  
   271  // SetButtonTextColorFocused sets the color of the button texts when focused.
   272  func (f *Form) SetButtonTextColorFocused(color tcell.Color) {
   273  	f.Lock()
   274  	defer f.Unlock()
   275  
   276  	f.buttonTextColorFocused = color
   277  }
   278  
   279  // SetFocus shifts the focus to the form element with the given index, counting
   280  // non-button items first and buttons last. Note that this index is only used
   281  // when the form itself receives focus.
   282  func (f *Form) SetFocus(index int) {
   283  	f.Lock()
   284  	defer f.Unlock()
   285  
   286  	if index < 0 {
   287  		f.focusedElement = 0
   288  	} else if index >= len(f.items)+len(f.buttons) {
   289  		f.focusedElement = len(f.items) + len(f.buttons)
   290  	} else {
   291  		f.focusedElement = index
   292  	}
   293  }
   294  
   295  // AddInputField adds an input field to the form. It has a label, an optional
   296  // initial value, a field width (a value of 0 extends it as far as possible),
   297  // an optional accept function to validate the item's value (set to nil to
   298  // accept any text), and an (optional) callback function which is invoked when
   299  // the input field's text has changed.
   300  func (f *Form) AddInputField(label, value string, fieldWidth int, accept func(textToCheck string, lastChar rune) bool, changed func(text string)) {
   301  	f.Lock()
   302  	defer f.Unlock()
   303  
   304  	inputField := NewInputField()
   305  	inputField.SetLabel(label)
   306  	inputField.SetText(value)
   307  	inputField.SetFieldWidth(fieldWidth)
   308  	inputField.SetAcceptanceFunc(accept)
   309  	inputField.SetChangedFunc(changed)
   310  
   311  	f.items = append(f.items, inputField)
   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)) {
   321  	f.Lock()
   322  	defer f.Unlock()
   323  
   324  	if mask == 0 {
   325  		mask = '*'
   326  	}
   327  
   328  	passwordField := NewInputField()
   329  	passwordField.SetLabel(label)
   330  	passwordField.SetText(value)
   331  	passwordField.SetFieldWidth(fieldWidth)
   332  	passwordField.SetMaskCharacter(mask)
   333  	passwordField.SetChangedFunc(changed)
   334  
   335  	f.items = append(f.items, passwordField)
   336  }
   337  
   338  // AddDropDownSimple adds a drop-down element to the form. It has a label, options,
   339  // and an (optional) callback function which is invoked when an option was
   340  // selected. The initial option may be a negative value to indicate that no
   341  // option is currently selected.
   342  func (f *Form) AddDropDownSimple(label string, initialOption int, selected func(index int, option *DropDownOption), options ...string) {
   343  	f.Lock()
   344  	defer f.Unlock()
   345  
   346  	dd := NewDropDown()
   347  	dd.SetLabel(label)
   348  	dd.SetOptionsSimple(selected, options...)
   349  	dd.SetCurrentOption(initialOption)
   350  
   351  	f.items = append(f.items, dd)
   352  }
   353  
   354  // AddDropDown adds a drop-down element to the form. It has a label, options,
   355  // and an (optional) callback function which is invoked when an option was
   356  // selected. The initial option may be a negative value to indicate that no
   357  // option is currently selected.
   358  func (f *Form) AddDropDown(label string, initialOption int, selected func(index int, option *DropDownOption), options []*DropDownOption) {
   359  	f.Lock()
   360  	defer f.Unlock()
   361  
   362  	dd := NewDropDown()
   363  	dd.SetLabel(label)
   364  	dd.SetOptions(selected, options...)
   365  	dd.SetCurrentOption(initialOption)
   366  
   367  	f.items = append(f.items, dd)
   368  }
   369  
   370  // AddCheckBox adds a checkbox to the form. It has a label, a message, an
   371  // initial state, and an (optional) callback function which is invoked when the
   372  // state of the checkbox was changed by the user.
   373  func (f *Form) AddCheckBox(label string, message string, checked bool, changed func(checked bool)) {
   374  	f.Lock()
   375  	defer f.Unlock()
   376  
   377  	c := NewCheckBox()
   378  	c.SetLabel(label)
   379  	c.SetMessage(message)
   380  	c.SetChecked(checked)
   381  	c.SetChangedFunc(changed)
   382  
   383  	f.items = append(f.items, c)
   384  }
   385  
   386  // AddSlider adds a slider to the form. It has a label, an initial value, a
   387  // maximum value, an amount to increment by when modified via keyboard, and an
   388  // (optional) callback function which is invoked when the state of the slider
   389  // was changed by the user.
   390  func (f *Form) AddSlider(label string, current, max, increment int, changed func(value int)) {
   391  	f.Lock()
   392  	defer f.Unlock()
   393  
   394  	s := NewSlider()
   395  	s.SetLabel(label)
   396  	s.SetMax(max)
   397  	s.SetProgress(current)
   398  	s.SetIncrement(increment)
   399  	s.SetChangedFunc(changed)
   400  
   401  	f.items = append(f.items, s)
   402  }
   403  
   404  // AddButton adds a new button to the form. The "selected" function is called
   405  // when the user selects this button. It may be nil.
   406  func (f *Form) AddButton(label string, selected func()) {
   407  	f.Lock()
   408  	defer f.Unlock()
   409  
   410  	button := NewButton(label)
   411  	button.SetSelectedFunc(selected)
   412  	f.buttons = append(f.buttons, button)
   413  }
   414  
   415  // GetButton returns the button at the specified 0-based index. Note that
   416  // buttons have been specially prepared for this form and modifying some of
   417  // their attributes may have unintended side effects.
   418  func (f *Form) GetButton(index int) *Button {
   419  	f.RLock()
   420  	defer f.RUnlock()
   421  
   422  	return f.buttons[index]
   423  }
   424  
   425  // RemoveButton removes the button at the specified position, starting with 0
   426  // for the button that was added first.
   427  func (f *Form) RemoveButton(index int) {
   428  	f.Lock()
   429  	defer f.Unlock()
   430  
   431  	f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
   432  }
   433  
   434  // GetButtonCount returns the number of buttons in this form.
   435  func (f *Form) GetButtonCount() int {
   436  	f.RLock()
   437  	defer f.RUnlock()
   438  
   439  	return len(f.buttons)
   440  }
   441  
   442  // GetButtonIndex returns the index of the button with the given label, starting
   443  // with 0 for the button that was added first. If no such label was found, -1
   444  // is returned.
   445  func (f *Form) GetButtonIndex(label string) int {
   446  	f.RLock()
   447  	defer f.RUnlock()
   448  
   449  	for index, button := range f.buttons {
   450  		if button.GetLabel() == label {
   451  			return index
   452  		}
   453  	}
   454  	return -1
   455  }
   456  
   457  // Clear removes all input elements from the form, including the buttons if
   458  // specified.
   459  func (f *Form) Clear(includeButtons bool) {
   460  	f.Lock()
   461  	defer f.Unlock()
   462  
   463  	f.items = nil
   464  	if includeButtons {
   465  		f.buttons = nil
   466  	}
   467  	f.focusedElement = 0
   468  }
   469  
   470  // ClearButtons removes all buttons from the form.
   471  func (f *Form) ClearButtons() {
   472  	f.Lock()
   473  	defer f.Unlock()
   474  
   475  	f.buttons = nil
   476  }
   477  
   478  // AddFormItem adds a new item to the form. This can be used to add your own
   479  // objects to the form. Note, however, that the Form class will override some
   480  // of its attributes to make it work in the form context. Specifically, these
   481  // are:
   482  //
   483  //   - The label width
   484  //   - The label color
   485  //   - The background color
   486  //   - The field text color
   487  //   - The field background color
   488  func (f *Form) AddFormItem(item FormItem) {
   489  	f.Lock()
   490  	defer f.Unlock()
   491  
   492  	if reflect.ValueOf(item).IsNil() {
   493  		panic("Invalid FormItem")
   494  	}
   495  
   496  	f.items = append(f.items, item)
   497  }
   498  
   499  // GetFormItemCount returns the number of items in the form (not including the
   500  // buttons).
   501  func (f *Form) GetFormItemCount() int {
   502  	f.RLock()
   503  	defer f.RUnlock()
   504  
   505  	return len(f.items)
   506  }
   507  
   508  // IndexOfFormItem returns the index of the given FormItem.
   509  func (f *Form) IndexOfFormItem(item FormItem) int {
   510  	f.l.RLock()
   511  	defer f.l.RUnlock()
   512  	for index, formItem := range f.items {
   513  		if item == formItem {
   514  			return index
   515  		}
   516  	}
   517  	return -1
   518  }
   519  
   520  // GetFormItem returns the form item at the given position, starting with index
   521  // 0. Elements are referenced in the order they were added. Buttons are not included.
   522  // If index is out of bounds it returns nil.
   523  func (f *Form) GetFormItem(index int) FormItem {
   524  	f.RLock()
   525  	defer f.RUnlock()
   526  	if index > len(f.items)-1 || index < 0 {
   527  		return nil
   528  	}
   529  	return f.items[index]
   530  }
   531  
   532  // RemoveFormItem removes the form element at the given position, starting with
   533  // index 0. Elements are referenced in the order they were added. Buttons are
   534  // not included.
   535  func (f *Form) RemoveFormItem(index int) {
   536  	f.Lock()
   537  	defer f.Unlock()
   538  
   539  	f.items = append(f.items[:index], f.items[index+1:]...)
   540  }
   541  
   542  // GetFormItemByLabel returns the first form element with the given label. If
   543  // no such element is found, nil is returned. Buttons are not searched and will
   544  // therefore not be returned.
   545  func (f *Form) GetFormItemByLabel(label string) FormItem {
   546  	f.RLock()
   547  	defer f.RUnlock()
   548  
   549  	for _, item := range f.items {
   550  		if item.GetLabel() == label {
   551  			return item
   552  		}
   553  	}
   554  	return nil
   555  }
   556  
   557  // GetFormItemIndex returns the index of the first form element with the given
   558  // label. If no such element is found, -1 is returned. Buttons are not searched
   559  // and will therefore not be returned.
   560  func (f *Form) GetFormItemIndex(label string) int {
   561  	f.RLock()
   562  	defer f.RUnlock()
   563  
   564  	for index, item := range f.items {
   565  		if item.GetLabel() == label {
   566  			return index
   567  		}
   568  	}
   569  	return -1
   570  }
   571  
   572  // GetFocusedItemIndex returns the indices of the form element or button which
   573  // currently has focus. If they don't, -1 is returned resepectively.
   574  func (f *Form) GetFocusedItemIndex() (formItem, button int) {
   575  	f.RLock()
   576  	defer f.RUnlock()
   577  
   578  	index := f.focusIndex()
   579  	if index < 0 {
   580  		return -1, -1
   581  	}
   582  	if index < len(f.items) {
   583  		return index, -1
   584  	}
   585  	return -1, index - len(f.items)
   586  }
   587  
   588  // SetWrapAround sets the flag that determines whether navigating the form will
   589  // wrap around. That is, navigating downwards on the last item will move the
   590  // selection to the first item (similarly in the other direction). If set to
   591  // false, the selection won't change when navigating downwards on the last item
   592  // or navigating upwards on the first item.
   593  func (f *Form) SetWrapAround(wrapAround bool) {
   594  	f.Lock()
   595  	defer f.Unlock()
   596  
   597  	f.wrapAround = wrapAround
   598  }
   599  
   600  // SetCancelFunc sets a handler which is called when the user hits the Escape
   601  // key.
   602  func (f *Form) SetCancelFunc(callback func()) {
   603  	f.Lock()
   604  	defer f.Unlock()
   605  
   606  	f.cancel = callback
   607  }
   608  
   609  // GetAttributes returns the current attribute settings of a form.
   610  func (f *Form) GetAttributes() *FormItemAttributes {
   611  	f.Lock()
   612  	defer f.Unlock()
   613  
   614  	return f.getAttributes()
   615  }
   616  
   617  func (f *Form) getAttributes() *FormItemAttributes {
   618  	attrs := &FormItemAttributes{
   619  		BackgroundColor:      f.backgroundColor,
   620  		LabelColor:           f.labelColor,
   621  		FieldBackgroundColor: f.fieldBackgroundColor,
   622  		FieldTextColor:       f.fieldTextColor,
   623  	}
   624  	if f.labelColorFocused == ColorUnset {
   625  		attrs.LabelColorFocused = f.labelColor
   626  	} else {
   627  		attrs.LabelColorFocused = f.labelColorFocused
   628  	}
   629  	if f.fieldBackgroundColorFocused == ColorUnset {
   630  		attrs.FieldBackgroundColorFocused = f.fieldTextColor
   631  	} else {
   632  		attrs.FieldBackgroundColorFocused = f.fieldBackgroundColorFocused
   633  	}
   634  	if f.fieldTextColorFocused == ColorUnset {
   635  		attrs.FieldTextColorFocused = f.fieldBackgroundColor
   636  	} else {
   637  		attrs.FieldTextColorFocused = f.fieldTextColorFocused
   638  	}
   639  	return attrs
   640  }
   641  
   642  // Draw draws this primitive onto the screen.
   643  func (f *Form) Draw(screen tcell.Screen) {
   644  	if !f.GetVisible() {
   645  		return
   646  	}
   647  
   648  	f.Box.Draw(screen)
   649  
   650  	f.Lock()
   651  	defer f.Unlock()
   652  
   653  	// Determine the actual item that has focus.
   654  	if index := f.focusIndex(); index >= 0 {
   655  		f.focusedElement = index
   656  	}
   657  
   658  	// Determine the dimensions.
   659  	x, y, width, height := f.GetInnerRect()
   660  	topLimit := y
   661  	bottomLimit := y + height
   662  	rightLimit := x + width
   663  	startX := x
   664  
   665  	// Find the longest label.
   666  	var maxLabelWidth int
   667  	for _, item := range f.items {
   668  		labelWidth := TaggedStringWidth(item.GetLabel())
   669  		if labelWidth > maxLabelWidth {
   670  			maxLabelWidth = labelWidth
   671  		}
   672  	}
   673  	maxLabelWidth++ // Add one space.
   674  
   675  	// Calculate positions of form items.
   676  	positions := make([]struct{ x, y, width, height int }, len(f.items)+len(f.buttons))
   677  	var focusedPosition struct{ x, y, width, height int }
   678  	for index, item := range f.items {
   679  		if !item.GetVisible() {
   680  			continue
   681  		}
   682  
   683  		// Calculate the space needed.
   684  		labelWidth := TaggedStringWidth(item.GetLabel())
   685  		var itemWidth int
   686  		if f.horizontal {
   687  			fieldWidth := item.GetFieldWidth()
   688  			if fieldWidth == 0 {
   689  				fieldWidth = DefaultFormFieldWidth
   690  			}
   691  			labelWidth++
   692  			itemWidth = labelWidth + fieldWidth
   693  		} else {
   694  			// We want all fields to align vertically.
   695  			labelWidth = maxLabelWidth
   696  			itemWidth = width
   697  		}
   698  
   699  		// Advance to next line if there is no space.
   700  		if f.horizontal && x+labelWidth+1 >= rightLimit {
   701  			x = startX
   702  			y += 2
   703  		}
   704  
   705  		// Adjust the item's attributes.
   706  		if x+itemWidth >= rightLimit {
   707  			itemWidth = rightLimit - x
   708  		}
   709  
   710  		attributes := f.getAttributes()
   711  		attributes.LabelWidth = labelWidth
   712  		setFormItemAttributes(item, attributes)
   713  
   714  		// Save position.
   715  		positions[index].x = x
   716  		positions[index].y = y
   717  		positions[index].width = itemWidth
   718  		positions[index].height = 1
   719  		if item.GetFocusable().HasFocus() {
   720  			focusedPosition = positions[index]
   721  		}
   722  
   723  		// Advance to next item.
   724  		if f.horizontal {
   725  			x += itemWidth + f.itemPadding
   726  		} else {
   727  			y += item.GetFieldHeight() + f.itemPadding
   728  		}
   729  	}
   730  
   731  	// How wide are the buttons?
   732  	buttonWidths := make([]int, len(f.buttons))
   733  	buttonsWidth := 0
   734  	for index, button := range f.buttons {
   735  		w := TaggedStringWidth(button.GetLabel()) + 4
   736  		buttonWidths[index] = w
   737  		buttonsWidth += w + 1
   738  	}
   739  	buttonsWidth--
   740  
   741  	// Where do we place them?
   742  	if !f.horizontal && x+buttonsWidth < rightLimit {
   743  		switch f.buttonsAlign {
   744  		case AlignRight:
   745  			x = rightLimit - buttonsWidth
   746  		case AlignCenter:
   747  			x = (x + rightLimit - buttonsWidth) / 2
   748  		}
   749  
   750  		// In vertical layouts, buttons always appear after an empty line.
   751  		if f.itemPadding == 0 {
   752  			y++
   753  		}
   754  	}
   755  
   756  	// Calculate positions of buttons.
   757  	for index, button := range f.buttons {
   758  		if !button.GetVisible() {
   759  			continue
   760  		}
   761  
   762  		space := rightLimit - x
   763  		buttonWidth := buttonWidths[index]
   764  		if f.horizontal {
   765  			if space < buttonWidth-4 {
   766  				x = startX
   767  				y += 2
   768  				space = width
   769  			}
   770  		} else {
   771  			if space < 1 {
   772  				break // No space for this button anymore.
   773  			}
   774  		}
   775  		if buttonWidth > space {
   776  			buttonWidth = space
   777  		}
   778  		button.SetLabelColor(f.buttonTextColor)
   779  		button.SetLabelColorFocused(f.buttonTextColorFocused)
   780  		button.SetBackgroundColorFocused(f.buttonBackgroundColorFocused)
   781  		button.SetBackgroundColor(f.buttonBackgroundColor)
   782  
   783  		buttonIndex := index + len(f.items)
   784  		positions[buttonIndex].x = x
   785  		positions[buttonIndex].y = y
   786  		positions[buttonIndex].width = buttonWidth
   787  		positions[buttonIndex].height = 1
   788  
   789  		if button.HasFocus() {
   790  			focusedPosition = positions[buttonIndex]
   791  		}
   792  
   793  		x += buttonWidth + 1
   794  	}
   795  
   796  	// Determine vertical offset based on the position of the focused item.
   797  	var offset int
   798  	if focusedPosition.y+focusedPosition.height > bottomLimit {
   799  		offset = focusedPosition.y + focusedPosition.height - bottomLimit
   800  		if focusedPosition.y-offset < topLimit {
   801  			offset = focusedPosition.y - topLimit
   802  		}
   803  	}
   804  
   805  	// Draw items.
   806  	for index, item := range f.items {
   807  		if !item.GetVisible() {
   808  			continue
   809  		}
   810  
   811  		// Set position.
   812  		y := positions[index].y - offset
   813  		height := positions[index].height
   814  		item.SetRect(positions[index].x, y, positions[index].width, height)
   815  
   816  		// Is this item visible?
   817  		if y+height <= topLimit || y >= bottomLimit {
   818  			continue
   819  		}
   820  
   821  		// Draw items with focus last (in case of overlaps).
   822  		if item.GetFocusable().HasFocus() {
   823  			defer item.Draw(screen)
   824  		} else {
   825  			item.Draw(screen)
   826  		}
   827  	}
   828  
   829  	// Draw buttons.
   830  	for index, button := range f.buttons {
   831  		if !button.GetVisible() {
   832  			continue
   833  		}
   834  
   835  		// Set position.
   836  		buttonIndex := index + len(f.items)
   837  		y := positions[buttonIndex].y - offset
   838  		height := positions[buttonIndex].height
   839  		button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
   840  
   841  		// Is this button visible?
   842  		if y+height <= topLimit || y >= bottomLimit {
   843  			continue
   844  		}
   845  
   846  		// Draw button.
   847  		button.Draw(screen)
   848  	}
   849  }
   850  
   851  func (f *Form) updateFocusedElement(decreasing bool) {
   852  	li := len(f.items)
   853  	l := len(f.items) + len(f.buttons)
   854  	for i := 0; i < l; i++ {
   855  		if f.focusedElement < 0 {
   856  			if f.wrapAround {
   857  				f.focusedElement = l - 1
   858  			} else {
   859  				f.focusedElement = 0
   860  			}
   861  		} else if f.focusedElement >= l {
   862  			if f.wrapAround {
   863  				f.focusedElement = 0
   864  			} else {
   865  				f.focusedElement = l - 1
   866  			}
   867  		}
   868  
   869  		if f.focusedElement < li {
   870  			item := f.items[f.focusedElement]
   871  			if item.GetVisible() {
   872  				break
   873  			}
   874  		} else {
   875  			button := f.buttons[f.focusedElement-li]
   876  			if button.GetVisible() {
   877  				break
   878  			}
   879  		}
   880  
   881  		if decreasing {
   882  			f.focusedElement--
   883  		} else {
   884  			f.focusedElement++
   885  		}
   886  	}
   887  
   888  }
   889  
   890  func (f *Form) formItemInputHandler(delegate func(p Primitive)) func(key tcell.Key) {
   891  	return func(key tcell.Key) {
   892  		f.Lock()
   893  
   894  		switch key {
   895  		case tcell.KeyTab, tcell.KeyEnter:
   896  			f.focusedElement++
   897  			f.updateFocusedElement(false)
   898  			f.Unlock()
   899  			f.Focus(delegate)
   900  			f.Lock()
   901  		case tcell.KeyBacktab:
   902  			f.focusedElement--
   903  			f.updateFocusedElement(true)
   904  			f.Unlock()
   905  			f.Focus(delegate)
   906  			f.Lock()
   907  		case tcell.KeyEscape:
   908  			if f.cancel != nil {
   909  				f.Unlock()
   910  				f.cancel()
   911  				f.Lock()
   912  			} else {
   913  				f.focusedElement = 0
   914  				f.updateFocusedElement(true)
   915  				f.Unlock()
   916  				f.Focus(delegate)
   917  				f.Lock()
   918  			}
   919  		}
   920  
   921  		f.Unlock()
   922  	}
   923  }
   924  
   925  // Focus is called by the application when the primitive receives focus.
   926  func (f *Form) Focus(delegate func(p Primitive)) {
   927  	f.Lock()
   928  	if len(f.items)+len(f.buttons) == 0 {
   929  		f.hasFocus = true
   930  		f.Unlock()
   931  		return
   932  	}
   933  	f.hasFocus = false
   934  
   935  	// Hand on the focus to one of our child elements.
   936  	if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
   937  		f.focusedElement = 0
   938  	}
   939  
   940  	if f.focusedElement < len(f.items) {
   941  		// We're selecting an item.
   942  		item := f.items[f.focusedElement]
   943  
   944  		attributes := f.getAttributes()
   945  		attributes.FinishedFunc = f.formItemInputHandler(delegate)
   946  
   947  		f.Unlock()
   948  
   949  		setFormItemAttributes(item, attributes)
   950  		delegate(item)
   951  	} else {
   952  		// We're selecting a button.
   953  		button := f.buttons[f.focusedElement-len(f.items)]
   954  		button.SetBlurFunc(f.formItemInputHandler(delegate))
   955  
   956  		f.Unlock()
   957  
   958  		delegate(button)
   959  	}
   960  }
   961  
   962  // HasFocus returns whether or not this primitive has focus.
   963  func (f *Form) HasFocus() bool {
   964  	f.Lock()
   965  	defer f.Unlock()
   966  
   967  	if f.hasFocus {
   968  		return true
   969  	}
   970  	return f.focusIndex() >= 0
   971  }
   972  
   973  // focusIndex returns the index of the currently focused item, counting form
   974  // items first, then buttons. A negative value indicates that no containeed item
   975  // has focus.
   976  func (f *Form) focusIndex() int {
   977  	for index, item := range f.items {
   978  		if item.GetVisible() && item.GetFocusable().HasFocus() {
   979  			return index
   980  		}
   981  	}
   982  	for index, button := range f.buttons {
   983  		if button.GetVisible() && button.focus.HasFocus() {
   984  			return len(f.items) + index
   985  		}
   986  	}
   987  	return -1
   988  }
   989  
   990  // MouseHandler returns the mouse handler for this primitive.
   991  func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   992  	return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   993  		if !f.InRect(event.Position()) {
   994  			return false, nil
   995  		}
   996  
   997  		// Determine items to pass mouse events to.
   998  		for _, item := range f.items {
   999  			consumed, capture = item.MouseHandler()(action, event, setFocus)
  1000  			if consumed {
  1001  				return
  1002  			}
  1003  		}
  1004  		for _, button := range f.buttons {
  1005  			consumed, capture = button.MouseHandler()(action, event, setFocus)
  1006  			if consumed {
  1007  				return
  1008  			}
  1009  		}
  1010  
  1011  		// A mouse click anywhere else will return the focus to the last selected
  1012  		// element.
  1013  		if action == MouseLeftClick {
  1014  			if f.focusedElement < len(f.items) {
  1015  				setFocus(f.items[f.focusedElement])
  1016  			} else if f.focusedElement < len(f.items)+len(f.buttons) {
  1017  				setFocus(f.buttons[f.focusedElement-len(f.items)])
  1018  			}
  1019  			consumed = true
  1020  		}
  1021  
  1022  		return
  1023  	})
  1024  }
  1025  
  1026  func setFormItemAttributes(item FormItem, attrs *FormItemAttributes) {
  1027  	item.SetLabelWidth(attrs.LabelWidth)
  1028  	item.SetBackgroundColor(attrs.BackgroundColor)
  1029  	item.SetLabelColor(attrs.LabelColor)
  1030  	item.SetLabelColorFocused(attrs.LabelColorFocused)
  1031  	item.SetFieldTextColor(attrs.FieldTextColor)
  1032  	item.SetFieldTextColorFocused(attrs.FieldTextColorFocused)
  1033  	item.SetFieldBackgroundColor(attrs.FieldBackgroundColor)
  1034  	item.SetFieldBackgroundColorFocused(attrs.FieldBackgroundColorFocused)
  1035  
  1036  	if attrs.FinishedFunc != nil {
  1037  		item.SetFinishedFunc(attrs.FinishedFunc)
  1038  	}
  1039  }
  1040  

View as plain text