package ui import ( "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) // arrowNavigation lets a form be driven with the arrow keys: // - Up/Down move between the fields and buttons, // - Left/Right change the value of a closed dropdown without opening it // (and move between the buttons), // // Enter or Space still opens a dropdown list. While a list is open it gets all keys. func arrowNavigation(f *tview.Form) { f.SetInputCapture(func(ev *tcell.EventKey) *tcell.EventKey { item, button := f.GetFocusedItemIndex() if item < 0 && button < 0 { return ev } dd, _ := focusedDropDown(f, item) if dd != nil && dd.IsOpen() { return ev } total := f.GetFormItemCount() + f.GetButtonCount() pos := item if button >= 0 { pos = f.GetFormItemCount() + button } switch ev.Key() { case tcell.KeyDown: f.SetFocus(min(pos+1, total-1)) case tcell.KeyUp: f.SetFocus(max(pos-1, 0)) case tcell.KeyLeft, tcell.KeyRight: step := 1 if ev.Key() == tcell.KeyLeft { step = -1 } switch { case dd != nil: n := dd.GetOptionCount() cur, _ := dd.GetCurrentOption() dd.SetCurrentOption((cur + step + n) % n) case button >= 0: f.SetFocus(min(max(pos+step, f.GetFormItemCount()), total-1)) default: return ev } default: return ev } return nil }) } func focusedDropDown(f *tview.Form, item int) (*tview.DropDown, bool) { if item < 0 { return nil, false } dd, ok := f.GetFormItem(item).(*tview.DropDown) return dd, ok } // bindSearchNow keeps a "Search now" checkbox consistent with whether a search // can find anything. While searchable() is false the box is unchecked, greyed // out and cannot be ticked (blocked is called instead); it returns to the // user's earlier choice afterwards. The returned function must be called // whenever the answer of searchable() may have changed. // // tview's SetDisabled is avoided on purpose: on a form item it re-triggers // the form's focus handling and leaves two fields highlighted. func bindSearchNow(box *tview.Checkbox, searchable func() bool, blocked func()) (sync func()) { want := true box.SetChangedFunc(func(checked bool) { switch { case searchable(): want = checked case checked: box.SetChecked(false) blocked() } }) return func() { if searchable() { box.SetLabelColor(tview.Styles.SecondaryTextColor) box.SetChecked(want) return } box.SetLabelColor(tcell.ColorGray) box.SetChecked(false) } }