nt

A sensible note-taking program
git clone git://git.laack.co/nt.git
Log | Files | Refs | README

dropdown.go (22943B)


      1 package tview
      2 
      3 import (
      4 	"regexp"
      5 	"strings"
      6 
      7 	"github.com/gdamore/tcell/v2"
      8 )
      9 
     10 // dropDownOption is one option that can be selected in a drop-down primitive.
     11 type dropDownOption struct {
     12 	Text     string // The text to be displayed in the drop-down.
     13 	Selected func() // The (optional) callback for when this option was selected.
     14 }
     15 
     16 // DropDown implements a selection widget whose options become visible in a
     17 // drop-down list when activated.
     18 //
     19 // See https://github.com/rivo/tview/wiki/DropDown for an example.
     20 type DropDown struct {
     21 	*Box
     22 
     23 	// Whether or not this drop-down is disabled/read-only.
     24 	disabled bool
     25 
     26 	// The options from which the user can choose.
     27 	options []*dropDownOption
     28 
     29 	// Strings to be placed before and after each drop-down option.
     30 	optionPrefix, optionSuffix string
     31 
     32 	// The index of the currently selected option. Negative if no option is
     33 	// currently selected.
     34 	currentOption int
     35 
     36 	// Strings to be placed before and after the current option.
     37 	currentOptionPrefix, currentOptionSuffix string
     38 
     39 	// The text to be displayed when no option has yet been selected.
     40 	noSelection string
     41 
     42 	// Set to true if the options are visible and selectable.
     43 	open bool
     44 
     45 	// The input field containing the entered prefix for the current selection.
     46 	// This is only visible when the drop-down is open. It never receives focus,
     47 	// however. And it only receives events, we never call its Draw method.
     48 	prefix *InputField
     49 
     50 	// The list element for the options.
     51 	list *List
     52 
     53 	// The text to be displayed before the input area.
     54 	label string
     55 
     56 	// The label style.
     57 	labelStyle tcell.Style
     58 
     59 	// The field style.
     60 	fieldStyle tcell.Style
     61 
     62 	// The style of the field when it is focused and the drop-down is closed.
     63 	focusedStyle tcell.Style
     64 
     65 	// The style of the field when it is disabled.
     66 	disabledStyle tcell.Style
     67 
     68 	// The style of the prefix.
     69 	prefixStyle tcell.Style
     70 
     71 	// The screen width of the label area. A value of 0 means use the width of
     72 	// the label text.
     73 	labelWidth int
     74 
     75 	// The screen width of the input area. A value of 0 means extend as much as
     76 	// possible.
     77 	fieldWidth int
     78 
     79 	// An optional function which is called when the user indicated that they
     80 	// are done selecting options. The key which was pressed is provided (tab,
     81 	// shift-tab, or escape).
     82 	done func(tcell.Key)
     83 
     84 	// A callback function set by the Form class and called when the user leaves
     85 	// this form item.
     86 	finished func(tcell.Key)
     87 
     88 	// A callback function which is called when the user changes the drop-down's
     89 	// selection.
     90 	selected func(text string, index int)
     91 
     92 	dragging bool // Set to true when mouse dragging is in progress.
     93 }
     94 
     95 // NewDropDown returns a new drop-down.
     96 func NewDropDown() *DropDown {
     97 	list := NewList()
     98 	list.ShowSecondaryText(false).
     99 		SetMainTextStyle(tcell.StyleDefault.Background(Styles.MoreContrastBackgroundColor).Foreground(Styles.PrimitiveBackgroundColor)).
    100 		SetSelectedStyle(tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.PrimitiveBackgroundColor)).
    101 		SetHighlightFullLine(true).
    102 		SetBackgroundColor(Styles.MoreContrastBackgroundColor)
    103 
    104 	prefix := NewInputField()
    105 
    106 	box := NewBox()
    107 	d := &DropDown{
    108 		Box:           box,
    109 		currentOption: -1,
    110 		list:          list,
    111 		prefix:        prefix,
    112 		labelStyle:    tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),
    113 		fieldStyle:    tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),
    114 		focusedStyle:  tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),
    115 		disabledStyle: tcell.StyleDefault.Background(box.backgroundColor).Foreground(Styles.SecondaryTextColor),
    116 		prefixStyle:   tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.ContrastBackgroundColor),
    117 	}
    118 
    119 	return d
    120 }
    121 
    122 // SetCurrentOption sets the index of the currently selected option. This may
    123 // be a negative value to indicate that no option is currently selected. Calling
    124 // this function will also trigger the "selected" callback (if there is one).
    125 func (d *DropDown) SetCurrentOption(index int) *DropDown {
    126 	if index >= 0 && index < len(d.options) {
    127 		d.currentOption = index
    128 		d.list.SetCurrentItem(index)
    129 		if d.selected != nil {
    130 			d.selected(d.options[index].Text, index)
    131 		}
    132 		if d.options[index].Selected != nil {
    133 			d.options[index].Selected()
    134 		}
    135 	} else {
    136 		d.currentOption = -1
    137 		d.list.SetCurrentItem(0) // Set to 0 because -1 means "last item".
    138 		if d.selected != nil {
    139 			d.selected("", -1)
    140 		}
    141 	}
    142 	return d
    143 }
    144 
    145 // GetCurrentOption returns the index of the currently selected option as well
    146 // as its text. If no option was selected, -1 and an empty string is returned.
    147 func (d *DropDown) GetCurrentOption() (int, string) {
    148 	var text string
    149 	if d.currentOption >= 0 && d.currentOption < len(d.options) {
    150 		text = d.options[d.currentOption].Text
    151 	}
    152 	return d.currentOption, text
    153 }
    154 
    155 // SetTextOptions sets the text to be placed before and after each drop-down
    156 // option (prefix/suffix), the text placed before and after the currently
    157 // selected option (currentPrefix/currentSuffix) as well as the text to be
    158 // displayed when no option is currently selected. Per default, all of these
    159 // strings are empty.
    160 func (d *DropDown) SetTextOptions(prefix, suffix, currentPrefix, currentSuffix, noSelection string) *DropDown {
    161 	d.currentOptionPrefix = currentPrefix
    162 	d.currentOptionSuffix = currentSuffix
    163 	d.noSelection = noSelection
    164 	d.optionPrefix = prefix
    165 	d.optionSuffix = suffix
    166 	for index := 0; index < d.list.GetItemCount(); index++ {
    167 		d.list.SetItemText(index, prefix+d.options[index].Text+suffix, "")
    168 	}
    169 	return d
    170 }
    171 
    172 // SetUseStyleTags sets a flag that determines whether tags found in the option
    173 // texts are interpreted as tview tags. By default, this flag is enabled (for
    174 // backwards compatibility reasons).
    175 func (d *DropDown) SetUseStyleTags(useStyleTags bool) *DropDown {
    176 	d.list.SetUseStyleTags(useStyleTags, useStyleTags)
    177 	return d
    178 }
    179 
    180 // SetLabel sets the text to be displayed before the input area.
    181 func (d *DropDown) SetLabel(label string) *DropDown {
    182 	d.label = label
    183 	return d
    184 }
    185 
    186 // GetLabel returns the text to be displayed before the input area.
    187 func (d *DropDown) GetLabel() string {
    188 	return d.label
    189 }
    190 
    191 // SetLabelWidth sets the screen width of the label. A value of 0 will cause the
    192 // primitive to use the width of the label string.
    193 func (d *DropDown) SetLabelWidth(width int) *DropDown {
    194 	d.labelWidth = width
    195 	return d
    196 }
    197 
    198 // SetLabelColor sets the color of the label.
    199 func (d *DropDown) SetLabelColor(color tcell.Color) *DropDown {
    200 	d.labelStyle = d.labelStyle.Foreground(color)
    201 	return d
    202 }
    203 
    204 // SetLabelStyle sets the style of the label.
    205 func (d *DropDown) SetLabelStyle(style tcell.Style) *DropDown {
    206 	d.labelStyle = style
    207 	return d
    208 }
    209 
    210 // SetFieldBackgroundColor sets the background color of the selected field.
    211 // This also overrides the prefix background color.
    212 func (d *DropDown) SetFieldBackgroundColor(color tcell.Color) *DropDown {
    213 	d.fieldStyle = d.fieldStyle.Background(color)
    214 	d.prefix.SetFieldBackgroundColor(color)
    215 	return d
    216 }
    217 
    218 // SetFieldTextColor sets the text color of the options area.
    219 func (d *DropDown) SetFieldTextColor(color tcell.Color) *DropDown {
    220 	d.fieldStyle = d.fieldStyle.Foreground(color)
    221 	return d
    222 }
    223 
    224 // SetFieldStyle sets the style of the options area.
    225 func (d *DropDown) SetFieldStyle(style tcell.Style) *DropDown {
    226 	d.fieldStyle = style
    227 	return d
    228 }
    229 
    230 // SetFocusedStyle sets the style of the options area when the drop-down is
    231 // focused and closed.
    232 func (d *DropDown) SetFocusedStyle(style tcell.Style) *DropDown {
    233 	d.focusedStyle = style
    234 	return d
    235 }
    236 
    237 // SetDisabledStyle sets the style of the options area when the drop-down is
    238 // disabled.
    239 func (d *DropDown) SetDisabledStyle(style tcell.Style) *DropDown {
    240 	d.disabledStyle = style
    241 	return d
    242 }
    243 
    244 // SetPrefixTextColor sets the color of the prefix string. The prefix string is
    245 // shown when the user starts typing text, which directly selects the first
    246 // option that starts with the typed string.
    247 func (d *DropDown) SetPrefixTextColor(color tcell.Color) *DropDown {
    248 	d.prefixStyle = d.prefixStyle.Foreground(color)
    249 	return d
    250 }
    251 
    252 // SetPrefixStyle sets the style of the prefix string. The prefix string is
    253 // shown when the user starts typing text, which directly selects the first
    254 // option that starts with the typed string.
    255 func (d *DropDown) SetPrefixStyle(style tcell.Style) *DropDown {
    256 	d.prefixStyle = style
    257 	return d
    258 }
    259 
    260 // SetListStyles sets the styles of the items in the drop-down list (unselected
    261 // as well as selected items). Style attributes are currently ignored but may be
    262 // used in the future.
    263 func (d *DropDown) SetListStyles(unselected, selected tcell.Style) *DropDown {
    264 	d.list.SetMainTextStyle(unselected).SetSelectedStyle(selected)
    265 	_, bg, _ := unselected.Decompose()
    266 	d.list.SetBackgroundColor(bg)
    267 	return d
    268 }
    269 
    270 // SetFormAttributes sets attributes shared by all form items.
    271 func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
    272 	d.labelWidth = labelWidth
    273 	d.SetLabelColor(labelColor)
    274 	d.SetBackgroundColor(bgColor)
    275 	d.SetFieldStyle(tcell.StyleDefault.Foreground(fieldTextColor).Background(fieldBgColor))
    276 	return d
    277 }
    278 
    279 // SetFieldWidth sets the screen width of the options area. A value of 0 means
    280 // extend to as long as the longest option text.
    281 func (d *DropDown) SetFieldWidth(width int) *DropDown {
    282 	d.fieldWidth = width
    283 	return d
    284 }
    285 
    286 // GetFieldWidth returns this primitive's field screen width.
    287 func (d *DropDown) GetFieldWidth() int {
    288 	if d.fieldWidth > 0 {
    289 		return d.fieldWidth
    290 	}
    291 	fieldWidth := 0
    292 	for _, option := range d.options {
    293 		width := TaggedStringWidth(option.Text)
    294 		if width > fieldWidth {
    295 			fieldWidth = width
    296 		}
    297 	}
    298 	return fieldWidth
    299 }
    300 
    301 // GetFieldHeight returns this primitive's field height.
    302 func (d *DropDown) GetFieldHeight() int {
    303 	return 1
    304 }
    305 
    306 // SetDisabled sets whether or not the item is disabled / read-only.
    307 func (d *DropDown) SetDisabled(disabled bool) FormItem {
    308 	d.disabled = disabled
    309 	if d.finished != nil {
    310 		d.finished(-1)
    311 	}
    312 	return d
    313 }
    314 
    315 // AddOption adds a new selectable option to this drop-down. The "selected"
    316 // callback is called when this option was selected. It may be nil.
    317 func (d *DropDown) AddOption(text string, selected func()) *DropDown {
    318 	d.options = append(d.options, &dropDownOption{Text: text, Selected: selected})
    319 	d.list.AddItem(d.optionPrefix+text+d.optionSuffix, "", 0, nil)
    320 	return d
    321 }
    322 
    323 // SetOptions replaces all current options with the ones provided and installs
    324 // one callback function which is called when one of the options is selected.
    325 // It will be called with the option's text and its index into the options
    326 // slice. The "selected" parameter may be nil.
    327 func (d *DropDown) SetOptions(texts []string, selected func(text string, index int)) *DropDown {
    328 	d.list.Clear()
    329 	d.options = nil
    330 	for _, text := range texts {
    331 		d.AddOption(text, nil)
    332 	}
    333 	d.selected = selected
    334 	return d
    335 }
    336 
    337 // GetOptionCount returns the number of options in the drop-down.
    338 func (d *DropDown) GetOptionCount() int {
    339 	return len(d.options)
    340 }
    341 
    342 // RemoveOption removes the specified option from the drop-down. Panics if the
    343 // index is out of range. If the currently selected option is removed, no option
    344 // will be selected.
    345 func (d *DropDown) RemoveOption(index int) *DropDown {
    346 	if index == d.currentOption {
    347 		d.currentOption = -1
    348 	}
    349 	d.options = append(d.options[:index], d.options[index+1:]...)
    350 	d.list.RemoveItem(index)
    351 	return d
    352 }
    353 
    354 // SetSelectedFunc sets a handler which is called when the user changes the
    355 // drop-down's option. This handler will be called in addition and prior to
    356 // an option's optional individual handler. The handler is provided with the
    357 // selected option's text and index. If "no option" was selected, these values
    358 // are an empty string and -1.
    359 func (d *DropDown) SetSelectedFunc(handler func(text string, index int)) *DropDown {
    360 	d.selected = handler
    361 	return d
    362 }
    363 
    364 // SetDoneFunc sets a handler which is called when the user is done selecting
    365 // options. The callback function is provided with the key that was pressed,
    366 // which is one of the following:
    367 //
    368 //   - KeyEscape: Abort selection.
    369 //   - KeyTab: Move to the next field.
    370 //   - KeyBacktab: Move to the previous field.
    371 func (d *DropDown) SetDoneFunc(handler func(key tcell.Key)) *DropDown {
    372 	d.done = handler
    373 	return d
    374 }
    375 
    376 // SetFinishedFunc sets a callback invoked when the user leaves this form item.
    377 func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
    378 	d.finished = handler
    379 	return d
    380 }
    381 
    382 // Draw draws this primitive onto the screen.
    383 func (d *DropDown) Draw(screen tcell.Screen) {
    384 	d.Box.DrawForSubclass(screen, d)
    385 
    386 	// Prepare.
    387 	x, y, width, height := d.GetInnerRect()
    388 	rightLimit := x + width
    389 	if height < 1 || rightLimit <= x {
    390 		return
    391 	}
    392 	useStyleTags, _ := d.list.GetUseStyleTags()
    393 
    394 	// Draw label.
    395 	if d.labelWidth > 0 {
    396 		labelWidth := d.labelWidth
    397 		if labelWidth > rightLimit-x {
    398 			labelWidth = rightLimit - x
    399 		}
    400 		printWithStyle(screen, d.label, x, y, 0, labelWidth, AlignLeft, d.labelStyle, true)
    401 		x += labelWidth
    402 	} else {
    403 		_, _, drawnWidth := printWithStyle(screen, d.label, x, y, 0, rightLimit-x, AlignLeft, d.labelStyle, true)
    404 		x += drawnWidth
    405 	}
    406 
    407 	// What's the longest option text?
    408 	maxWidth := 0
    409 	for _, option := range d.options {
    410 		str := d.optionPrefix + option.Text + d.optionSuffix
    411 		if !useStyleTags {
    412 			str = Escape(str)
    413 		}
    414 		strWidth := TaggedStringWidth(str)
    415 		if strWidth > maxWidth {
    416 			maxWidth = strWidth
    417 		}
    418 		str = d.currentOptionPrefix + option.Text + d.currentOptionSuffix
    419 		if !useStyleTags {
    420 			str = Escape(str)
    421 		}
    422 		strWidth = TaggedStringWidth(str)
    423 		if strWidth > maxWidth {
    424 			maxWidth = strWidth
    425 		}
    426 	}
    427 
    428 	// Draw selection area.
    429 	fieldWidth := d.fieldWidth
    430 	if fieldWidth == 0 {
    431 		fieldWidth = maxWidth
    432 		if d.currentOption < 0 {
    433 			noSelectionWidth := TaggedStringWidth(d.noSelection)
    434 			if noSelectionWidth > fieldWidth {
    435 				fieldWidth = noSelectionWidth
    436 			}
    437 		} else if d.currentOption < len(d.options) {
    438 			currentOptionWidth := TaggedStringWidth(d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix)
    439 			if currentOptionWidth > fieldWidth {
    440 				fieldWidth = currentOptionWidth
    441 			}
    442 		}
    443 	}
    444 	if rightLimit-x < fieldWidth {
    445 		fieldWidth = rightLimit - x
    446 	}
    447 	fieldStyle := d.fieldStyle
    448 	if d.disabled {
    449 		fieldStyle = d.disabledStyle
    450 	} else if d.HasFocus() && !d.open {
    451 		fieldStyle = d.focusedStyle
    452 	}
    453 	for index := 0; index < fieldWidth; index++ {
    454 		screen.SetContent(x+index, y, ' ', nil, fieldStyle)
    455 	}
    456 
    457 	// Draw selected text.
    458 	prefix := Escape(d.prefix.GetText())
    459 	if d.HasFocus() && d.open && len(prefix) > 0 {
    460 		// The drop-down is open and we have an input prefix.
    461 		//  Draw current option prefix first.
    462 		currentOptionPrefix := d.currentOptionPrefix
    463 		currentOptionSuffix := d.currentOptionSuffix
    464 		if !useStyleTags {
    465 			currentOptionPrefix = Escape(currentOptionPrefix)
    466 			currentOptionSuffix = Escape(currentOptionSuffix)
    467 		}
    468 		_, _, copWidth := printWithStyle(screen, currentOptionPrefix, x, y, 0, fieldWidth, AlignLeft, d.fieldStyle, false)
    469 		if copWidth < fieldWidth {
    470 			// Then draw the prefix.
    471 			_, _, prefixWidth := printWithStyle(screen, prefix, x+copWidth, y, 0, fieldWidth-copWidth, AlignLeft, d.prefixStyle, false)
    472 			if copWidth+prefixWidth < fieldWidth {
    473 				// Then the current option remainder.
    474 				var corWidth int
    475 				currentItem := d.list.GetCurrentItem()
    476 				if currentItem >= 0 && currentItem < len(d.options) {
    477 					text := d.options[currentItem].Text
    478 					if !useStyleTags {
    479 						text = Escape(text)
    480 					}
    481 					_, _, corWidth = printWithStyle(screen, text, x+copWidth+prefixWidth, y, prefixWidth, fieldWidth-copWidth-prefixWidth, AlignLeft, d.fieldStyle, false)
    482 				}
    483 				if copWidth+prefixWidth+corWidth < fieldWidth {
    484 					// And finally the current option suffix.
    485 					printWithStyle(screen, currentOptionSuffix, x+copWidth+prefixWidth+corWidth, y, 0, fieldWidth-copWidth-prefixWidth-corWidth, AlignLeft, d.fieldStyle, false)
    486 				}
    487 			}
    488 		}
    489 	} else {
    490 		// The drop-down is closed. Just draw the selected option.
    491 		text := d.noSelection
    492 		if d.currentOption >= 0 && d.currentOption < len(d.options) {
    493 			text = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix
    494 		}
    495 		if !useStyleTags {
    496 			text = Escape(text)
    497 		}
    498 		printWithStyle(screen, text, x, y, 0, fieldWidth, AlignLeft, fieldStyle, false)
    499 	}
    500 
    501 	// Draw options list.
    502 	if d.HasFocus() && d.open {
    503 		lx := x
    504 		ly := y + 1
    505 		lwidth := maxWidth
    506 		lheight := len(d.options)
    507 		swidth, sheight := screen.Size()
    508 		// We prefer to align the left sides of the list and the main widget, but
    509 		// if there is no space to the right, then shift the list to the left.
    510 		if lx+lwidth >= swidth {
    511 			lx = swidth - lwidth
    512 			if lx < 0 {
    513 				lx = 0
    514 			}
    515 		}
    516 		// We prefer to drop down but if there is no space, maybe drop up?
    517 		if ly+lheight >= sheight && ly-2 > lheight-ly {
    518 			ly = y - lheight
    519 			if ly < 0 {
    520 				ly = 0
    521 			}
    522 		}
    523 		if ly+lheight >= sheight {
    524 			lheight = sheight - ly
    525 		}
    526 		d.list.SetRect(lx, ly, lwidth, lheight)
    527 		d.list.Draw(screen)
    528 	}
    529 }
    530 
    531 // InputHandler returns the handler for this primitive.
    532 func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
    533 	return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
    534 		if d.disabled {
    535 			return
    536 		}
    537 
    538 		// Process key event.
    539 		switch key := event.Key(); key {
    540 		case tcell.KeyDown, tcell.KeyUp, tcell.KeyHome, tcell.KeyEnd, tcell.KeyPgDn, tcell.KeyPgUp:
    541 			// Open the list and forward the event to it.
    542 			d.openList(setFocus)
    543 			if handler := d.list.InputHandler(); handler != nil {
    544 				handler(event, setFocus)
    545 			}
    546 			d.prefix.SetText("")
    547 		case tcell.KeyEnter:
    548 			// If the list is closed, open it. Otherwise, forward the event to
    549 			// it.
    550 			if !d.open {
    551 				d.openList(setFocus)
    552 			} else if handler := d.list.InputHandler(); handler != nil {
    553 				handler(event, setFocus)
    554 			}
    555 		case tcell.KeyEscape, tcell.KeyTab, tcell.KeyBacktab:
    556 			// Done selecting.
    557 			if d.done != nil {
    558 				d.done(key)
    559 			}
    560 			if d.finished != nil {
    561 				d.finished(key)
    562 			}
    563 			d.closeList(setFocus)
    564 		default:
    565 			// Pass other key events to the input field.
    566 			if handler := d.prefix.InputHandler(); handler != nil {
    567 				handler(event, setFocus)
    568 			}
    569 			d.evalPrefix()
    570 			d.openList(setFocus)
    571 		}
    572 	})
    573 }
    574 
    575 // evalPrefix selects an item in the drop-down list based on the current prefix.
    576 func (d *DropDown) evalPrefix() {
    577 	prefix := strings.ToLower(d.prefix.GetText())
    578 	if len(prefix) == 0 {
    579 		return
    580 	}
    581 	useStyleTags, _ := d.list.GetUseStyleTags()
    582 	for index, option := range d.options {
    583 		text := option.Text
    584 		if useStyleTags {
    585 			text = stripTags(text)
    586 		}
    587 		if strings.HasPrefix(strings.ToLower(text), prefix) {
    588 			d.list.SetCurrentItem(index)
    589 			return
    590 		}
    591 	}
    592 }
    593 
    594 // openList hands control over to the embedded List primitive.
    595 func (d *DropDown) openList(setFocus func(Primitive)) {
    596 	if d.open {
    597 		return
    598 	}
    599 
    600 	d.open = true
    601 
    602 	d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
    603 		if d.dragging {
    604 			return // If we're dragging the mouse, we don't want to trigger any events.
    605 		}
    606 
    607 		// An option was selected. Close the list again.
    608 		d.currentOption = index
    609 		d.closeList(setFocus)
    610 
    611 		// Clear the prefix input field.
    612 		d.prefix.SetText("")
    613 
    614 		// Trigger "selected" event.
    615 		currentOption := d.options[d.currentOption]
    616 		if d.selected != nil {
    617 			d.selected(currentOption.Text, d.currentOption)
    618 		}
    619 		if currentOption.Selected != nil {
    620 			currentOption.Selected()
    621 		}
    622 	}).SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
    623 		switch key := event.Key(); key {
    624 		case tcell.KeyDown, tcell.KeyUp, tcell.KeyPgDn, tcell.KeyPgUp, tcell.KeyHome, tcell.KeyEnd, tcell.KeyEnter: // Basic list navigation.
    625 			break
    626 		case tcell.KeyEscape: // Abort selection.
    627 			d.closeList(setFocus)
    628 			return nil
    629 		default: // All other keys are passed to the input field.
    630 			if handler := d.prefix.InputHandler(); handler != nil {
    631 				handler(event, setFocus)
    632 			}
    633 			return nil
    634 		}
    635 
    636 		return event
    637 	})
    638 
    639 	setFocus(d.list)
    640 }
    641 
    642 // closeList closes the embedded List element by hiding it and removing focus
    643 // from it.
    644 func (d *DropDown) closeList(setFocus func(Primitive)) {
    645 	d.open = false
    646 	if d.list.HasFocus() {
    647 		setFocus(d)
    648 	}
    649 }
    650 
    651 // IsOpen returns true if the drop-down list is currently open.
    652 func (d *DropDown) IsOpen() bool {
    653 	return d.open
    654 }
    655 
    656 // Focus is called by the application when the primitive receives focus.
    657 func (d *DropDown) Focus(delegate func(p Primitive)) {
    658 	// If we're part of a form and this item is disabled, there's nothing the
    659 	// user can do here so we're finished.
    660 	if d.finished != nil && d.disabled {
    661 		d.finished(-1)
    662 		return
    663 	}
    664 
    665 	if d.open {
    666 		delegate(d.list)
    667 	} else {
    668 		d.Box.Focus(delegate)
    669 	}
    670 }
    671 
    672 // HasFocus returns whether or not this primitive has focus.
    673 func (d *DropDown) HasFocus() bool {
    674 	if d.open {
    675 		return d.list.HasFocus()
    676 	}
    677 	return d.Box.HasFocus()
    678 }
    679 
    680 // MouseHandler returns the mouse handler for this primitive.
    681 func (d *DropDown) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
    682 	return d.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
    683 		if d.disabled {
    684 			return false, nil
    685 		}
    686 
    687 		// Was the mouse event in the drop-down box itself (or on its label)?
    688 		x, y := event.Position()
    689 		inRect := d.InInnerRect(x, y)
    690 		if !d.open && !inRect {
    691 			return d.InRect(x, y), nil // No, and it's not expanded either. Ignore.
    692 		}
    693 
    694 		// As long as the drop-down is open, we capture all mouse events.
    695 		if d.open {
    696 			capture = d
    697 		}
    698 
    699 		switch action {
    700 		case MouseLeftDown:
    701 			consumed = d.open || inRect
    702 			capture = d
    703 			if !d.open {
    704 				d.openList(setFocus)
    705 				d.dragging = true
    706 			} else if consumed, _ := d.list.MouseHandler()(MouseLeftClick, event, setFocus); !consumed {
    707 				d.closeList(setFocus) // Close drop-down if clicked outside of it.
    708 			}
    709 		case MouseMove:
    710 			if d.dragging {
    711 				// We pretend it's a left click so we can see the selection during
    712 				// dragging. Because we don't act upon it, it's not a problem.
    713 				d.list.MouseHandler()(MouseLeftClick, event, setFocus)
    714 				consumed = true
    715 			}
    716 		case MouseLeftUp:
    717 			if d.dragging {
    718 				d.dragging = false
    719 				d.list.MouseHandler()(MouseLeftClick, event, setFocus)
    720 				consumed = true
    721 			}
    722 		}
    723 
    724 		return
    725 	})
    726 }
    727 
    728 // PasteHandler returns the handler for this primitive.
    729 func (d *DropDown) PasteHandler() func(pastedText string, setFocus func(p Primitive)) {
    730 	return d.WrapPasteHandler(func(pastedText string, setFocus func(p Primitive)) {
    731 		if !d.open || d.disabled {
    732 			return
    733 		}
    734 
    735 		// Strip any newline characters (simple version).
    736 		pastedText = regexp.MustCompile(`\r?\n`).ReplaceAllString(pastedText, "")
    737 
    738 		// Forward the pasted text to the input field.
    739 		d.prefix.PasteHandler()(pastedText, setFocus)
    740 	})
    741 }