package cview

import (
	
	
	
	

	
)

// ListItem represents an item in a List.
type ListItem struct {
	disabled      bool        // Whether or not the list item is selectable.
	mainText      []byte      // The main text of the list item.
	secondaryText []byte      // A secondary text to be shown underneath the main text.
	shortcut      rune        // The key to select the list item directly, 0 if there is no shortcut.
	selected      func()      // The optional function which is called when the item is selected.
	reference     interface{} // An optional reference object.

	sync.RWMutex
}

// NewListItem returns a new item for a list.
func ( string) *ListItem {
	return &ListItem{
		mainText: []byte(),
	}
}

// SetMainBytes sets the main text of the list item.
func ( *ListItem) ( []byte) {
	.Lock()
	defer .Unlock()

	.mainText = 
}

// SetMainText sets the main text of the list item.
func ( *ListItem) ( string) {
	.SetMainBytes([]byte())
}

// GetMainBytes returns the item's main text.
func ( *ListItem) () []byte {
	.RLock()
	defer .RUnlock()

	return .mainText
}

// GetMainText returns the item's main text.
func ( *ListItem) () string {
	return string(.GetMainBytes())
}

// SetSecondaryBytes sets a secondary text to be shown underneath the main text.
func ( *ListItem) ( []byte) {
	.Lock()
	defer .Unlock()

	.secondaryText = 
}

// SetSecondaryText sets a secondary text to be shown underneath the main text.
func ( *ListItem) ( string) {
	.SetSecondaryBytes([]byte())
}

// GetSecondaryBytes returns the item's secondary text.
func ( *ListItem) () []byte {
	.RLock()
	defer .RUnlock()

	return .secondaryText
}

// GetSecondaryText returns the item's secondary text.
func ( *ListItem) () string {
	return string(.GetSecondaryBytes())
}

// SetShortcut sets the key to select the ListItem directly, 0 if there is no shortcut.
func ( *ListItem) ( rune) {
	.Lock()
	defer .Unlock()

	.shortcut = 
}

// GetShortcut returns the ListItem's shortcut.
func ( *ListItem) () rune {
	.RLock()
	defer .RUnlock()

	return .shortcut
}

// SetSelectedFunc sets a function which is called when the ListItem is selected.
func ( *ListItem) ( func()) {
	.Lock()
	defer .Unlock()

	.selected = 
}

// SetReference allows you to store a reference of any type in the item
func ( *ListItem) ( interface{}) {
	.Lock()
	defer .Unlock()

	.reference = 
}

// GetReference returns the item's reference object.
func ( *ListItem) () interface{} {
	.RLock()
	defer .RUnlock()

	return .reference
}

// List displays rows of items, each of which can be selected.
type List struct {
	*Box
	*ContextMenu

	// The items of the list.
	items []*ListItem

	// The index of the currently selected item.
	currentItem int

	// Whether or not to show the secondary item texts.
	showSecondaryText bool

	// The item main text color.
	mainTextColor tcell.Color

	// The item secondary text color.
	secondaryTextColor tcell.Color

	// The item shortcut text color.
	shortcutColor tcell.Color

	// The text color for selected items.
	selectedTextColor tcell.Color

	// The style attributes for selected items.
	selectedTextAttributes tcell.AttrMask

	// Visibility of the scroll bar.
	scrollBarVisibility ScrollBarVisibility

	// The scroll bar color.
	scrollBarColor tcell.Color

	// The background color for selected items.
	selectedBackgroundColor tcell.Color

	// If true, the selection is only shown when the list has focus.
	selectedFocusOnly bool

	// If true, the selection must remain visible when scrolling.
	selectedAlwaysVisible bool

	// If true, the selection must remain centered when scrolling.
	selectedAlwaysCentered bool

	// If true, the entire row is highlighted when selected.
	highlightFullLine bool

	// Whether or not navigating the list will wrap around.
	wrapAround bool

	// Whether or not hovering over an item will highlight it.
	hover bool

	// The number of list items and columns by which the list is scrolled
	// down/to the right.
	itemOffset, columnOffset int

	// An optional function which is called when the user has navigated to a list
	// item.
	changed func(index int, item *ListItem)

	// An optional function which is called when a list item was selected. This
	// function will be called even if the list item defines its own callback.
	selected func(index int, item *ListItem)

	// An optional function which is called when the user presses the Escape key.
	done func()

	// The height of the list the last time it was drawn.
	height int

	// Prefix and suffix strings drawn for unselected elements.
	unselectedPrefix, unselectedSuffix []byte

	// Prefix and suffix strings drawn for selected elements.
	selectedPrefix, selectedSuffix []byte

	// Maximum prefix and suffix width.
	prefixWidth, suffixWidth int

	sync.RWMutex
}

