@ -7,10 +7,55 @@ import (
"github.com/gdamore/tcell/v2"
)
// dropDownOption is one option that can be selected in a drop-down primitive.
type dropDownOption struct {
Text string // The text to be displayed in the drop-down.
Selected func ( ) // The (optional) callback for when this option was selected.
// DropDownOption is one option that can be selected in a drop-down primitive.
type DropDownOption struct {
text string // The text to be displayed in the drop-down.
selected func ( index int , option * DropDownOption ) // The (optional) callback for when this option was selected.
reference interface { } // An optional reference object.
}
func NewDropDownOption ( text string ) * DropDownOption {
return & DropDownOption { text : text }
}
// GetText returns the text of this dropdown option.
func ( d * DropDownOption ) GetText ( ) string {
return d . text
}
// SetText returns the text of this dropdown option.
func ( d * DropDownOption ) SetText ( text string ) * DropDownOption {
d . text = text
return d
}
// SetSelectedFunc sets the handler to be called when this option is selected.
func ( d * DropDownOption ) SetSelectedFunc ( handler func ( index int , option * DropDownOption ) ) * DropDownOption {
d . selected = handler
return d
}
// GetReference returns the reference object of this dropdown option.
func ( d * DropDownOption ) GetReference ( ) interface { } {
return d . reference
}
// SetReference allows you to store a reference of any type in this option.
func ( d * DropDownOption ) SetReference ( reference interface { } ) * DropDownOption {
d . reference = reference
return d
}
// SetChangedFunc sets a handler which is called when the user changes the
// drop-down's option. This handler will be called in addition and prior to
// an option's optional individual handler. The handler is provided with the
// selected option's index and the option itself. If "no option" was selected, these values
// are -1 and nil.
func ( d * DropDown ) SetChangedFunc ( handler func ( index int , option * DropDownOption ) ) * DropDown {
d . list . SetChangedFunc ( func ( index int , item * ListItem ) {
handler ( index , d . options [ index ] )
} )
return d
}
// DropDown implements a selection widget whose options become visible in a
@ -19,7 +64,7 @@ type DropDown struct {
* Box
// The options from which the user can choose.
options [ ] * d ropDownOption
options [ ] * D ropDownOption
// Strings to be placed before and after each drop-down option.
optionPrefix , optionSuffix string
@ -28,7 +73,7 @@ type DropDown struct {
// currently selected.
currentOption int
// Strings to be placed bee fore and after the current option.
// Strings to be placed before and after the current option.
currentOptionPrefix , currentOptionSuffix string
// The text to be displayed when no option has yet been selected.
@ -49,12 +94,21 @@ type DropDown struct {
// The label color.
labelColor tcell . Color
// The label color when focused.
labelColorFocused tcell . Color
// The background color of the input area.
fieldBackgroundColor tcell . Color
// The background color of the input area when focused.
fieldBackgroundColorFocused tcell . Color
// The text color of the input area.
fieldTextColor tcell . Color
// The text color of the input area when focused.
fieldTextColorFocused tcell . Color
// The color for prefixes.
prefixTextColor tcell . Color
@ -77,11 +131,17 @@ type DropDown struct {
// A callback function which is called when the user changes the drop-down's
// selection.
selected func ( text string , index int )
selected func ( index int , option * DropDownOption )
// Set to true when mouse dragging is in progress.
dragging bool
// The chars to show when the option's text gets shortened.
abbreviationChars string
// The symbol to draw at the end of the field.
dropDownSymbol rune
sync . RWMutex
}
@ -96,13 +156,18 @@ func NewDropDown() *DropDown {
SetBackgroundColor ( Styles . MoreContrastBackgroundColor )
d := & DropDown {
Box : NewBox ( ) ,
currentOption : - 1 ,
list : list ,
labelColor : Styles . SecondaryTextColor ,
fieldBackgroundColor : Styles . ContrastBackgroundColor ,
fieldTextColor : Styles . PrimaryTextColor ,
prefixTextColor : Styles . ContrastSecondaryTextColor ,
Box : NewBox ( ) ,
currentOption : - 1 ,
list : list ,
labelColor : Styles . SecondaryTextColor ,
labelColorFocused : Styles . SecondaryTextColor ,
fieldBackgroundColor : Styles . ContrastBackgroundColor ,
fieldBackgroundColorFocused : Styles . ContrastBackgroundColor ,
fieldTextColor : Styles . PrimaryTextColor ,
fieldTextColorFocused : Styles . PrimaryTextColor ,
prefixTextColor : Styles . ContrastSecondaryTextColor ,
dropDownSymbol : Styles . DropDownSymbol ,
abbreviationChars : Styles . DropDownAbbreviationChars ,
}
d . focus = d
@ -110,6 +175,15 @@ func NewDropDown() *DropDown {
return d
}
// SetDropDownSymbolRune sets the rune to be drawn at the end of the dropdown field
// to indicate that this field is a dropdown.
func ( d * DropDown ) SetDropDownSymbolRune ( symbol rune ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . dropDownSymbol = symbol
return d
}
// SetCurrentOption sets the index of the currently selected option. This may
// be a negative value to indicate that no option is currently selected. Calling
// this function will also trigger the "selected" callback (if there is one).
@ -122,12 +196,12 @@ func (d *DropDown) SetCurrentOption(index int) *DropDown {
d . list . SetCurrentItem ( index )
if d . selected != nil {
d . Unlock ( )
d . selected ( d . options [ index ] . Text , index )
d . selected ( index , d . options [ index ] )
d . Lock ( )
}
if d . options [ index ] . S elected != nil {
if d . options [ index ] . s elected != nil {
d . Unlock ( )
d . options [ index ] . Selected ( )
d . options [ index ] . selected ( index , d . options [ index ] )
d . Lock ( )
}
} else {
@ -135,7 +209,7 @@ func (d *DropDown) SetCurrentOption(index int) *DropDown {
d . list . SetCurrentItem ( 0 ) // Set to 0 because -1 means "last item".
if d . selected != nil {
d . Unlock ( )
d . selected ( "" , - 1 )
d . selected ( - 1 , nil )
d . Lock ( )
}
}
@ -143,16 +217,16 @@ func (d *DropDown) SetCurrentOption(index int) *DropDown {
}
// GetCurrentOption returns the index of the currently selected option as well
// as its text. If no option was selected, -1 and an empty string is returned.
func ( d * DropDown ) GetCurrentOption ( ) ( int , string ) {
// as the option itself. If no option was selected, -1 and nil is returned.
func ( d * DropDown ) GetCurrentOption ( ) ( int , * DropDownOption ) {
d . RLock ( )
defer d . RUnlock ( )
var text string
var option * DropDownOption
if d . currentOption >= 0 && d . currentOption < len ( d . options ) {
text = d . options [ d . currentOption ] . Text
option = d . options [ d . currentOption ]
}
return d . currentOption , text
return d . currentOption , option
}
// SetTextOptions sets the text to be placed before and after each drop-down
@ -170,7 +244,7 @@ func (d *DropDown) SetTextOptions(prefix, suffix, currentPrefix, currentSuffix,
d . optionPrefix = prefix
d . optionSuffix = suffix
for index := 0 ; index < d . list . GetItemCount ( ) ; index ++ {
d . list . SetItemText ( index , prefix + d . options [ index ] . T ext+ suffix , "" )
d . list . SetItemText ( index , prefix + d . options [ index ] . t ext+ suffix , "" )
}
return d
}
@ -192,6 +266,24 @@ func (d *DropDown) GetLabel() string {
return d . label
}
// SetAttributes sets the given attributes on the drop down.
func ( d * DropDown ) SetAttributes ( attributes ... FormItemAttribute ) {
allAttributes := newFormItemAttributes ( )
for _ , attribute := range attributes {
attribute . apply ( allAttributes )
}
allAttributes . setLabelWidth ( & d . labelWidth )
allAttributes . setBackgroundColor ( & d . backgroundColor )
allAttributes . setLabelColor ( & d . labelColor )
allAttributes . setLabelColorFocused ( & d . labelColorFocused )
allAttributes . setFieldTextColor ( & d . fieldTextColor )
allAttributes . setFieldTextColorFocused ( & d . fieldTextColorFocused )
allAttributes . setFieldBackgroundColor ( & d . fieldBackgroundColor )
allAttributes . setFieldBackgroundColorFocused ( & d . fieldBackgroundColorFocused )
allAttributes . setFinishedFunc ( & d . finished )
}
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func ( d * DropDown ) SetLabelWidth ( width int ) * DropDown {
@ -202,6 +294,12 @@ func (d *DropDown) SetLabelWidth(width int) *DropDown {
return d
}
// SetBackgroundColor sets the background color.
func ( d * DropDown ) SetBackgroundColor ( color tcell . Color ) * DropDown {
d . Box . SetBackgroundColor ( color )
return d
}
// SetLabelColor sets the color of the label.
func ( d * DropDown ) SetLabelColor ( color tcell . Color ) * DropDown {
d . Lock ( )
@ -211,6 +309,15 @@ func (d *DropDown) SetLabelColor(color tcell.Color) *DropDown {
return d
}
// SetLabelColorFocused sets the color of the label when focused.
func ( d * DropDown ) SetLabelColorFocused ( color tcell . Color ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . labelColorFocused = color
return d
}
// SetFieldBackgroundColor sets the background color of the options area.
func ( d * DropDown ) SetFieldBackgroundColor ( color tcell . Color ) * DropDown {
d . Lock ( )
@ -220,6 +327,15 @@ func (d *DropDown) SetFieldBackgroundColor(color tcell.Color) *DropDown {
return d
}
// SetFieldBackgroundColorFocused sets the background color of the options area when focused.
func ( d * DropDown ) SetFieldBackgroundColorFocused ( color tcell . Color ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . fieldBackgroundColorFocused = color
return d
}
// SetFieldTextColor sets the text color of the options area.
func ( d * DropDown ) SetFieldTextColor ( color tcell . Color ) * DropDown {
d . Lock ( )
@ -229,27 +345,59 @@ func (d *DropDown) SetFieldTextColor(color tcell.Color) *DropDown {
return d
}
// SetPrefixTextColor sets the color of the prefix string. The prefix string is
// shown when the user starts typing text, which directly selects the first
// option that starts with the typed string.
func ( d * DropDown ) SetPrefixTextColor ( color tcell . Color ) * DropDown {
// SetFieldTextColorFocused sets the text color of the options area when focused.
func ( d * DropDown ) SetFieldTextColorFocused ( color tcell . Color ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . prefixTextColor = color
d . fieldTextColorFocused = color
return d
}
// SetDropDownTextColor sets text color of the drop down list.
func ( d * DropDown ) SetDropDownTextColor ( color tcell . Color ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . list . SetMainTextColor ( color )
return d
}
// SetFormAttributes sets attributes shared by all form items.
func ( d * DropDown ) SetFormAttributes ( labelWidth int , labelColor , bgColor , fieldTextColor , fieldBgColor tcell . Color ) FormItem {
// SetDropDownBackgroundColor sets the background color of the drop list .
func ( d * DropDown ) SetDropDownBackgroundColor ( color tcell . Color ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . labelWidth = labelWidth
d . labelColor = labelColor
d . backgroundColor = bgColor
d . fieldTextColor = fieldTextColor
d . fieldBackgroundColor = fieldBgColor
d . list . SetBackgroundColor ( color )
return d
}
// The text color of the selected option in the drop down list.
func ( d * DropDown ) SetDropDownSelectedTextColor ( color tcell . Color ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . list . SetSelectedTextColor ( color )
return d
}
// The background color of the selected option in the drop down list.
func ( d * DropDown ) SetDropDownSelectedBackgroundColor ( color tcell . Color ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . list . SetSelectedBackgroundColor ( color )
return d
}
// SetPrefixTextColor sets the color of the prefix string. The prefix string is
// shown when the user starts typing text, which directly selects the first
// option that starts with the typed string.
func ( d * DropDown ) SetPrefixTextColor ( color tcell . Color ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . prefixTextColor = color
return d
}
@ -263,54 +411,84 @@ func (d *DropDown) SetFieldWidth(width int) *DropDown {
return d
}
// GetFieldHeight returns the height of the field.
func ( d * DropDown ) GetFieldHeight ( ) int {
return 1
}
// GetFieldWidth returns this primitive's field screen width.
func ( d * DropDown ) GetFieldWidth ( ) int {
d . RLock ( )
defer d . RUnlock ( )
return d . getFieldWidth ( )
}
func ( d * DropDown ) getFieldWidth ( ) int {
if d . fieldWidth > 0 {
return d . fieldWidth
}
fieldWidth := 0
for _ , option := range d . options {
width := TaggedStringWidth ( option . T ext)
width := TaggedStringWidth ( option . t ext)
if width > fieldWidth {
fieldWidth = width
}
}
fieldWidth += len ( d . optionPrefix ) + len ( d . optionSuffix )
fieldWidth += len ( d . currentOptionPrefix ) + len ( d . currentOptionSuffix )
fieldWidth += 3 // space + dropDownSymbol + space
return fieldWidth
}
// AddOption adds a new selectable option to this drop-down. The "selected"
// callback is called when this option was selected. It may be nil.
func ( d * DropDown ) AddOption ( text string , selected func ( ) ) * DropDown {
// AddOptions adds new selectable options to this drop-down.
func ( d * DropDown ) AddOptionsSimple ( options ... string ) * DropDown {
optionsToAdd := make ( [ ] * DropDownOption , len ( options ) )
for i , option := range options {
optionsToAdd [ i ] = NewDropDownOption ( option )
}
d . AddOptions ( optionsToAdd ... )
return d
}
// AddOptions adds new selectable options to this drop-down.
func ( d * DropDown ) AddOptions ( options ... * DropDownOption ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
return d . addOptions ( options ... )
}
return d . addOption ( text , selected )
func ( d * DropDown ) addOptions ( options ... * DropDownOption ) * DropDown {
d . options = append ( d . options , options ... )
for _ , option := range options {
d . list . AddItem ( NewListItem ( d . optionPrefix + option . text + d . optionSuffix ) )
}
return d
}
func ( d * DropDown ) addOption ( text string , selected func ( ) ) * DropDown {
d . options = append ( d . options , & dropDownOption { Text : text , Selected : selected } )
d . list . AddItem ( d . optionPrefix + text + d . optionSuffix , "" , 0 , nil )
// SetOptionsSimple replaces all current options with the ones provided and installs
// one callback function which is called when one of the options is selected.
// It will be called with the option's index and the option itself
// The "selected" parameter may be nil.
func ( d * DropDown ) SetOptionsSimple ( selected func ( index int , option * DropDownOption ) , options ... string ) * DropDown {
optionsToSet := make ( [ ] * DropDownOption , len ( options ) )
for i , option := range options {
optionsToSet [ i ] = NewDropDownOption ( option )
}
d . SetOptions ( selected , optionsToSet ... )
return d
}
// SetOptions replaces all current options with the ones provided and installs
// one callback function which is called when one of the options is selected.
// It will be called with the option's text and its index into the options
// slice. The "selected" parameter may be nil.
func ( d * DropDown ) SetOptions ( texts [ ] string , selected func ( text string , index int ) ) * DropDown {
// It will be called with the option's index and the option itself.
// The "selected" parameter may be nil.
func ( d * DropDown ) SetOptions ( selected func ( index int , option * DropDownOption ) , options ... * DropDownOption ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
d . list . Clear ( )
d . options = nil
for index , text := range texts {
func ( t string , i int ) {
d . addOption ( text , nil )
} ( text , index )
}
d . addOptions ( options ... )
d . selected = selected
return d
}
@ -318,9 +496,9 @@ func (d *DropDown) SetOptions(texts []string, selected func(text string, index i
// SetSelectedFunc sets a handler which is called when the user changes the
// drop-down's option. This handler will be called in addition and prior to
// an option's optional individual handler. The handler is provided with the
// selected option's text and index . If "no option" was selected, these values
// are an empty string and -1 .
func ( d * DropDown ) SetSelectedFunc ( handler func ( text string , index int ) ) * DropDown {
// selected option's index and the option itself . If "no option" was selected, these values
// are -1 and nil .
func ( d * DropDown ) SetSelectedFunc ( handler func ( index int , option * DropDownOption ) ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
@ -344,7 +522,7 @@ func (d *DropDown) SetDoneFunc(handler func(key tcell.Key)) *DropDown {
}
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
func ( d * DropDown ) SetFinishedFunc ( handler func ( key tcell . Key ) ) FormItem {
func ( d * DropDown ) SetFinishedFunc ( handler func ( key tcell . Key ) ) * DropDown {
d . Lock ( )
defer d . Unlock ( )
@ -360,6 +538,16 @@ func (d *DropDown) Draw(screen tcell.Screen) {
d . Lock ( )
defer d . Unlock ( )
// Select colors
labelColor := d . labelColor
fieldBackgroundColor := d . fieldBackgroundColor
fieldTextColor := d . fieldTextColor
if hasFocus {
labelColor = d . labelColorFocused
fieldBackgroundColor = d . fieldBackgroundColorFocused
fieldTextColor = d . fieldTextColorFocused
}
// Prepare.
x , y , width , height := d . GetInnerRect ( )
rightLimit := x + width
@ -373,10 +561,10 @@ func (d *DropDown) Draw(screen tcell.Screen) {
if labelWidth > rightLimit - x {
labelWidth = rightLimit - x
}
Print ( screen , d . label , x , y , labelWidth , AlignLeft , d . labelColor )
Print ( screen , d . label , x , y , labelWidth , AlignLeft , labelColor )
x += labelWidth
} else {
_ , drawnWidth := Print ( screen , d . label , x , y , rightLimit - x , AlignLeft , d . labelColor )
_ , drawnWidth := Print ( screen , d . label , x , y , rightLimit - x , AlignLeft , labelColor )
x += drawnWidth
}
@ -384,14 +572,14 @@ func (d *DropDown) Draw(screen tcell.Screen) {
maxWidth := 0
optionWrapWidth := TaggedStringWidth ( d . optionPrefix + d . optionSuffix )
for _ , option := range d . options {
strWidth := TaggedStringWidth ( option . T ext) + optionWrapWidth
strWidth := TaggedStringWidth ( option . t ext) + optionWrapWidth
if strWidth > maxWidth {
maxWidth = strWidth
}
}
// Draw selection area.
fieldWidth := d . fieldWidth
fieldWidth := d . getFieldWidth ( )
if fieldWidth == 0 {
fieldWidth = maxWidth
if d . currentOption < 0 {
@ -400,7 +588,7 @@ func (d *DropDown) Draw(screen tcell.Screen) {
fieldWidth = noSelectionWidth
}
} else if d . currentOption < len ( d . options ) {
currentOptionWidth := TaggedStringWidth ( d . currentOptionPrefix + d . options [ d . currentOption ] . T ext + d . currentOptionSuffix )
currentOptionWidth := TaggedStringWidth ( d . currentOptionPrefix + d . options [ d . currentOption ] . t ext + d . currentOptionSuffix )
if currentOptionWidth > fieldWidth {
fieldWidth = currentOptionWidth
}
@ -409,10 +597,7 @@ func (d *DropDown) Draw(screen tcell.Screen) {
if rightLimit - x < fieldWidth {
fieldWidth = rightLimit - x
}
fieldStyle := tcell . StyleDefault . Background ( d . fieldBackgroundColor )
if hasFocus && ! d . open {
fieldStyle = fieldStyle . Background ( d . fieldTextColor )
}
fieldStyle := tcell . StyleDefault . Background ( fieldBackgroundColor )
for index