Arrow-key navigation in the add dialog
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -202,7 +202,7 @@ func (a *App) openModal(p tview.Primitive, width, height int) {
|
|||||||
AddItem(p, height, 0, true).
|
AddItem(p, height, 0, true).
|
||||||
AddItem(nil, 0, 1, false), width, 0, true).
|
AddItem(nil, 0, 1, false), width, 0, true).
|
||||||
AddItem(nil, 0, 1, false)
|
AddItem(nil, 0, 1, false)
|
||||||
a.pushOverlay(box, p, "Tab/arrows move · Enter select · Esc cancel")
|
a.pushOverlay(box, p, "↑↓ move · ←→ change · Enter select · Esc cancel")
|
||||||
}
|
}
|
||||||
|
|
||||||
// openFull shows p above the whole view area (the tab bar and status bar stay visible).
|
// openFull shows p above the whole view area (the tab bar and status bar stay visible).
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArrowNavigation(t *testing.T) {
|
||||||
|
f := tview.NewForm().
|
||||||
|
AddDropDown("A", []string{"x", "y", "z"}, 0, nil).
|
||||||
|
AddCheckbox("B", true, nil).
|
||||||
|
AddButton("Add", nil).AddButton("Cancel", nil)
|
||||||
|
arrowNavigation(f)
|
||||||
|
var focus func(p tview.Primitive)
|
||||||
|
focus = func(p tview.Primitive) { p.Focus(focus) }
|
||||||
|
f.Focus(focus)
|
||||||
|
cap := f.GetInputCapture()
|
||||||
|
send := func(key tcell.Key) { cap(tcell.NewEventKey(key, 0, 0)) }
|
||||||
|
|
||||||
|
dd := f.GetFormItem(0).(*tview.DropDown)
|
||||||
|
send(tcell.KeyRight)
|
||||||
|
if i, _ := dd.GetCurrentOption(); i != 1 {
|
||||||
|
t.Fatalf("Right: option %d, want 1", i)
|
||||||
|
}
|
||||||
|
send(tcell.KeyLeft)
|
||||||
|
send(tcell.KeyLeft)
|
||||||
|
if i, _ := dd.GetCurrentOption(); i != 2 {
|
||||||
|
t.Fatalf("Left wraps: option %d, want 2", i)
|
||||||
|
}
|
||||||
|
send(tcell.KeyDown)
|
||||||
|
if item, _ := f.GetFocusedItemIndex(); item != 1 {
|
||||||
|
t.Fatalf("Down: item %d, want 1", item)
|
||||||
|
}
|
||||||
|
send(tcell.KeyDown)
|
||||||
|
send(tcell.KeyDown)
|
||||||
|
send(tcell.KeyDown) // clamps at the last button
|
||||||
|
if _, b := f.GetFocusedItemIndex(); b != 1 {
|
||||||
|
t.Fatalf("button %d, want 1", b)
|
||||||
|
}
|
||||||
|
send(tcell.KeyLeft)
|
||||||
|
if _, b := f.GetFocusedItemIndex(); b != 0 {
|
||||||
|
t.Fatalf("Left on buttons: %d, want 0", b)
|
||||||
|
}
|
||||||
|
send(tcell.KeyUp)
|
||||||
|
if item, _ := f.GetFocusedItemIndex(); item != 1 {
|
||||||
|
t.Fatalf("Up from first button: item %d, want 1", item)
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -14,7 +14,8 @@ const helpText = `[yellow]Global[-]
|
|||||||
|
|
||||||
[yellow]Movies and Series[-]
|
[yellow]Movies and Series[-]
|
||||||
/ focus the search box
|
/ focus the search box
|
||||||
Enter, a add the selected title
|
Enter, a add the selected title (dialog: ↑↓ move, ←→ change a
|
||||||
|
dropdown, Space toggles, Enter/Space opens a list)
|
||||||
i interactive release search (library titles)
|
i interactive release search (library titles)
|
||||||
|
|
||||||
[yellow]Library[-]
|
[yellow]Library[-]
|
||||||
|
|||||||
@@ -325,6 +325,7 @@ func (v *searchView) add(i int) {
|
|||||||
})
|
})
|
||||||
form.AddButton("Cancel", v.app.closeModal)
|
form.AddButton("Cancel", v.app.closeModal)
|
||||||
form.SetCancelFunc(v.app.closeModal)
|
form.SetCancelFunc(v.app.closeModal)
|
||||||
|
arrowNavigation(form)
|
||||||
form.SetBorder(true).SetTitle(" Add " + tview.Escape(r.title()) + " ")
|
form.SetBorder(true).SetTitle(" Add " + tview.Escape(r.title()) + " ")
|
||||||
|
|
||||||
v.app.openModal(form, 60, height)
|
v.app.openModal(form, 60, height)
|
||||||
|
|||||||
Reference in New Issue
Block a user