// NewList returns a new form.
func () *List {
	 := &List{
		Box:                     NewBox(),
		showSecondaryText:       true,
		scrollBarVisibility:     ScrollBarAuto,
		mainTextColor:           Styles.PrimaryTextColor,
		secondaryTextColor:      Styles.TertiaryTextColor,
		shortcutColor:           Styles.SecondaryTextColor,
		selectedTextColor:       Styles.PrimitiveBackgroundColor,
		scrollBarColor:          Styles.ScrollBarColor,
		selectedBackgroundColor: Styles.PrimaryTextColor,
	}

	.ContextMenu = NewContextMenu()
	.focus = 

	return 
}

// SetCurrentItem sets the currently selected item by its index, starting at 0
// for the first item. If a negative index is provided, items are referred to
// from the back (-1 = last item, -2 = second-to-last item, and so on). Out of
// range indices are clamped to the beginning/end.
//
// Calling this function triggers a "changed" event if the selection changes.
func ( *List) ( int) {
	// TODO deadlock
	.Lock()

	if  < 0 {
		 = len(.items) + 
	}
	if  >= len(.items) {
		 = len(.items) - 1
	}
	if  < 0 {
		 = 0
	}

	 := .currentItem
	.currentItem = 

	.updateOffset()

	if  !=  &&  < len(.items) && .changed != nil {
		 := .items[]
		.Unlock()
		.changed(, )
	} else {
		.Unlock()
	}
}

// GetCurrentItem returns the currently selected list item,
// Returns nil if no item is selected.
func ( *List) () *ListItem {
	.RLock()
	defer .RUnlock()

	if len(.items) == 0 || .currentItem >= len(.items) {
		return nil
	}
	return .items[.currentItem]
}

// GetCurrentItemIndex returns the index of the currently selected list item,
// starting at 0 for the first item and its struct.
func ( *List) () int {
	.RLock()
	defer .RUnlock()
	return .currentItem
}

// GetItems returns all list items.
func ( *List) () []*ListItem {
	.RLock()
	defer .RUnlock()
	return .items
}

// RemoveItem removes the item with the given index (starting at 0) from the
// list. If a negative index is provided, items are referred to from the back
// (-1 = last item, -2 = second-to-last item, and so on). Out of range indices
// are clamped to the beginning/end, i.e. unless the list is empty, an item is
// always removed.
//
// The currently selected item is shifted accordingly. If it is the one that is
// removed, a "changed" event is fired.
func ( *List) ( int) {
	.Lock()

	if len(.items) == 0 {
		.Unlock()
		return
	}

	// Adjust index.
	if  < 0 {
		 = len(.items) + 
	}
	if  >= len(.items) {
		 = len(.items) - 1
	}
	if  < 0 {
		 = 0
	}

	// Remove item.
	.items = append(.items[:], .items[+1:]...)

	// If there is nothing left, we're done.
	if len(.items) == 0 {
		.Unlock()
		return
	}

	// Shift current item.
	 := .currentItem
	if .currentItem >=  && .currentItem > 0 {
		.currentItem--
	}

	// Fire "changed" event for removed items.
	if  ==  &&  < len(.items) && .changed != nil {
		 := .items[.currentItem]
		.Unlock()
		.changed(.currentItem, )
	} else {
		.Unlock()
	}
}

// SetOffset sets the number of list items and columns by which the list is
// scrolled down/to the right.
func ( *List) (,  int) {
	.Lock()
	defer .Unlock()

	if  < 0 {
		 = 0
	}
	if  < 0 {
		 = 0
	}

	.itemOffset, .columnOffset = , 
}

