list.go (24123B)
1 package tview 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/gdamore/tcell/v2" 8 ) 9 10 // listItem represents one item in a List. 11 type listItem struct { 12 MainText string // The main text of the list item. 13 SecondaryText string // A secondary text to be shown underneath the main text. 14 Shortcut rune // The key to select the list item directly, 0 if there is no shortcut. 15 Selected func() // The optional function which is called when the item is selected. 16 } 17 18 // List displays rows of items, each of which can be selected. List items can be 19 // shown as a single line or as two lines. They can be selected by pressing 20 // their assigned shortcut key, navigating to them and pressing Enter, or 21 // clicking on them with the mouse. The following key binds are available: 22 // 23 // - Down arrow / tab: Move down one item. 24 // - Up arrow / backtab: Move up one item. 25 // - Home: Move to the first item. 26 // - End: Move to the last item. 27 // - Page down: Move down one page. 28 // - Page up: Move up one page. 29 // - Enter / Space: Select the current item. 30 // - Right / left: Scroll horizontally. Only if the list is wider than the 31 // available space. 32 // 33 // By default, list item texts can contain style tags. Use 34 // [List.SetUseStyleTags] to disable this feature. 35 // 36 // See [List.SetChangedFunc] for a way to be notified when the user navigates 37 // to a list item. See [List.SetSelectedFunc] for a way to be notified when a 38 // list item was selected. 39 // 40 // See https://github.com/rivo/tview/wiki/List for an example. 41 type List struct { 42 *Box 43 44 // The items of the list. 45 items []*listItem 46 47 // The index of the currently selected item. 48 currentItem int 49 50 // Whether or not to show the secondary item texts. 51 showSecondaryText bool 52 53 // The item main text style. 54 mainTextStyle tcell.Style 55 56 // The item secondary text style. 57 secondaryTextStyle tcell.Style 58 59 // The item shortcut text style. 60 shortcutStyle tcell.Style 61 62 // The style for selected items. 63 selectedStyle tcell.Style 64 65 // If true, the selection is only shown when the list has focus. 66 selectedFocusOnly bool 67 68 // If true, the entire row is highlighted when selected. 69 highlightFullLine bool 70 71 // Whether or not style tags can be used in the main text. 72 mainStyleTags bool 73 74 // Whether or not style tags can be used in the secondary text. 75 secondaryStyleTags bool 76 77 // Whether or not navigating the list will wrap around. 78 wrapAround bool 79 80 // The number of list items skipped at the top before the first item is 81 // drawn. 82 itemOffset int 83 84 // The number of cells skipped on the left side of an item text. Shortcuts 85 // are not affected. 86 horizontalOffset int 87 88 // An optional function which is called when the user has navigated to a 89 // list item. 90 changed func(index int, mainText, secondaryText string, shortcut rune) 91 92 // An optional function which is called when a list item was selected. This 93 // function will be called even if the list item defines its own callback. 94 selected func(index int, mainText, secondaryText string, shortcut rune) 95 96 // An optional function which is called when the user presses the Escape key. 97 done func() 98 } 99 100 // NewList returns a new list. 101 func NewList() *List { 102 return &List{ 103 Box: NewBox(), 104 showSecondaryText: true, 105 wrapAround: true, 106 mainTextStyle: tcell.StyleDefault.Foreground(Styles.PrimaryTextColor).Background(Styles.PrimitiveBackgroundColor), 107 secondaryTextStyle: tcell.StyleDefault.Foreground(Styles.TertiaryTextColor).Background(Styles.PrimitiveBackgroundColor), 108 shortcutStyle: tcell.StyleDefault.Foreground(Styles.SecondaryTextColor).Background(Styles.PrimitiveBackgroundColor), 109 selectedStyle: tcell.StyleDefault.Foreground(Styles.PrimitiveBackgroundColor).Background(Styles.PrimaryTextColor), 110 mainStyleTags: true, 111 secondaryStyleTags: true, 112 } 113 } 114 115 // SetCurrentItem sets the currently selected item by its index, starting at 0 116 // for the first item. If a negative index is provided, items are referred to 117 // from the back (-1 = last item, -2 = second-to-last item, and so on). Out of 118 // range indices are clamped to the beginning/end. 119 // 120 // Calling this function triggers a "changed" event if the selection changes. 121 func (l *List) SetCurrentItem(index int) *List { 122 if index < 0 { 123 index = len(l.items) + index 124 } 125 if index >= len(l.items) { 126 index = len(l.items) - 1 127 } 128 if index < 0 { 129 index = 0 130 } 131 132 if index != l.currentItem && l.changed != nil { 133 item := l.items[index] 134 l.changed(index, item.MainText, item.SecondaryText, item.Shortcut) 135 } 136 137 l.currentItem = index 138 139 return l 140 } 141 142 // GetCurrentItem returns the index of the currently selected list item, 143 // starting at 0 for the first item. 144 func (l *List) GetCurrentItem() int { 145 return l.currentItem 146 } 147 148 // SetOffset sets the number of items to be skipped (vertically) as well as the 149 // number of cells skipped horizontally when the list is drawn. Note that one 150 // item corresponds to two rows when there are secondary texts. Shortcuts are 151 // always drawn. 152 // 153 // These values may change when the list is drawn to ensure the currently 154 // selected item is visible and item texts move out of view. Users can also 155 // modify these values by interacting with the list. 156 func (l *List) SetOffset(items, horizontal int) *List { 157 l.itemOffset = items 158 l.horizontalOffset = horizontal 159 return l 160 } 161 162 // GetOffset returns the number of items skipped while drawing, as well as the 163 // number of cells item text is moved to the left. See also SetOffset() for more 164 // information on these values. 165 func (l *List) GetOffset() (int, int) { 166 return l.itemOffset, l.horizontalOffset 167 } 168 169 // RemoveItem removes the item with the given index (starting at 0) from the 170 // list. If a negative index is provided, items are referred to from the back 171 // (-1 = last item, -2 = second-to-last item, and so on). Out of range indices 172 // are clamped to the beginning/end, i.e. unless the list is empty, an item is 173 // always removed. 174 // 175 // The currently selected item is shifted accordingly. If it is the one that is 176 // removed, a "changed" event is fired, unless no items are left. 177 func (l *List) RemoveItem(index int) *List { 178 if len(l.items) == 0 { 179 return l 180 } 181 182 // Adjust index. 183 if index < 0 { 184 index = len(l.items) + index 185 } 186 if index >= len(l.items) { 187 index = len(l.items) - 1 188 } 189 if index < 0 { 190 index = 0 191 } 192 193 // Remove item. 194 l.items = append(l.items[:index], l.items[index+1:]...) 195 196 // If there is nothing left, we're done. 197 if len(l.items) == 0 { 198 return l 199 } 200 201 // Shift current item. 202 previousCurrentItem := l.currentItem 203 if l.currentItem > index || l.currentItem == len(l.items) { 204 l.currentItem-- 205 } 206 207 // Fire "changed" event for removed items. 208 if previousCurrentItem == index && l.changed != nil { 209 item := l.items[l.currentItem] 210 l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) 211 } 212 213 return l 214 } 215 216 // SetMainTextColor sets the color of the items' main text. 217 func (l *List) SetMainTextColor(color tcell.Color) *List { 218 l.mainTextStyle = l.mainTextStyle.Foreground(color) 219 return l 220 } 221 222 // SetMainTextStyle sets the style of the items' main text. Note that the 223 // background color is ignored in order not to override the background color of 224 // the list itself. 225 func (l *List) SetMainTextStyle(style tcell.Style) *List { 226 l.mainTextStyle = style 227 return l 228 } 229 230 // SetSecondaryTextColor sets the color of the items' secondary text. 231 func (l *List) SetSecondaryTextColor(color tcell.Color) *List { 232 l.secondaryTextStyle = l.secondaryTextStyle.Foreground(color) 233 return l 234 } 235 236 // SetSecondaryTextStyle sets the style of the items' secondary text. Note that 237 // the background color is ignored in order not to override the background color 238 // of the list itself. 239 func (l *List) SetSecondaryTextStyle(style tcell.Style) *List { 240 l.secondaryTextStyle = style 241 return l 242 } 243 244 // SetShortcutColor sets the color of the items' shortcut. 245 func (l *List) SetShortcutColor(color tcell.Color) *List { 246 l.shortcutStyle = l.shortcutStyle.Foreground(color) 247 return l 248 } 249 250 // SetShortcutStyle sets the style of the items' shortcut. Note that the 251 // background color is ignored in order not to override the background color of 252 // the list itself. 253 func (l *List) SetShortcutStyle(style tcell.Style) *List { 254 l.shortcutStyle = style 255 return l 256 } 257 258 // SetSelectedTextColor sets the text color of selected items. Note that the 259 // color of main text characters that are different from the main text color 260 // (e.g. style tags) is maintained. 261 func (l *List) SetSelectedTextColor(color tcell.Color) *List { 262 l.selectedStyle = l.selectedStyle.Foreground(color) 263 return l 264 } 265 266 // SetSelectedBackgroundColor sets the background color of selected items. 267 func (l *List) SetSelectedBackgroundColor(color tcell.Color) *List { 268 l.selectedStyle = l.selectedStyle.Background(color) 269 return l 270 } 271 272 // SetSelectedStyle sets the style of the selected items. Note that the color of 273 // main text characters that are different from the main text color (e.g. color 274 // tags) is maintained. 275 func (l *List) SetSelectedStyle(style tcell.Style) *List { 276 l.selectedStyle = style 277 return l 278 } 279 280 // SetUseStyleTags sets a flag which determines whether style tags are used in 281 // the main and secondary texts. The default is true. 282 func (l *List) SetUseStyleTags(mainStyleTags, secondaryStyleTags bool) *List { 283 l.mainStyleTags = mainStyleTags 284 l.secondaryStyleTags = secondaryStyleTags 285 return l 286 } 287 288 // GetUseStyleTags returns whether style tags are used in the main and secondary 289 // texts. 290 func (l *List) GetUseStyleTags() (mainStyleTags, secondaryStyleTags bool) { 291 return l.mainStyleTags, l.secondaryStyleTags 292 } 293 294 // SetSelectedFocusOnly sets a flag which determines when the currently selected 295 // list item is highlighted. If set to true, selected items are only highlighted 296 // when the list has focus. If set to false, they are always highlighted. 297 func (l *List) SetSelectedFocusOnly(focusOnly bool) *List { 298 l.selectedFocusOnly = focusOnly 299 return l 300 } 301 302 // SetHighlightFullLine sets a flag which determines whether the colored 303 // background of selected items spans the entire width of the view. If set to 304 // true, the highlight spans the entire view. If set to false, only the text of 305 // the selected item from beginning to end is highlighted. 306 func (l *List) SetHighlightFullLine(highlight bool) *List { 307 l.highlightFullLine = highlight 308 return l 309 } 310 311 // ShowSecondaryText determines whether or not to show secondary item texts. 312 func (l *List) ShowSecondaryText(show bool) *List { 313 l.showSecondaryText = show 314 return l 315 } 316 317 // SetWrapAround sets the flag that determines whether navigating the list will 318 // wrap around. That is, navigating downwards on the last item will move the 319 // selection to the first item (similarly in the other direction). If set to 320 // false, the selection won't change when navigating downwards on the last item 321 // or navigating upwards on the first item. 322 func (l *List) SetWrapAround(wrapAround bool) *List { 323 l.wrapAround = wrapAround 324 return l 325 } 326 327 // SetChangedFunc sets the function which is called when the user navigates to 328 // a list item. The function receives the item's index in the list of items 329 // (starting with 0), its main text, secondary text, and its shortcut rune. 330 // 331 // This function is also called when the first item is added or when 332 // SetCurrentItem() is called. 333 func (l *List) SetChangedFunc(handler func(index int, mainText string, secondaryText string, shortcut rune)) *List { 334 l.changed = handler 335 return l 336 } 337 338 // SetSelectedFunc sets the function which is called when the user selects a 339 // list item by pressing Enter on the current selection. The function receives 340 // the item's index in the list of items (starting with 0), its main text, 341 // secondary text, and its shortcut rune. 342 func (l *List) SetSelectedFunc(handler func(int, string, string, rune)) *List { 343 l.selected = handler 344 return l 345 } 346 347 // GetSelectedFunc returns the function set with [List.SetSelectedFunc] or nil 348 // if no such function was set. 349 func (l *List) GetSelectedFunc() func(int, string, string, rune) { 350 return l.selected 351 } 352 353 // SetDoneFunc sets a function which is called when the user presses the Escape 354 // key. 355 func (l *List) SetDoneFunc(handler func()) *List { 356 l.done = handler 357 return l 358 } 359 360 // AddItem calls [List.InsertItem] with an index of -1. 361 func (l *List) AddItem(mainText, secondaryText string, shortcut rune, selected func()) *List { 362 l.InsertItem(-1, mainText, secondaryText, shortcut, selected) 363 return l 364 } 365 366 // InsertItem adds a new item to the list at the specified index. An index of 0 367 // will insert the item at the beginning, an index of 1 before the second item, 368 // and so on. An index of [List.GetItemCount] or higher will insert the item at 369 // the end of the list. Negative indices are also allowed: An index of -1 will 370 // insert the item at the end of the list, an index of -2 before the last item, 371 // and so on. An index of -GetItemCount()-1 or lower will insert the item at the 372 // beginning. 373 // 374 // An item has a main text which will be highlighted when selected. It also has 375 // a secondary text which is shown underneath the main text (if it is set to 376 // visible) but which may remain empty. 377 // 378 // The shortcut is a key binding. If the specified rune is entered, the item 379 // is selected immediately. Set to 0 for no binding. 380 // 381 // The "selected" callback will be invoked when the user selects the item. You 382 // may provide nil if no such callback is needed or if all events are handled 383 // through the selected callback set with [List.SetSelectedFunc]. 384 // 385 // The currently selected item will shift its position accordingly. If the list 386 // was previously empty, a "changed" event is fired because the new item becomes 387 // selected. 388 func (l *List) InsertItem(index int, mainText, secondaryText string, shortcut rune, selected func()) *List { 389 item := &listItem{ 390 MainText: mainText, 391 SecondaryText: secondaryText, 392 Shortcut: shortcut, 393 Selected: selected, 394 } 395 396 // Shift index to range. 397 if index < 0 { 398 index = len(l.items) + index + 1 399 } 400 if index < 0 { 401 index = 0 402 } else if index > len(l.items) { 403 index = len(l.items) 404 } 405 406 // Shift current item. 407 if l.currentItem < len(l.items) && l.currentItem >= index { 408 l.currentItem++ 409 } 410 411 // Insert item (make space for the new item, then shift and insert). 412 l.items = append(l.items, nil) 413 if index < len(l.items)-1 { // -1 because l.items has already grown by one item. 414 copy(l.items[index+1:], l.items[index:]) 415 } 416 l.items[index] = item 417 418 // Fire a "change" event for the first item in the list. 419 if len(l.items) == 1 && l.changed != nil { 420 item := l.items[0] 421 l.changed(0, item.MainText, item.SecondaryText, item.Shortcut) 422 } 423 424 return l 425 } 426 427 // GetItemCount returns the number of items in the list. 428 func (l *List) GetItemCount() int { 429 return len(l.items) 430 } 431 432 // GetItemSelectedFunc returns the function which is called when the user 433 // selects the item with the given index, if such a function was set. If no 434 // function was set, nil is returned. Panics if the index is out of range. 435 func (l *List) GetItemSelectedFunc(index int) func() { 436 return l.items[index].Selected 437 } 438 439 // GetItemText returns an item's texts (main and secondary). Panics if the index 440 // is out of range. 441 func (l *List) GetItemText(index int) (main, secondary string) { 442 return l.items[index].MainText, l.items[index].SecondaryText 443 } 444 445 // SetItemText sets an item's main and secondary text. Panics if the index is 446 // out of range. 447 func (l *List) SetItemText(index int, main, secondary string) *List { 448 item := l.items[index] 449 item.MainText = main 450 item.SecondaryText = secondary 451 return l 452 } 453 454 // FindItems searches the main and secondary texts for the given strings and 455 // returns a list of item indices in which those strings are found. One of the 456 // two search strings may be empty, it will then be ignored. Indices are always 457 // returned in ascending order. 458 // 459 // If mustContainBoth is set to true, mainSearch must be contained in the main 460 // text AND secondarySearch must be contained in the secondary text. If it is 461 // false, only one of the two search strings must be contained. 462 // 463 // Set ignoreCase to true for case-insensitive search. 464 func (l *List) FindItems(mainSearch, secondarySearch string, mustContainBoth, ignoreCase bool) (indices []int) { 465 if mainSearch == "" && secondarySearch == "" { 466 return 467 } 468 469 if ignoreCase { 470 mainSearch = strings.ToLower(mainSearch) 471 secondarySearch = strings.ToLower(secondarySearch) 472 } 473 474 for index, item := range l.items { 475 mainText := item.MainText 476 secondaryText := item.SecondaryText 477 if ignoreCase { 478 mainText = strings.ToLower(mainText) 479 secondaryText = strings.ToLower(secondaryText) 480 } 481 482 // strings.Contains() always returns true for a "" search. 483 mainContained := strings.Contains(mainText, mainSearch) 484 secondaryContained := strings.Contains(secondaryText, secondarySearch) 485 if mustContainBoth && mainContained && secondaryContained || 486 !mustContainBoth && (mainSearch != "" && mainContained || secondarySearch != "" && secondaryContained) { 487 indices = append(indices, index) 488 } 489 } 490 491 return 492 } 493 494 // Clear removes all items from the list. 495 func (l *List) Clear() *List { 496 l.items = nil 497 l.currentItem = 0 498 return l 499 } 500 501 // Draw draws this primitive onto the screen. 502 func (l *List) Draw(screen tcell.Screen) { 503 l.Box.DrawForSubclass(screen, l) 504 505 // Determine the dimensions. 506 x, y, width, height := l.GetInnerRect() 507 bottomLimit := y + height 508 _, totalHeight := screen.Size() 509 if bottomLimit > totalHeight { 510 bottomLimit = totalHeight 511 } 512 513 // Adjust offsets to keep the current item in view. 514 if height == 0 { 515 return 516 } 517 if l.currentItem < l.itemOffset { 518 l.itemOffset = l.currentItem 519 } else if l.showSecondaryText { 520 if 2*(l.currentItem-l.itemOffset) >= height-1 { 521 l.itemOffset = (2*l.currentItem + 3 - height) / 2 522 } 523 } else { 524 if l.currentItem-l.itemOffset >= height { 525 l.itemOffset = l.currentItem + 1 - height 526 } 527 } 528 if l.horizontalOffset < 0 { 529 l.horizontalOffset = 0 530 } 531 532 // Do we show any shortcuts? 533 var showShortcuts bool 534 for _, item := range l.items { 535 if item.Shortcut != 0 { 536 showShortcuts = true 537 x += 4 538 width -= 4 539 break 540 } 541 } 542 543 // Draw the list items. 544 var maxWidth int // The maximum printed item width. 545 for index, item := range l.items { 546 if index < l.itemOffset { 547 continue 548 } 549 550 if y >= bottomLimit { 551 break 552 } 553 554 // Shortcuts. 555 if showShortcuts && item.Shortcut != 0 { 556 printWithStyle(screen, fmt.Sprintf("(%s)", string(item.Shortcut)), x-5, y, 0, 4, AlignRight, l.shortcutStyle, false) 557 } 558 559 // Main text. 560 selected := index == l.currentItem && (!l.selectedFocusOnly || l.HasFocus()) 561 style := l.mainTextStyle 562 if selected { 563 style = l.selectedStyle 564 } 565 mainText := item.MainText 566 if !l.mainStyleTags { 567 mainText = Escape(mainText) 568 } 569 _, _, printedWidth := printWithStyle(screen, mainText, x, y, l.horizontalOffset, width, AlignLeft, style, false) 570 if printedWidth > maxWidth { 571 maxWidth = printedWidth 572 } 573 574 // Draw until the end of the line if requested. 575 if selected && l.highlightFullLine { 576 for bx := printedWidth; bx < width; bx++ { 577 screen.SetContent(x+bx, y, ' ', nil, style) 578 } 579 } 580 581 y++ 582 if y >= bottomLimit { 583 break 584 } 585 586 // Secondary text. 587 if l.showSecondaryText { 588 secondaryText := item.SecondaryText 589 if !l.secondaryStyleTags { 590 secondaryText = Escape(secondaryText) 591 } 592 _, _, printedWidth := printWithStyle(screen, secondaryText, x, y, l.horizontalOffset, width, AlignLeft, l.secondaryTextStyle, false) 593 if printedWidth > maxWidth { 594 maxWidth = printedWidth 595 } 596 y++ 597 } 598 } 599 600 // We don't want the item text to get out of view. If the horizontal offset 601 // is too high, we reset it and redraw. (That should be about as efficient 602 // as calculating everything up front.) 603 if l.horizontalOffset > 0 && maxWidth < width { 604 l.horizontalOffset -= width - maxWidth 605 l.Draw(screen) 606 } 607 } 608 609 // InputHandler returns the handler for this primitive. 610 func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { 611 return l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { 612 if event.Key() == tcell.KeyEscape { 613 if l.done != nil { 614 l.done() 615 } 616 return 617 } else if len(l.items) == 0 { 618 return 619 } 620 621 previousItem := l.currentItem 622 623 switch key := event.Key(); key { 624 case tcell.KeyTab, tcell.KeyDown: 625 l.currentItem++ 626 case tcell.KeyBacktab, tcell.KeyUp: 627 l.currentItem-- 628 case tcell.KeyRight: 629 l.horizontalOffset += 2 // We shift by 2 to account for two-cell characters. 630 case tcell.KeyLeft: 631 l.horizontalOffset -= 2 632 case tcell.KeyHome: 633 l.currentItem = 0 634 case tcell.KeyEnd: 635 l.currentItem = len(l.items) - 1 636 case tcell.KeyPgDn: 637 _, _, _, height := l.GetInnerRect() 638 l.currentItem += height 639 if l.currentItem >= len(l.items) { 640 l.currentItem = len(l.items) - 1 641 } 642 case tcell.KeyPgUp: 643 _, _, _, height := l.GetInnerRect() 644 l.currentItem -= height 645 if l.currentItem < 0 { 646 l.currentItem = 0 647 } 648 case tcell.KeyEnter: 649 if l.currentItem >= 0 && l.currentItem < len(l.items) { 650 item := l.items[l.currentItem] 651 if item.Selected != nil { 652 item.Selected() 653 } 654 if l.selected != nil { 655 l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) 656 } 657 } 658 case tcell.KeyRune: 659 ch := event.Rune() 660 if ch != ' ' { 661 // It's not a space bar. Is it a shortcut? 662 var found bool 663 for index, item := range l.items { 664 if item.Shortcut == ch { 665 // We have a shortcut. 666 found = true 667 l.currentItem = index 668 break 669 } 670 } 671 if !found { 672 break 673 } 674 } 675 item := l.items[l.currentItem] 676 if item.Selected != nil { 677 item.Selected() 678 } 679 if l.selected != nil { 680 l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) 681 } 682 } 683 684 if l.currentItem < 0 { 685 if l.wrapAround { 686 l.currentItem = len(l.items) - 1 687 } else { 688 l.currentItem = 0 689 } 690 } else if l.currentItem >= len(l.items) { 691 if l.wrapAround { 692 l.currentItem = 0 693 } else { 694 l.currentItem = len(l.items) - 1 695 } 696 } 697 698 if l.currentItem != previousItem && l.currentItem < len(l.items) { 699 if l.changed != nil { 700 item := l.items[l.currentItem] 701 l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) 702 } 703 } 704 }) 705 } 706 707 // indexAtPoint returns the index of the list item found at the given position 708 // or a negative value if there is no such list item. 709 func (l *List) indexAtPoint(x, y int) int { 710 rectX, rectY, width, height := l.GetInnerRect() 711 if rectX < 0 || rectX >= rectX+width || y < rectY || y >= rectY+height { 712 return -1 713 } 714 715 index := y - rectY 716 if l.showSecondaryText { 717 index /= 2 718 } 719 index += l.itemOffset 720 721 if index >= len(l.items) { 722 return -1 723 } 724 return index 725 } 726 727 // MouseHandler returns the mouse handler for this primitive. 728 func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) { 729 return l.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) { 730 if !l.InRect(event.Position()) { 731 return false, nil 732 } 733 734 // Process mouse event. 735 switch action { 736 case MouseLeftClick: 737 setFocus(l) 738 index := l.indexAtPoint(event.Position()) 739 if index != -1 { 740 item := l.items[index] 741 if item.Selected != nil { 742 item.Selected() 743 } 744 if l.selected != nil { 745 l.selected(index, item.MainText, item.SecondaryText, item.Shortcut) 746 } 747 if index != l.currentItem { 748 if l.changed != nil { 749 l.changed(index, item.MainText, item.SecondaryText, item.Shortcut) 750 } 751 } 752 l.currentItem = index 753 } 754 consumed = true 755 case MouseScrollUp: 756 if l.itemOffset > 0 { 757 l.itemOffset-- 758 } 759 consumed = true 760 case MouseScrollDown: 761 lines := len(l.items) - l.itemOffset 762 if l.showSecondaryText { 763 lines *= 2 764 } 765 if _, _, _, height := l.GetInnerRect(); lines > height { 766 l.itemOffset++ 767 } 768 consumed = true 769 case MouseScrollLeft: 770 l.horizontalOffset-- 771 consumed = true 772 case MouseScrollRight: 773 l.horizontalOffset++ 774 consumed = true 775 } 776 777 return 778 }) 779 }