...

Source file src/code.rocketnine.space/tslocum/cview/form.go

Documentation: code.rocketnine.space/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"reflect"
     5  	"sync"
     6  
     7  	"github.com/gdamore/tcell/v2"
     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  		if f.buttonsAlign == AlignRight {
   744  			x = rightLimit - buttonsWidth
   745  		} else if f.buttonsAlign == AlignCenter {
   746  			x = (x + rightLimit - buttonsWidth) / 2
   747  		}
   748  
   749  		// In vertical layouts, buttons always appear after an empty line.
   750  		if f.itemPadding == 0 {
   751  			y++
   752  		}
   753  	}
   754  
   755  	// Calculate positions of buttons.
   756  	for index, button := range f.buttons {
   757  		if !button.GetVisible() {
   758  			continue
   759  		}
   760  
   761  		space := rightLimit - x
   762  		buttonWidth := buttonWidths[index]
   763  		if f.horizontal {
   764  			if space < buttonWidth-4 {
   765  				x = startX
   766  				y += 2
   767  				space = width
   768  			}
   769  		} else {
   770  			if space < 1 {
   771  				break // No space for this button anymore.
   772  			}
   773  		}
   774  		if buttonWidth > space {
   775  			buttonWidth = space
   776  		}
   777  		button.SetLabelColor(f.buttonTextColor)
   778  		button.SetLabelColorFocused(f.buttonTextColorFocused)
   779  		button.SetBackgroundColorFocused(f.buttonBackgroundColorFocused)
   780  		button.SetBackgroundColor(f.buttonBackgroundColor)
   781  
   782  		buttonIndex := index + len(f.items)
   783  		positions[buttonIndex].x = x
   784  		positions[buttonIndex].y = y
   785  		positions[buttonIndex].width = buttonWidth
   786  		positions[buttonIndex].height = 1
   787  
   788  		if button.HasFocus() {
   789  			focusedPosition = positions[buttonIndex]
   790  		}
   791  
   792  		x += buttonWidth + 1
   793  	}
   794  
   795  	// Determine vertical offset based on the position of the focused item.
   796  	var offset int
   797  	if focusedPosition.y+focusedPosition.height > bottomLimit {
   798  		offset = focusedPosition.y + focusedPosition.height - bottomLimit
   799  		if focusedPosition.y-offset < topLimit {
   800  			offset = focusedPosition.y - topLimit
   801  		}
   802  	}
   803  
   804  	// Draw items.
   805  	for index, item := range f.items {
   806  		if !item.GetVisible() {
   807  			continue
   808  		}
   809  
   810  		// Set position.
   811  		y := positions[index].y - offset
   812  		height := positions[index].height
   813  		item.SetRect(positions[index].x, y, positions[index].width, height)
   814  
   815  		// Is this item visible?
   816  		if y+height <= topLimit || y >= bottomLimit {
   817  			continue
   818  		}
   819  
   820  		// Draw items with focus last (in case of overlaps).
   821  		if item.GetFocusable().HasFocus() {
   822  			defer item.Draw(screen)
   823  		} else {
   824  			item.Draw(screen)
   825  		}
   826  	}
   827  
   828  	// Draw buttons.
   829  	for index, button := range f.buttons {
   830  		if !button.GetVisible() {
   831  			continue
   832  		}
   833  
   834  		// Set position.
   835  		buttonIndex := index + len(f.items)
   836  		y := positions[buttonIndex].y - offset
   837  		height := positions[buttonIndex].height
   838  		button.SetRect(positions[buttonIndex].x, y, positions[buttonIndex].width, height)
   839  
   840  		// Is this button visible?
   841  		if y+height <= topLimit || y >= bottomLimit {
   842  			continue
   843  		}
   844  
   845  		// Draw button.
   846  		button.Draw(screen)
   847  	}
   848  }
   849  
   850  func (f *Form) updateFocusedElement(decreasing bool) {
   851  	li := len(f.items)
   852  	l := len(f.items) + len(f.buttons)
   853  	for i := 0; i < l; i++ {
   854  		if f.focusedElement < 0 {
   855  			if f.wrapAround {
   856  				f.focusedElement = l - 1
   857  			} else {
   858  				f.focusedElement = 0
   859  			}
   860  		} else if f.focusedElement >= l {
   861  			if f.wrapAround {
   862  				f.focusedElement = 0
   863  			} else {
   864  				f.focusedElement = l - 1
   865  			}
   866  		}
   867  
   868  		if f.focusedElement < li {
   869  			item := f.items[f.focusedElement]
   870  			if item.GetVisible() {
   871  				break
   872  			}
   873  		} else {
   874  			button := f.buttons[f.focusedElement-li]
   875  			if button.GetVisible() {
   876  				break
   877  			}
   878  		}
   879  
   880  		if decreasing {
   881  			f.focusedElement--
   882  		} else {
   883  			f.focusedElement++
   884  		}
   885  	}
   886  
   887  }
   888  
   889  func (f *Form) formItemInputHandler(delegate func(p Primitive)) func(key tcell.Key) {
   890  	return func(key tcell.Key) {
   891  		f.Lock()
   892  
   893  		switch key {
   894  		case tcell.KeyTab, tcell.KeyEnter:
   895  			f.focusedElement++
   896  			f.updateFocusedElement(false)
   897  			f.Unlock()
   898  			f.Focus(delegate)
   899  			f.Lock()
   900  		case tcell.KeyBacktab:
   901  			f.focusedElement--
   902  			f.updateFocusedElement(true)
   903  			f.Unlock()
   904  			f.Focus(delegate)
   905  			f.Lock()
   906  		case tcell.KeyEscape:
   907  			if f.cancel != nil {
   908  				f.Unlock()
   909  				f.cancel()
   910  				f.Lock()
   911  			} else {
   912  				f.focusedElement = 0
   913  				f.updateFocusedElement(true)
   914  				f.Unlock()
   915  				f.Focus(delegate)
   916  				f.Lock()
   917  			}
   918  		}
   919  
   920  		f.Unlock()
   921  	}
   922  }
   923  
   924  // Focus is called by the application when the primitive receives focus.
   925  func (f *Form) Focus(delegate func(p Primitive)) {
   926  	f.Lock()
   927  	if len(f.items)+len(f.buttons) == 0 {
   928  		f.hasFocus = true
   929  		f.Unlock()
   930  		return
   931  	}
   932  	f.hasFocus = false
   933  
   934  	// Hand on the focus to one of our child elements.
   935  	if f.focusedElement < 0 || f.focusedElement >= len(f.items)+len(f.buttons) {
   936  		f.focusedElement = 0
   937  	}
   938  
   939  	if f.focusedElement < len(f.items) {
   940  		// We're selecting an item.
   941  		item := f.items[f.focusedElement]
   942  
   943  		attributes := f.getAttributes()
   944  		attributes.FinishedFunc = f.formItemInputHandler(delegate)
   945  
   946  		f.Unlock()
   947  
   948  		setFormItemAttributes(item, attributes)
   949  		delegate(item)
   950  	} else {
   951  		// We're selecting a button.
   952  		button := f.buttons[f.focusedElement-len(f.items)]
   953  		button.SetBlurFunc(f.formItemInputHandler(delegate))
   954  
   955  		f.Unlock()
   956  
   957  		delegate(button)
   958  	}
   959  }
   960  
   961  // HasFocus returns whether or not this primitive has focus.
   962  func (f *Form) HasFocus() bool {
   963  	f.Lock()
   964  	defer f.Unlock()
   965  
   966  	if f.hasFocus {
   967  		return true
   968  	}
   969  	return f.focusIndex() >= 0
   970  }
   971  
   972  // focusIndex returns the index of the currently focused item, counting form
   973  // items first, then buttons. A negative value indicates that no containeed item
   974  // has focus.
   975  func (f *Form) focusIndex() int {
   976  	for index, item := range f.items {
   977  		if item.GetVisible() && item.GetFocusable().HasFocus() {
   978  			return index
   979  		}
   980  	}
   981  	for index, button := range f.buttons {
   982  		if button.GetVisible() && button.focus.HasFocus() {
   983  			return len(f.items) + index
   984  		}
   985  	}
   986  	return -1
   987  }
   988  
   989  // MouseHandler returns the mouse handler for this primitive.
   990  func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   991  	return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   992  		if !f.InRect(event.Position()) {
   993  			return false, nil
   994  		}
   995  
   996  		// Determine items to pass mouse events to.
   997  		for _, item := range f.items {
   998  			consumed, capture = item.MouseHandler()(action, event, setFocus)
   999  			if consumed {
  1000  				return
  1001  			}
  1002  		}
  1003  		for _, button := range f.buttons {
  1004  			consumed, capture = button.MouseHandler()(action, event, setFocus)
  1005  			if consumed {
  1006  				return
  1007  			}
  1008  		}
  1009  
  1010  		// A mouse click anywhere else will return the focus to the last selected
  1011  		// element.
  1012  		if action == MouseLeftClick {
  1013  			if f.focusedElement < len(f.items) {
  1014  				setFocus(f.items[f.focusedElement])
  1015  			} else if f.focusedElement < len(f.items)+len(f.buttons) {
  1016  				setFocus(f.buttons[f.focusedElement-len(f.items)])
  1017  			}
  1018  			consumed = true
  1019  		}
  1020  
  1021  		return
  1022  	})
  1023  }
  1024  
  1025  func setFormItemAttributes(item FormItem, attrs *FormItemAttributes) {
  1026  	item.SetLabelWidth(attrs.LabelWidth)
  1027  	item.SetBackgroundColor(attrs.BackgroundColor)
  1028  	item.SetLabelColor(attrs.LabelColor)
  1029  	item.SetLabelColorFocused(attrs.LabelColorFocused)
  1030  	item.SetFieldTextColor(attrs.FieldTextColor)
  1031  	item.SetFieldTextColorFocused(attrs.FieldTextColorFocused)
  1032  	item.SetFieldBackgroundColor(attrs.FieldBackgroundColor)
  1033  	item.SetFieldBackgroundColorFocused(attrs.FieldBackgroundColorFocused)
  1034  
  1035  	if attrs.FinishedFunc != nil {
  1036  		item.SetFinishedFunc(attrs.FinishedFunc)
  1037  	}
  1038  }
  1039  

View as plain text