// GetOffset returns the number of list items and columns by which the list is
// scrolled down/to the right.
func ( *List) () (int, int) {
	.Lock()
	defer .Unlock()

	return .itemOffset, .columnOffset
}

// SetMainTextColor sets the color of the items' main text.
func ( *List) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.mainTextColor = 
}

// SetSecondaryTextColor sets the color of the items' secondary text.
func ( *List) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.secondaryTextColor = 
}

// SetShortcutColor sets the color of the items' shortcut.
func ( *List) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.shortcutColor = 
}

// SetSelectedTextColor sets the text color of selected items.
func ( *List) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.selectedTextColor = 
}

// SetSelectedTextAttributes sets the style attributes of selected items.
func ( *List) ( tcell.AttrMask) {
	.Lock()
	defer .Unlock()

	.selectedTextAttributes = 
}

// SetSelectedBackgroundColor sets the background color of selected items.
func ( *List) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.selectedBackgroundColor = 
}

// SetSelectedFocusOnly sets a flag which determines when the currently selected
// list item is highlighted. If set to true, selected items are only highlighted
// when the list has focus. If set to false, they are always highlighted.
func ( *List) ( bool) {
	.Lock()
	defer .Unlock()

	.selectedFocusOnly = 
}

// SetSelectedAlwaysVisible sets a flag which determines whether the currently
// selected list item must remain visible when scrolling.
func ( *List) ( bool) {
	.Lock()
	defer .Unlock()

	.selectedAlwaysVisible = 
}

// SetSelectedAlwaysCentered sets a flag which determines whether the currently
// selected list item must remain centered when scrolling.
func ( *List) ( bool) {
	.Lock()
	defer .Unlock()

	.selectedAlwaysCentered = 
}

// SetHighlightFullLine sets a flag which determines whether the colored
// background of selected items spans the entire width of the view. If set to
// true, the highlight spans the entire view. If set to false, only the text of
// the selected item from beginning to end is highlighted.
func ( *List) ( bool) {
	.Lock()
	defer .Unlock()

	.highlightFullLine = 
}

// ShowSecondaryText determines whether or not to show secondary item texts.
func ( *List) ( bool) {
	.Lock()
	defer .Unlock()

	.showSecondaryText = 
	return
}

// SetScrollBarVisibility specifies the display of the scroll bar.
func ( *List) ( ScrollBarVisibility) {
	.Lock()
	defer .Unlock()

	.scrollBarVisibility = 
}

// SetScrollBarColor sets the color of the scroll bar.
func ( *List) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.scrollBarColor = 
}

// SetHover sets the flag that determines whether hovering over an item will
// highlight it (without triggering callbacks set with SetSelectedFunc).
func ( *List) ( bool) {
	.Lock()
	defer .Unlock()

	.hover = 
}

// SetWrapAround sets the flag that determines whether navigating the list will
// wrap around. That is, navigating downwards on the last item will move the
// selection to the first item (similarly in the other direction). If set to
// false, the selection won't change when navigating downwards on the last item
// or navigating upwards on the first item.
func ( *List) ( bool) {
	.Lock()
	defer .Unlock()

	.wrapAround = 
}

// SetChangedFunc sets the function which is called when the user navigates to
// a list item. The function receives the item's index in the list of items
// (starting with 0) and the list item.
//
// This function is also called when the first item is added or when
// SetCurrentItem() is called.
func ( *List) ( func( int,  *ListItem)) {
	.Lock()
	defer .Unlock()

	.changed = 
}

// SetSelectedFunc sets the function which is called when the user selects a
// list item by pressing Enter on the current selection. The function receives
// the item's index in the list of items (starting with 0) and its struct.
func ( *List) ( func(int, *ListItem)) {
	.Lock()
	defer .Unlock()

	.selected = 
}

// SetDoneFunc sets a function which is called when the user presses the Escape
// key.
func ( *List) ( func()) {
	.Lock()
	defer .Unlock()

	.done = 
}

// AddItem calls InsertItem() with an index of -1.
func ( *List) ( *ListItem) {
	.InsertItem(-1, )
}

