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 }