diff --git a/Application.go b/Application.go index c19b634..a304eef 100644 --- a/Application.go +++ b/Application.go @@ -45,7 +45,9 @@ func (a *Application) Run() error { // We catch panics to clean up because they mess up the terminal. defer func() { if p := recover(); p != nil { - a.screen.Fini() + if a.screen != nil { + a.screen.Fini() + } panic(p) } }() @@ -59,6 +61,9 @@ func (a *Application) Run() error { // Start event loop. for { + if a.screen == nil { + break + } event := a.screen.PollEvent() if event == nil { break // The screen was finalized. @@ -66,14 +71,16 @@ func (a *Application) Run() error { switch event := event.(type) { case *tcell.EventKey: if event.Key() == tcell.KeyCtrlC { - a.Stop() + a.Stop() // Ctrl-C closes the application. } a.Lock() - p := a.focus + p := a.focus // Pass other key events to the currently focused primitive. a.Unlock() if p != nil { if handler := p.InputHandler(); handler != nil { - handler(event) + handler(event, func(p Primitive) { + a.SetFocus(p) + }) a.Draw() } } @@ -93,7 +100,11 @@ func (a *Application) Run() error { // Stop stops the application, causing Run() to return. func (a *Application) Stop() { + if a.screen == nil { + return + } a.screen.Fini() + a.screen = nil } // Draw refreshes the screen. It calls the Draw() function of the application's @@ -102,7 +113,7 @@ func (a *Application) Draw() *Application { a.Lock() defer a.Unlock() - // Maybe we're not ready yet. + // Maybe we're not ready yet or not anymore. if a.screen == nil { return a } @@ -151,7 +162,9 @@ func (a *Application) SetFocus(p Primitive) *Application { } a.focus = p a.Unlock() - p.Focus(a) + p.Focus(func(p Primitive) { + a.SetFocus(p) + }) return a } diff --git a/box.go b/box.go index baaa839..a26ee8e 100644 --- a/box.go +++ b/box.go @@ -145,7 +145,7 @@ func (b *Box) SetRect(x, y, width, height int) { } // InputHandler returns nil. -func (b *Box) InputHandler() func(event *tcell.EventKey) { +func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { return nil } @@ -181,7 +181,7 @@ func (b *Box) SetTitleColor(color tcell.Color) *Box { } // Focus is called when this primitive receives focus. -func (b *Box) Focus(app *Application) { +func (b *Box) Focus(delegate func(p Primitive)) { b.hasFocus = true } diff --git a/button.go b/button.go index 1cba3ee..473f847 100644 --- a/button.go +++ b/button.go @@ -118,8 +118,8 @@ func (b *Button) Draw(screen tcell.Screen) { } // InputHandler returns the handler for this primitive. -func (b *Button) InputHandler() func(event *tcell.EventKey) { - return func(event *tcell.EventKey) { +func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return func(event *tcell.EventKey, setFocus func(p Primitive)) { // Process key event. switch key := event.Key(); key { case tcell.KeyEnter: // Selected. diff --git a/demos/basic.go b/demos/basic.go index d782a08..d49802b 100644 --- a/demos/basic.go +++ b/demos/basic.go @@ -10,24 +10,24 @@ func main() { var list *tview.List frame := tview.NewFrame(tview.NewForm(). - AddItem("First name", "", 20, nil). - AddItem("Last name", "", 20, nil). - AddItem("Age", "", 4, nil). + AddInputField("First name", "", 20, nil). + AddInputField("Last name", "", 20, nil). + AddInputField("Age", "", 4, nil). + AddDropDown("Select", []string{"One", "Two", "Three"}, 1, func(text string, index int) { + if text == "Three" { + app.Stop() + } + }). AddButton("Save", func() { app.Stop() }). AddButton("Cancel", nil). AddButton("Go to list", func() { app.SetFocus(list) })). AddText("Customer details", true, tview.AlignLeft, tcell.ColorRed). AddText("Customer details", false, tview.AlignCenter, tcell.ColorRed) - frame.SetBorder(true) + frame.SetBorder(true).SetTitle("Customers") list = tview.NewList(). - AddItem("Edit a form", "You can do whatever you want", 'e'). - AddItem("Quit the program", "Do it!", 0). - SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) { - if shortcut == 'e' { - app.SetFocus(frame) - } - }) + AddItem("Edit a form", "You can do whatever you want", 'e', func() { app.SetFocus(frame) }). + AddItem("Quit the program", "Do it!", 0, func() { app.Stop() }) list.SetBorder(true) flex := tview.NewFlex(tview.FlexColumn, []tview.Primitive{ diff --git a/dropdown.go b/dropdown.go new file mode 100644 index 0000000..6aef40e --- /dev/null +++ b/dropdown.go @@ -0,0 +1,298 @@ +package tview + +import ( + "github.com/gdamore/tcell" +) + +// dropDownOption is one option that can be selected in a drop-down primitive. +type dropDownOption struct { + Text string // The text to be displayed in the drop-down. + Selected func() // The (optional) callback for when this option was selected. +} + +// DropDown is a one-line box (three lines if there is a title) where the +// user can enter text. +type DropDown struct { + *Box + + // The options from which the user can choose. + options []*dropDownOption + + // The index of the currently selected option. Negative if no option is + // currently selected. + currentOption int + + // Set to true if the options are visible and selectable. + open bool + + // The list element for the options. + list *List + + // The text to be displayed before the input area. + label string + + // The label color. + labelColor tcell.Color + + // The background color of the input area. + fieldBackgroundColor tcell.Color + + // The text color of the input area. + fieldTextColor tcell.Color + + // The length of the input area. A value of 0 means extend as much as + // possible. + fieldLength int + + // An optional function which is called when the user indicated that they + // are done selecting options. The key which was pressed is provided (tab, + // shift-tab, or escape). + done func(tcell.Key) +} + +// NewDropDown returns a new drop-down. +func NewDropDown() *DropDown { + list := NewList().ShowSecondaryText(false) + list.SetMainTextColor(tcell.ColorBlack). + SetSelectedTextColor(tcell.ColorBlack). + SetSelectedBackgroundColor(tcell.ColorWhite). + SetBackgroundColor(tcell.ColorGreen) + + d := &DropDown{ + Box: NewBox(), + currentOption: -1, + list: list, + labelColor: tcell.ColorYellow, + fieldBackgroundColor: tcell.ColorBlue, + fieldTextColor: tcell.ColorWhite, + } + + d.focus = d + + return d +} + +// SetCurrentOption sets the index of the currently selected option. +func (d *DropDown) SetCurrentOption(index int) *DropDown { + d.currentOption = index + d.list.SetCurrentItem(index) + return d +} + +// SetLabel sets the text to be displayed before the input area. +func (d *DropDown) SetLabel(label string) *DropDown { + d.label = label + return d +} + +// GetLabel returns the text to be displayed before the input area. +func (d *DropDown) GetLabel() string { + return d.label +} + +// SetLabelColor sets the color of the label. +func (d *DropDown) SetLabelColor(color tcell.Color) *DropDown { + d.labelColor = color + return d +} + +// SetFieldBackgroundColor sets the background color of the options area. +func (d *DropDown) SetFieldBackgroundColor(color tcell.Color) *DropDown { + d.fieldBackgroundColor = color + return d +} + +// SetFieldTextColor sets the text color of the options area. +func (d *DropDown) SetFieldTextColor(color tcell.Color) *DropDown { + d.fieldTextColor = color + return d +} + +// SetFormAttributes sets attributes shared by all form items. +func (d *DropDown) SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { + d.label = label + d.labelColor = labelColor + d.backgroundColor = bgColor + d.fieldTextColor = fieldTextColor + d.fieldBackgroundColor = fieldBgColor + return d +} + +// SetFieldLength sets the length of the options area. A value of 0 means extend +// to as long as the longest option text. +func (d *DropDown) SetFieldLength(length int) *DropDown { + d.fieldLength = length + return d +} + +// AddOption adds a new selectable option to this drop-down. The "selected" +// callback is called when this option was selected. It may be nil. +func (d *DropDown) AddOption(text string, selected func()) *DropDown { + d.options = append(d.options, &dropDownOption{Text: text, Selected: selected}) + d.list.AddItem(text, "", 0, selected) + return d +} + +// SetOptions replaces all current options with the ones provided and installs +// one callback function which is called when one of the options is selected. +// It will be called with the option's text and its index into the options +// slice. The "selected" parameter may be nil. +func (d *DropDown) SetOptions(texts []string, selected func(text string, index int)) *DropDown { + d.list.ClearItems() + d.options = nil + for index, text := range texts { + func(t string, i int) { + d.AddOption(text, func() { + if selected != nil { + selected(t, i) + } + }) + }(text, index) + } + return d +} + +// SetDoneFunc sets a handler which is called when the user is done selecting +// options. The callback function is provided with the key that was pressed, +// which is one of the following: +// +// - KeyEscape: Abort selection. +// - KeyTab: Move to the next field. +// - KeyBacktab: Move to the previous field. +func (d *DropDown) SetDoneFunc(handler func(key tcell.Key)) *DropDown { + d.done = handler + return d +} + +// SetFinishedFunc calls SetDoneFunc(). +func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem { + return d.SetDoneFunc(handler) +} + +// GetFocusable returns the item's Focusable. +func (d *DropDown) GetFocusable() Focusable { + return d.focus +} + +// Draw draws this primitive onto the screen. +func (d *DropDown) Draw(screen tcell.Screen) { + d.Box.Draw(screen) + + // Prepare + x := d.x + y := d.y + rightLimit := x + d.width + height := d.height + if d.border { + x++ + y++ + rightLimit -= 2 + height -= 2 + } + if height < 1 || rightLimit <= x { + return + } + + // Draw label. + x += Print(screen, d.label, x, y, rightLimit-x, AlignLeft, d.labelColor) + + // What's the longest option text? + maxLength := 0 + for _, option := range d.options { + length := len([]rune(option.Text)) + if length > maxLength { + maxLength = length + } + } + + // Draw selection area. + fieldLength := d.fieldLength + if fieldLength == 0 { + fieldLength = maxLength + } + if rightLimit-x < fieldLength { + fieldLength = rightLimit - x + } + fieldStyle := tcell.StyleDefault.Background(d.fieldBackgroundColor) + if d.GetFocusable().HasFocus() && !d.open { + fieldStyle = fieldStyle.Background(d.fieldTextColor) + } + for index := 0; index < fieldLength; index++ { + screen.SetContent(x+index, y, ' ', nil, fieldStyle) + } + + // Draw selected text. + if d.currentOption >= 0 && d.currentOption < len(d.options) { + color := d.fieldTextColor + if d.GetFocusable().HasFocus() && !d.open { + color = d.fieldBackgroundColor + } + Print(screen, d.options[d.currentOption].Text, x, y, fieldLength, AlignLeft, color) + } + + // Draw options list. + if d.HasFocus() && d.open { + // We prefer to drop down but if there is no space, maybe drop up? + lx := x + ly := y + 1 + lwidth := maxLength + lheight := len(d.options) + _, sheight := screen.Size() + if ly+lheight >= sheight && ly-lheight-1 >= 0 { + ly = y - lheight + } + d.list.SetRect(lx, ly, lwidth, lheight) + d.list.Draw(screen) + } + + // No cursor for this primitive. + if d.focus.HasFocus() { + screen.HideCursor() + } +} + +// InputHandler returns the handler for this primitive. +func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return func(event *tcell.EventKey, setFocus func(p Primitive)) { + // Process key event. + switch key := event.Key(); key { + case tcell.KeyEnter, tcell.KeyRune, tcell.KeyDown: + if key == tcell.KeyRune && event.Rune() != ' ' { + break + } + d.open = true + d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) { + // An option was selected. Close the list again. + d.open = false + setFocus(d) + d.currentOption = index + + // Trigger "selected" event. + if d.options[d.currentOption].Selected != nil { + d.options[d.currentOption].Selected() + } + }) + setFocus(d.list) + case tcell.KeyEscape, tcell.KeyTab, tcell.KeyBacktab: + if d.done != nil { + d.done(key) + } + } + } +} + +// Focus is called by the application when the primitive receives focus. +func (d *DropDown) Focus(delegate func(p Primitive)) { + d.Box.Focus(delegate) + if d.open { + delegate(d.list) + } +} + +// HasFocus returns whether or not this primitive has focus. +func (d *DropDown) HasFocus() bool { + if d.open { + return d.list.HasFocus() + } + return d.hasFocus +} diff --git a/flex.go b/flex.go index c763ed6..924ff3c 100644 --- a/flex.go +++ b/flex.go @@ -99,14 +99,14 @@ func (f *Flex) SetRect(x, y, width, height int) { } // InputHandler returns nil. -func (f *Flex) InputHandler() func(event *tcell.EventKey) { +func (f *Flex) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { return nil } // Focus is called when this primitive receives focus. -func (f *Flex) Focus(app *Application) { +func (f *Flex) Focus(delegate func(p Primitive)) { if len(f.items) > 0 { - app.SetFocus(f.items[0].Item) + delegate(f.items[0].Item) } } diff --git a/form.go b/form.go index e47c01a..d47f962 100644 --- a/form.go +++ b/form.go @@ -6,12 +6,33 @@ import ( "github.com/gdamore/tcell" ) +// FormItem is the interface all form items must implement to be able to be +// included in a form. +type FormItem interface { + Primitive + + // GetLabel returns the item's label text. + GetLabel() string + + // SetFormAttributes sets a number of item attributes at once. + SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem + + // SetEnteredFunc sets the handler function for when the user finished + // entering data into the item. The handler may receive events for the + // Enter key (we're done), the Escape key (cancel input), the Tab key (move to + // next field), and the Backtab key (move to previous field). + SetFinishedFunc(handler func(key tcell.Key)) FormItem + + // GetFocusable returns the item's Focusable. + GetFocusable() Focusable +} + // Form is a Box which contains multiple input fields, one per row. type Form struct { *Box // The items of the form (one row per item). - items []*InputField + items []FormItem // The buttons of the form. buttons []*Button @@ -74,11 +95,11 @@ func (f *Form) SetFieldTextColor(color tcell.Color) *Form { return f } -// AddItem adds a new item to the form. It has a label, an optional initial -// value, a field length (a value of 0 extends it as far as possible), and -// an optional accept function to validate the item's value (set to nil to +// AddInputField adds an input field to the form. It has a label, an optional +// initial value, a field length (a value of 0 extends it as far as possible), +// and an optional accept function to validate the item's value (set to nil to // accept any text). -func (f *Form) AddItem(label, value string, fieldLength int, accept func(textToCheck string, lastChar rune) bool) *Form { +func (f *Form) AddInputField(label, value string, fieldLength int, accept func(textToCheck string, lastChar rune) bool) *Form { f.items = append(f.items, NewInputField(). SetLabel(label). SetText(value). @@ -87,6 +108,17 @@ func (f *Form) AddItem(label, value string, fieldLength int, accept func(textToC return f } +// AddDropDown adds a drop-down element to the form. It has a label, options, +// and an (optional) callback function which is invoked when an option was +// selected. +func (f *Form) AddDropDown(label string, options []string, initialOption int, selected func(option string, optionIndex int)) *Form { + f.items = append(f.items, NewDropDown(). + SetLabel(label). + SetCurrentOption(initialOption). + SetOptions(options, selected)) + return f +} + // AddButton adds a new button to the form. The "selected" function is called // when the user selects this button. It may be nil. func (f *Form) AddButton(label string, selected func()) *Form { @@ -113,8 +145,8 @@ func (f *Form) Draw(screen tcell.Screen) { // Find the longest label. var labelLength int - for _, inputField := range f.items { - label := strings.TrimSpace(inputField.GetLabel()) + for _, item := range f.items { + label := strings.TrimSpace(item.GetLabel()) if len([]rune(label)) > labelLength { labelLength = len([]rune(label)) } @@ -122,18 +154,23 @@ func (f *Form) Draw(screen tcell.Screen) { labelLength++ // Add one space. // Set up and draw the input fields. - for _, inputField := range f.items { + for _, item := range f.items { if y >= bottomLimit { return // Stop here. } - label := strings.TrimSpace(inputField.GetLabel()) - inputField.SetLabelColor(f.labelColor). - SetFieldBackgroundColor(f.fieldBackgroundColor). - SetFieldTextColor(f.fieldTextColor). - SetLabel(label+strings.Repeat(" ", labelLength-len([]rune(label)))). - SetBackgroundColor(f.backgroundColor). - SetRect(x, y, width, 1) - inputField.Draw(screen) + label := strings.TrimSpace(item.GetLabel()) + item.SetFormAttributes( + label+strings.Repeat(" ", labelLength-len([]rune(label))), + f.labelColor, + f.backgroundColor, + f.fieldTextColor, + f.fieldBackgroundColor, + ).SetRect(x, y, width, 1) + if item.GetFocusable().HasFocus() { + defer item.Draw(screen) + } else { + item.Draw(screen) + } y += 1 + f.itemPadding } @@ -161,7 +198,7 @@ func (f *Form) Draw(screen tcell.Screen) { } // Focus is called by the application when the primitive receives focus. -func (f *Form) Focus(app *Application) { +func (f *Form) Focus(delegate func(p Primitive)) { if len(f.items)+len(f.buttons) == 0 { return } @@ -182,30 +219,31 @@ func (f *Form) Focus(app *Application) { case tcell.KeyEscape: f.focusedElement = 0 } - f.Focus(app) + f.Focus(delegate) } + if f.focusedElement < len(f.items) { // We're selecting an item. - inputField := f.items[f.focusedElement] - inputField.SetDoneFunc(handler) - app.SetFocus(inputField) + item := f.items[f.focusedElement] + item.SetFinishedFunc(handler) + delegate(item) } else { // We're selecting a button. button := f.buttons[f.focusedElement-len(f.items)] button.SetBlurFunc(handler) - app.SetFocus(button) + delegate(button) } } // InputHandler returns the handler for this primitive. -func (f *Form) InputHandler() func(event *tcell.EventKey) { - return func(event *tcell.EventKey) {} +func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return func(event *tcell.EventKey, setFocus func(p Primitive)) {} } // HasFocus returns whether or not this primitive has focus. func (f *Form) HasFocus() bool { for _, item := range f.items { - if item.focus.HasFocus() { + if item.GetFocusable().HasFocus() { return true } } diff --git a/frame.go b/frame.go index 1656c80..05924ed 100644 --- a/frame.go +++ b/frame.go @@ -150,14 +150,13 @@ func (f *Frame) Draw(screen tcell.Screen) { } // Focus is called when this primitive receives focus. -func (f *Frame) Focus(app *Application) { - app.SetFocus(f.primitive) +func (f *Frame) Focus(delegate func(p Primitive)) { + delegate(f.primitive) } // InputHandler returns the handler for this primitive. -func (f *Frame) InputHandler() func(event *tcell.EventKey) { - return func(event *tcell.EventKey) { - } +func (f *Frame) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return func(event *tcell.EventKey, setFocus func(p Primitive)) {} } // HasFocus returns whether or not this primitive has focus. diff --git a/inputfield.go b/inputfield.go index 3db8c7d..0e0ace5 100644 --- a/inputfield.go +++ b/inputfield.go @@ -132,6 +132,16 @@ func (i *InputField) SetFieldTextColor(color tcell.Color) *InputField { return i } +// SetFormAttributes sets attributes shared by all form items. +func (i *InputField) SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem { + i.label = label + i.labelColor = labelColor + i.backgroundColor = bgColor + i.fieldTextColor = fieldTextColor + i.fieldBackgroundColor = fieldBgColor + return i +} + // SetFieldLength sets the length of the input area. A value of 0 means extend // as much as possible. func (i *InputField) SetFieldLength(length int) *InputField { @@ -162,6 +172,16 @@ func (i *InputField) SetDoneFunc(handler func(key tcell.Key)) *InputField { return i } +// SetFinishedFunc calls SetDoneFunc(). +func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem { + return i.SetDoneFunc(handler) +} + +// GetFocusable returns the item's Focusable. +func (i *InputField) GetFocusable() Focusable { + return i.focus +} + // Draw draws this primitive onto the screen. func (i *InputField) Draw(screen tcell.Screen) { i.Box.Draw(screen) @@ -233,8 +253,8 @@ func (i *InputField) setCursor(screen tcell.Screen) { } // InputHandler returns the handler for this primitive. -func (i *InputField) InputHandler() func(event *tcell.EventKey) { - return func(event *tcell.EventKey) { +func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return func(event *tcell.EventKey, setFocus func(p Primitive)) { // Process key event. switch key := event.Key(); key { case tcell.KeyRune: // Regular character. diff --git a/list.go b/list.go index 198d317..ff3c890 100644 --- a/list.go +++ b/list.go @@ -11,6 +11,7 @@ type listItem struct { MainText string // The main text of the list item. SecondaryText string // 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. } // List displays rows of items, each of which can be selected. @@ -26,8 +27,7 @@ type List struct { // Whether or not to show the secondary item texts. showSecondaryText bool - // The item main text color. Selected items have their background and text - // color switched. + // The item main text color. mainTextColor tcell.Color // The item secondary text color. @@ -36,29 +36,44 @@ type List struct { // The item shortcut text color. shortcutColor tcell.Color - // An optional function which is called when a list item was selected. + // The text color for selected items. + selectedTextColor tcell.Color + + // The background color for selected items. + selectedBackgroundColor tcell.Color + + // 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, mainText, secondaryText string, shortcut rune) } // NewList returns a new form. func NewList() *List { return &List{ - Box: NewBox(), - showSecondaryText: true, - mainTextColor: tcell.ColorWhite, - secondaryTextColor: tcell.ColorGreen, - shortcutColor: tcell.ColorYellow, + Box: NewBox(), + showSecondaryText: true, + mainTextColor: tcell.ColorWhite, + secondaryTextColor: tcell.ColorGreen, + shortcutColor: tcell.ColorYellow, + selectedTextColor: tcell.ColorBlack, + selectedBackgroundColor: tcell.ColorWhite, } } -// SetMainTextColorColor sets the color of the items' main text. -func (l *List) SetMainTextColorColor(color tcell.Color) *List { +// SetCurrentItem sets the currently selected item by its index. +func (l *List) SetCurrentItem(index int) *List { + l.currentItem = index + return l +} + +// SetMainTextColor sets the color of the items' main text. +func (l *List) SetMainTextColor(color tcell.Color) *List { l.mainTextColor = color return l } -// SetSecondaryTextColorColor sets the color of the items' secondary text. -func (l *List) SetSecondaryTextColorColor(color tcell.Color) *List { +// SetSecondaryTextColor sets the color of the items' secondary text. +func (l *List) SetSecondaryTextColor(color tcell.Color) *List { l.secondaryTextColor = color return l } @@ -69,6 +84,18 @@ func (l *List) SetShortcutColor(color tcell.Color) *List { return l } +// SetSelectedTextColor sets the text color of selected items. +func (l *List) SetSelectedTextColor(color tcell.Color) *List { + l.selectedTextColor = color + return l +} + +// SetSelectedBackgroundColor sets the background color of selected items. +func (l *List) SetSelectedBackgroundColor(color tcell.Color) *List { + l.selectedBackgroundColor = color + return l +} + // ShowSecondaryText determines whether or not to show secondary item texts. func (l *List) ShowSecondaryText(show bool) *List { l.showSecondaryText = show @@ -90,15 +117,27 @@ func (l *List) SetSelectedFunc(handler func(int, string, string, rune)) *List { // // The shortcut is a key binding. If the specified rune is entered, the item // is selected immediately. Set to 0 for no binding. -func (l *List) AddItem(mainText, secondaryText string, shortcut rune) *List { +// +// The "selected" callback will be invoked when the user selects the item. You +// may provide nil if no such item is needed or if all events are handled +// through the selected callback set with SetSelectedFunc(). +func (l *List) AddItem(mainText, secondaryText string, shortcut rune, selected func()) *List { l.items = append(l.items, &listItem{ MainText: mainText, SecondaryText: secondaryText, Shortcut: shortcut, + Selected: selected, }) return l } +// ClearItems removes all items from the list. +func (l *List) ClearItems() *List { + l.items = nil + l.currentItem = 0 + return l +} + // Draw draws this primitive onto the screen. func (l *List) Draw(screen tcell.Screen) { l.Box.Draw(screen) @@ -141,11 +180,11 @@ func (l *List) Draw(screen tcell.Screen) { color := l.mainTextColor if l.focus.HasFocus() && index == l.currentItem { textLength := len([]rune(item.MainText)) - style := tcell.StyleDefault.Background(l.mainTextColor) + style := tcell.StyleDefault.Background(l.selectedBackgroundColor) for bx := 0; bx < textLength && bx < width; bx++ { screen.SetContent(x+bx, y, ' ', nil, style) } - color = l.backgroundColor + color = l.selectedTextColor } Print(screen, item.MainText, x, y, width, AlignLeft, color) y++ @@ -163,8 +202,8 @@ func (l *List) Draw(screen tcell.Screen) { } // InputHandler returns the handler for this primitive. -func (l *List) InputHandler() func(event *tcell.EventKey) { - return func(event *tcell.EventKey) { +func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return func(event *tcell.EventKey, setFocus func(p Primitive)) { switch key := event.Key(); key { case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight: l.currentItem++ @@ -179,8 +218,11 @@ func (l *List) InputHandler() func(event *tcell.EventKey) { case tcell.KeyPgUp: l.currentItem -= 5 case tcell.KeyEnter: + item := l.items[l.currentItem] + if item.Selected != nil { + item.Selected() + } if l.selected != nil { - item := l.items[l.currentItem] l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) } case tcell.KeyRune: @@ -200,8 +242,11 @@ func (l *List) InputHandler() func(event *tcell.EventKey) { break } } + item := l.items[l.currentItem] + if item.Selected != nil { + item.Selected() + } if l.selected != nil { - item := l.items[l.currentItem] l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) } } diff --git a/primitive.go b/primitive.go index 1dff02c..d66a4fb 100644 --- a/primitive.go +++ b/primitive.go @@ -22,12 +22,17 @@ type Primitive interface { // A value of nil may also be returned, in which case this primitive cannot // receive focus and will not process any key events. // + // The handler will receive the key event and a function that allows it to + // set the focus to a different primitive, so that future key events are sent + // to that primitive. + // // The Application's Draw() function will be called automatically after the // handler returns. - InputHandler() func(event *tcell.EventKey) + InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) // Focus is called by the application when the primitive receives focus. - Focus(app *Application) + // Implementers may call delegate() to pass the focus on to another primitive. + Focus(delegate func(p Primitive)) // Blur is called by the application when the primitive loses focus. Blur()