// InsertItem adds a new item to the list at the specified index. An index of 0
// will insert the item at the beginning, an index of 1 before the second item,
// and so on. An index of GetItemCount() or higher will insert the item at the
// end of the list. Negative indices are also allowed: An index of -1 will
// insert the item at the end of the list, an index of -2 before the last item,
// and so on. An index of -GetItemCount()-1 or lower will insert the item at the
// beginning.
//
// An item has a main text which will be highlighted when selected. It also has
// a secondary text which is shown underneath the main text (if it is set to
// visible) but which may remain empty.
//
// The shortcut is a key binding. If the specified rune is entered, the item
// is selected immediately. Set to 0 for no binding.
//
// The "selected" callback will be invoked when the user selects the item. You
// may provide nil if no such callback is needed or if all events are handled
// through the selected callback set with SetSelectedFunc().
//
// The currently selected item will shift its position accordingly. If the list
// was previously empty, a "changed" event is fired because the new item becomes
// selected.
func ( *List) ( int,  *ListItem) {
	.Lock()

	// Shift index to range.
	if  < 0 {
		 = len(.items) +  + 1
	}
	if  < 0 {
		 = 0
	} else if  > len(.items) {
		 = len(.items)
	}

	// Shift current item.
	if .currentItem < len(.items) && .currentItem >=  {
		.currentItem++
	}

	// Insert item (make space for the new item, then shift and insert).
	.items = append(.items, nil)
	if  < len(.items)-1 { // -1 because l.items has already grown by one item.
		copy(.items[+1:], .items[:])
	}
	.items[] = 

	// Fire a "change" event for the first item in the list.
	if len(.items) == 1 && .changed != nil {
		 := .items[0]
		.Unlock()
		.changed(0, )
	} else {
		.Unlock()
	}
}

// GetItem returns the ListItem at the given index.
// Returns nil when index is out of bounds.
func ( *List) ( int) *ListItem {
	if  > len(.items)-1 {
		return nil
	}
	return .items[]
}

// GetItemCount returns the number of items in the list.
func ( *List) () int {
	.RLock()
	defer .RUnlock()

	return len(.items)
}

// GetItemText returns an item's texts (main and secondary). Panics if the index
// is out of range.
func ( *List) ( int) (,  string) {
	.RLock()
	defer .RUnlock()
	return string(.items[].mainText), string(.items[].secondaryText)
}

// SetItemText sets an item's main and secondary text. Panics if the index is
// out of range.
func ( *List) ( int, ,  string) {
	.Lock()
	defer .Unlock()

	 := .items[]
	.mainText = []byte()
	.secondaryText = []byte()
}

// SetItemEnabled sets whether an item is selectable. Panics if the index is
// out of range.
func ( *List) ( int,  bool) {
	.Lock()
	defer .Unlock()

	 := .items[]
	.disabled = !
}

// SetIndicators is used to set prefix and suffix indicators for selected and unselected items.
func ( *List) (, , ,  string) {
	.Lock()
	defer .Unlock()
	.selectedPrefix = []byte()
	.selectedSuffix = []byte()
	.unselectedPrefix = []byte()
	.unselectedSuffix = []byte()
	.prefixWidth = len()
	if len() > .prefixWidth {
		.prefixWidth = len()
	}
	.suffixWidth = len()
	if len() > .suffixWidth {
		.suffixWidth = len()
	}
}

// FindItems searches the main and secondary texts for the given strings and
// returns a list of item indices in which those strings are found. One of the
// two search strings may be empty, it will then be ignored. Indices are always
// returned in ascending order.
//
// If mustContainBoth is set to true, mainSearch must be contained in the main
// text AND secondarySearch must be contained in the secondary text. If it is
// false, only one of the two search strings must be contained.
//
// Set ignoreCase to true for case-insensitive search.
func ( *List) (,  string, ,  bool) ( []int) {
	.RLock()
	defer .RUnlock()

	if  == "" &&  == "" {
		return
	}

	if  {
		 = strings.ToLower()
		 = strings.ToLower()
	}

	 := []byte()
	 := []byte()

	for ,  := range .items {
		 := .mainText
		 := .secondaryText
		if  {
			 = bytes.ToLower()
			 = bytes.ToLower()
		}

		// strings.Contains() always returns true for a "" search.
		 := bytes.Contains(, )
		 := bytes.Contains(, )
		if  &&  &&  ||
			! && (len() > 0 &&  || len() > 0 && ) {
			 = append(, )
		}
	}

	return
}

