Improve action menu escape handling
Make the F11 action menu close through a shared close action so Escape and the explicit Close actions menu item follow the same path. Treat Escape-prefixed Up and Down events as close inputs while the action menu is active, which handles termbox InputAlt behavior after pressing Escape. Preserve and restore the user/channel tree selection across action menu open and close, and preserve selection across live tree rebuilds. Tested with: GOCACHE=/tmp/barnard-go-cache go test ./...
This commit is contained in:
+40
-4
@@ -59,11 +59,34 @@ func (t *Tree) uiSetBounds(x0, y0, x1, y1 int) {
|
||||
}
|
||||
|
||||
func (t *Tree) Rebuild() {
|
||||
t.rebuild(false, nil)
|
||||
}
|
||||
|
||||
func (t *Tree) RebuildPreservingActiveItem(sameItem func(previous, current TreeItem) bool) {
|
||||
t.rebuild(true, sameItem)
|
||||
}
|
||||
|
||||
func (t *Tree) SetActiveItem(target TreeItem, sameItem func(previous, current TreeItem) bool) bool {
|
||||
if target == nil || sameItem == nil {
|
||||
return false
|
||||
}
|
||||
for line, item := range t.lines {
|
||||
if sameItem(target, item.Item) {
|
||||
t.SetActiveLine(line, false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tree) rebuild(preserveActive bool, sameItem func(previous, current TreeItem) bool) {
|
||||
if t.Generator == nil {
|
||||
t.lines = []renderedTreeItem{}
|
||||
return
|
||||
}
|
||||
|
||||
previousItem := t.ActiveItem()
|
||||
previousLine := t.activeLine
|
||||
lines := []renderedTreeItem{}
|
||||
for _, item := range t.Generator(nil) {
|
||||
children := t.rebuild_rec(item, 0)
|
||||
@@ -72,8 +95,22 @@ func (t *Tree) Rebuild() {
|
||||
}
|
||||
}
|
||||
t.lines = lines
|
||||
t.SetActiveLine(0, false)
|
||||
t.uiDraw()
|
||||
if preserveActive {
|
||||
t.SetActiveLine(previousLine, false)
|
||||
if previousItem != nil && sameItem != nil {
|
||||
for line, item := range t.lines {
|
||||
if sameItem(previousItem, item.Item) {
|
||||
t.SetActiveLine(line, false)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.SetActiveLine(0, false)
|
||||
}
|
||||
if t.ui != nil {
|
||||
t.uiDraw()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) rebuild_rec(parent TreeItem, level int) []renderedTreeItem {
|
||||
@@ -138,11 +175,10 @@ func (t *Tree) uiDraw() {
|
||||
fg := t.Fg
|
||||
bg := t.Bg
|
||||
dx := x - t.x0
|
||||
dy := y - t.y0
|
||||
if reader != nil && level*2 <= dx {
|
||||
if ch, _, err := reader.ReadRune(); err == nil {
|
||||
chr = ch
|
||||
fg, bg = item.TreeItemStyle(fg, bg, t.active && t.activeLine == dy)
|
||||
fg, bg = item.TreeItemStyle(fg, bg, t.active && t.activeLine == line)
|
||||
}
|
||||
}
|
||||
termbox.SetCell(x, y, chr, termbox.Attribute(fg), termbox.Attribute(bg))
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package uiterm
|
||||
|
||||
import "testing"
|
||||
|
||||
type testTreeItem string
|
||||
|
||||
func (i testTreeItem) String() string {
|
||||
return string(i)
|
||||
}
|
||||
|
||||
func (i testTreeItem) TreeItemStyle(fg, bg Attribute, active bool) (Attribute, Attribute) {
|
||||
return fg, bg
|
||||
}
|
||||
|
||||
func TestTreeRebuildResetsActiveLine(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tree := Tree{
|
||||
Generator: func(item TreeItem) []TreeItem {
|
||||
if item != nil {
|
||||
return nil
|
||||
}
|
||||
return []TreeItem{testTreeItem("one"), testTreeItem("two"), testTreeItem("three")}
|
||||
},
|
||||
}
|
||||
|
||||
tree.Rebuild()
|
||||
tree.SetActiveLine(2, false)
|
||||
tree.Rebuild()
|
||||
|
||||
if tree.activeLine != 0 {
|
||||
t.Fatalf("expected active line to reset to 0, got %d", tree.activeLine)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeRebuildPreservingActiveItemKeepsMatchingItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
items := []TreeItem{testTreeItem("one"), testTreeItem("two"), testTreeItem("three")}
|
||||
tree := Tree{
|
||||
Generator: func(item TreeItem) []TreeItem {
|
||||
if item != nil {
|
||||
return nil
|
||||
}
|
||||
return items
|
||||
},
|
||||
}
|
||||
|
||||
tree.Rebuild()
|
||||
tree.SetActiveLine(1, false)
|
||||
items = []TreeItem{testTreeItem("three"), testTreeItem("one"), testTreeItem("two")}
|
||||
tree.RebuildPreservingActiveItem(func(previous, current TreeItem) bool {
|
||||
return previous.String() == current.String()
|
||||
})
|
||||
|
||||
if tree.activeLine != 2 {
|
||||
t.Fatalf("expected active line to follow matching item to line 2, got %d", tree.activeLine)
|
||||
}
|
||||
if got := tree.ActiveItem().String(); got != "two" {
|
||||
t.Fatalf("expected active item two, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeSetActiveItemSelectsMatchingItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tree := Tree{
|
||||
Generator: func(item TreeItem) []TreeItem {
|
||||
if item != nil {
|
||||
return nil
|
||||
}
|
||||
return []TreeItem{testTreeItem("one"), testTreeItem("two"), testTreeItem("three")}
|
||||
},
|
||||
}
|
||||
|
||||
tree.Rebuild()
|
||||
if ok := tree.SetActiveItem(testTreeItem("three"), func(previous, current TreeItem) bool {
|
||||
return previous.String() == current.String()
|
||||
}); !ok {
|
||||
t.Fatal("expected matching item to be selected")
|
||||
}
|
||||
|
||||
if tree.activeLine != 2 {
|
||||
t.Fatalf("expected active line 2, got %d", tree.activeLine)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user