// Clear removes all items from the list.
func ( *List) () {
	.Lock()
	defer .Unlock()

	.items = nil
	.currentItem = 0
	.itemOffset = 0
	.columnOffset = 0
}

// Focus is called by the application when the primitive receives focus.
func ( *List) ( func( Primitive)) {
	.Box.Focus()
	if .ContextMenu.open {
		(.ContextMenu.list)
	}
}

// HasFocus returns whether or not this primitive has focus.
func ( *List) () bool {
	if .ContextMenu.open {
		return .ContextMenu.list.()
	}

	.RLock()
	defer .RUnlock()
	return .hasFocus
}

// Transform modifies the current selection.
func ( *List) ( Transformation) {
	.Lock()

	 := .currentItem

	.transform()

	if .currentItem !=  && .currentItem < len(.items) && .changed != nil {
		 := .items[.currentItem]
		.Unlock()
		.changed(.currentItem, )
	} else {
		.Unlock()
	}
}

func ( *List) ( Transformation) {
	var  bool

	 := .height
	if .showSecondaryText {
		 /= 2
	}
	if  < 1 {
		 = 1
	}

	switch  {
	case TransformFirstItem:
		.currentItem = 0
		.itemOffset = 0
		 = true
	case TransformLastItem:
		.currentItem = len(.items) - 1
	case TransformPreviousItem:
		.currentItem--
		 = true
	case TransformNextItem:
		.currentItem++
	case TransformPreviousPage:
		.currentItem -= 
		 = true
	case TransformNextPage:
		.currentItem += 
		.itemOffset += 
	}

	for  := 0;  < len(.items); ++ {
		if .currentItem < 0 {
			if .wrapAround {
				.currentItem = len(.items) - 1
			} else {
				.currentItem = 0
				.itemOffset = 0
			}
		} else if .currentItem >= len(.items) {
			if .wrapAround {
				.currentItem = 0
				.itemOffset = 0
			} else {
				.currentItem = len(.items) - 1
			}
		}

		 := .items[.currentItem]
		.RLock()
		if !.disabled && (.shortcut > 0 || len(.mainText) > 0 || len(.secondaryText) > 0) {

			.RUnlock()
			break
		}

		if  {
			.currentItem--
		} else {
			.currentItem++
		}

		.RUnlock()
	}

	.updateOffset()
}

func ( *List) () {
	_, _, _, .height = .GetInnerRect()

	 := .height
	if .selectedAlwaysCentered {
		 /= 2
	}

	if .currentItem < .itemOffset {
		.itemOffset = .currentItem
	} else if .showSecondaryText {
		if 2*(.currentItem-.itemOffset) >= -1 {
			.itemOffset = (2*.currentItem + 3 - ) / 2
		}
	} else {
		if .currentItem-.itemOffset >=  {
			.itemOffset = .currentItem + 1 - 
		}
	}

	if .showSecondaryText {
		if .itemOffset > len(.items)-(.height/2) {
			.itemOffset = len(.items) - .height/2
		}
	} else {
		if .itemOffset > len(.items)-.height {
			.itemOffset = len(.items) - .height
		}
	}

	if .itemOffset < 0 {
		.itemOffset = 0
	}

	// Maximum width of item text
	 := 0
	for ,  := range .items {
		 := TaggedTextWidth(.mainText)
		 := TaggedTextWidth(.secondaryText)
		if  >  {
			 = 
		}
		if .shortcut != 0 {
			 += 4
		}

		if  >  {
			 = 
		}
	}

	// Additional width for scroll bar
	 := 0
	if .scrollBarVisibility == ScrollBarAlways ||
		(.scrollBarVisibility == ScrollBarAuto &&
			((!.showSecondaryText && len(.items) > .innerHeight) ||
				(.showSecondaryText && len(.items) > .innerHeight/2))) {
		 = 1
	}

	if .columnOffset > (-.innerWidth)+ {
		.columnOffset = ( - .innerWidth) + 
	}
	if .columnOffset < 0 {
		.columnOffset = 0
	}
}

// Draw draws this primitive onto the screen.
func ( *List) ( tcell.Screen) {
	if !.GetVisible() {
		return
	}

	.Box.Draw()
	 := .GetFocusable().HasFocus()

	.Lock()
	defer .Unlock()

	// Determine the dimensions.
	, , ,  := .GetInnerRect()
	 := 
	 :=  + .paddingLeft + .paddingRight + .prefixWidth + .suffixWidth
	 :=  + 

	.height = 

	,  := .Size()
	 := 
	 :=  + ( - 1) + .paddingLeft + .paddingRight
	if  > -1 {
		 =  - 1
	}

	// Halve scroll bar height when drawing two lines per list item.
	if .showSecondaryText {
		 /= 2
	}

	// Do we show any shortcuts?
	var  bool
	for ,  := range .items {
		if .shortcut != 0 {
			 = true
			 += 4
			 -= 4
			break
		}
	}

	// Adjust offset to keep the current selection in view.
	if .selectedAlwaysVisible || .selectedAlwaysCentered {
		.updateOffset()
	}

	 := int(float64(len(.items)) * (float64(.itemOffset) / float64(len(.items)-)))

	// Draw the list items.
	for ,  := range .items {
		if  < .itemOffset {
			continue
		}

		if  >=  {
			break
		}

		 := .mainText
		 := .secondaryText
		if .columnOffset > 0 {
			if .columnOffset < len() {
				 = [.columnOffset:]
			} else {
				 = nil
			}
			if .columnOffset < len() {
				 = [.columnOffset:]
			} else {
				 = nil
			}
		}

		if len(.mainText) == 0 && len(.secondaryText) == 0 && .shortcut == 0 { // Divider
			Print(, []byte(string(tcell.RuneLTee)), -2, , 1, AlignLeft, .mainTextColor)
			Print(, bytes.Repeat([]byte(string(tcell.RuneHLine)), ), -1, , , AlignLeft, .mainTextColor)
			Print(, []byte(string(tcell.RuneRTee)), +-1, , 1, AlignLeft, .mainTextColor)

			RenderScrollBar(, .scrollBarVisibility, , , , len(.items), , -.itemOffset, .hasFocus, .scrollBarColor)
			++
			continue
		}

		if  == .currentItem {
			if len(.selectedPrefix) > 0 {
				 = append(.selectedPrefix, ...)
			}
			if len(.selectedSuffix) > 0 {
				 = append(, .selectedSuffix...)
			}

		} else {
			if len(.unselectedPrefix) > 0 {
				 = append(.unselectedPrefix, ...)
			}
			if len(.unselectedSuffix) > 0 {
				 = append(, .unselectedSuffix...)
			}
		}
		if .disabled {
			// Shortcuts.
			if  && .shortcut != 0 {
				Print(, []byte(fmt.Sprintf("(%c)", .shortcut)), -5, , 4, AlignRight, tcell.ColorDarkSlateGray.TrueColor())
			}

			// Main text.
			Print(, , , , , AlignLeft, tcell.ColorGray.TrueColor())

			RenderScrollBar(, .scrollBarVisibility, , , , len(.items), , -.itemOffset, .hasFocus, .scrollBarColor)
			++
			continue
		}

		// Shortcuts.
		if  && .shortcut != 0 {
			Print(, []byte(fmt.Sprintf("(%c)", .shortcut)), -5, , 4, AlignRight, .shortcutColor)
		}

		// Main text.
		Print(, , , , , AlignLeft, .mainTextColor)

		// Background color of selected text.
		if  == .currentItem && (!.selectedFocusOnly || ) {
			 := 
			if !.highlightFullLine {
				if  := TaggedTextWidth();  <  {
					 = 
				}
			}

			for  := 0;  < ; ++ {
				, , ,  := .GetContent(+, )
				, ,  := .Decompose()
				if  == .mainTextColor {
					 = .selectedTextColor
				}
				 = SetAttributes(.Background(.selectedBackgroundColor).Foreground(), .selectedTextAttributes)
				.SetContent(+, , , , )
			}
		}

		RenderScrollBar(, .scrollBarVisibility, , , , len(.items), , -.itemOffset, .hasFocus, .scrollBarColor)

		++

		if  >=  {
			break
		}

		// Secondary text.
		if .showSecondaryText {
			Print(, , , , , AlignLeft, .secondaryTextColor)

			RenderScrollBar(, .scrollBarVisibility, , , , len(.items), , -.itemOffset, .hasFocus, .scrollBarColor)

			++
		}
	}

	// Overdraw scroll bar when necessary.
	for  <  {
		RenderScrollBar(, .scrollBarVisibility, , , , len(.items), , -, .hasFocus, .scrollBarColor)

		++
	}

	// Draw context menu.
	if  && .ContextMenu.open {
		 := .ContextMenuList()

		, , ,  = .GetInnerRect()

		// What's the longest option text?
		 := 0
		for ,  := range .items {
			 := TaggedTextWidth(.mainText)
			if .shortcut != 0 {
				 += 4
			}
			if  >  {
				 = 
			}
		}

		 := len(.items)
		 := 

		// Add space for borders
		 += 2
		 += 2

		 += .paddingLeft + .paddingRight
		 += .paddingTop + .paddingBottom

		,  := .ContextMenu.x, .ContextMenu.y
		if  < 0 ||  < 0 {
			 := 7
			if  {
				 += 4
			}
			 := .currentItem
			if .showSecondaryText {
				 *= 2
			}
			, , ,  := .GetInnerRect()
			,  = +, +
		}

		,  := .Size()
		if + >=  && -2 > - {
			for  := ( + ) - ;  > 0; -- {
				--
				if + <  {
					break
				}
			}
			if  < 0 {
				 = 0
			}
		}
		if + >=  {
			 =  - 
		}

		if .scrollBarVisibility == ScrollBarAlways || (.scrollBarVisibility == ScrollBarAuto && len(.items) > ) {
			++ // Add space for scroll bar
		}

		.SetRect(, , , )
		.()
	}
}

// InputHandler returns the handler for this primitive.
func ( *List) () func( *tcell.EventKey,  func( Primitive)) {
	return .WrapInputHandler(func( *tcell.EventKey,  func( Primitive)) {
		.Lock()

		if HitShortcut(, Keys.Cancel) {
			if .ContextMenu.open {
				.Unlock()

				.ContextMenu.hide()
				return
			}

			if .done != nil {
				.Unlock()
				.done()
			} else {
				.Unlock()
			}
			return
		} else if HitShortcut(, Keys.Select, Keys.Select2) {
			if .currentItem >= 0 && .currentItem < len(.items) {
				 := .items[.currentItem]
				if !.disabled {
					if .selected != nil {
						.Unlock()
						.selected()
						.Lock()
					}
					if .selected != nil {
						.Unlock()
						.selected(.currentItem, )
						.Lock()
					}
				}
			}
		} else if HitShortcut(, Keys.ShowContextMenu) {
			defer .ContextMenu.show(.currentItem, -1, -1, )
		} else if len(.items) == 0 {
			.Unlock()
			return
		}

		if .Key() == tcell.KeyRune {
			 := .Rune()
			if  != ' ' {
				// It's not a space bar. Is it a shortcut?
				for ,  := range .items {
					if !.disabled && .shortcut ==  {
						// We have a shortcut.
						.currentItem = 

						 := .items[.currentItem]
						if .selected != nil {
							.Unlock()
							.selected()
							.Lock()
						}
						if .selected != nil {
							.Unlock()
							.selected(.currentItem, )
							.Lock()
						}

						.Unlock()
						return
					}
				}
			}
		}

		 := .currentItem

		if HitShortcut(, Keys.MoveFirst, Keys.MoveFirst2) {
			.transform(TransformFirstItem)
		} else if HitShortcut(, Keys.MoveLast, Keys.MoveLast2) {
			.transform(TransformLastItem)
		} else if HitShortcut(, Keys.MoveUp, Keys.MoveUp2) {
			.transform(TransformPreviousItem)
		} else if HitShortcut(, Keys.MoveDown, Keys.MoveDown2) {
			.transform(TransformNextItem)
		} else if HitShortcut(, Keys.MoveLeft, Keys.MoveLeft2) {
			.columnOffset--
			.updateOffset()
		} else if HitShortcut(, Keys.MoveRight, Keys.MoveRight2) {
			.columnOffset++
			.updateOffset()
		} else if HitShortcut(, Keys.MovePreviousPage) {
			.transform(TransformPreviousPage)
		} else if HitShortcut(, Keys.MoveNextPage) {
			.transform(TransformNextPage)
		}

		if .currentItem !=  && .currentItem < len(.items) && .changed != nil {
			 := .items[.currentItem]
			.Unlock()
			.changed(.currentItem, )
		} else {
			.Unlock()
		}
	})
}

// indexAtY returns the index of the list item found at the given Y position
// or a negative value if there is no such list item.
func ( *List) ( int) int {
	, , ,  := .GetInnerRect()
	if  <  ||  >= + {
		return -1
	}

	 :=  - 
	if .showSecondaryText {
		 /= 2
	}
	 += .itemOffset

	if  >= len(.items) {
		return -1
	}
	return 
}

// indexAtPoint returns the index of the list item found at the given position
// or a negative value if there is no such list item.
func ( *List) (,  int) int {
	, , ,  := .GetInnerRect()
	if  <  ||  >= + ||  <  ||  >= + {
		return -1
	}

	 :=  - 
	if .showSecondaryText {
		 /= 2
	}
	 += .itemOffset

	if  >= len(.items) {
		return -1
	}
	return 
}

// MouseHandler returns the mouse handler for this primitive.
func ( *List) () func(
	 MouseAction,  *tcell.EventMouse,  func( Primitive)) ( bool,  Primitive) {

	return .WrapMouseHandler(func(
		 MouseAction,  *tcell.EventMouse,  func( Primitive)) ( bool,  Primitive) {

		,  := .Position()
		if !.InRect(, ) {
			return false, nil
		}

		.Lock()

		// Pass events to context menu.
		if .ContextMenuVisible() && .ContextMenuList().InRect(.Position()) {
			defer .ContextMenuList().()(, , )
			 = true
			.Unlock()
			return
		}

		if !.InRect(.Position()) {
			.Unlock()
			return false, nil
		}

		// Process mouse event.
		switch  {
		case MouseLeftClick:
			if .ContextMenuVisible() {
				defer .ContextMenu.hide()
				 = true
				.Unlock()
				return
			}

			.Unlock()
			()
			.Lock()

			 := .indexAtPoint(.Position())
			if  != -1 {
				 := .items[]
				if !.disabled {
					.currentItem = 
					if .selected != nil {
						.Unlock()
						.selected()
						.Lock()
					}
					if .selected != nil {
						.Unlock()
						.selected(, )
						.Lock()
					}
					if  != .currentItem && .changed != nil {
						.Unlock()
						.changed(, )
						.Lock()
					}
				}
			}
			 = true
		case MouseMiddleClick:
			if .ContextMenu.open {
				defer .ContextMenu.hide()
				 = true
				.Unlock()
				return
			}
		case MouseRightDown:
			if len(.ContextMenuList().items) == 0 {
				.Unlock()
				return
			}

			,  := .Position()

			 := .indexAtPoint(.Position())
			if  != -1 {
				 := .items[]
				if !.disabled {
					.currentItem = 
					if  != .currentItem && .changed != nil {
						.Unlock()
						.changed(, )
						.Lock()
					}
				}
			}

			defer .ContextMenu.show(.currentItem, , , )
			.ContextMenu.drag = true
			 = true
		case MouseMove:
			if .hover {
				,  := .Position()
				 := .indexAtY()
				if  >= 0 {
					 := .items[]
					if !.disabled {
						.currentItem = 
					}
				}

				 = true
			}
		case MouseScrollUp:
			if .itemOffset > 0 {
				.itemOffset--
			}
			 = true
		case MouseScrollDown:
			 := len(.items) - .itemOffset
			if .showSecondaryText {
				 *= 2
			}
			if , , ,  := .GetInnerRect();  >  {
				.itemOffset++
			}
			 = true
		}

		.Unlock()
		return
	})
}