Archived
Support the mouse in the branch popup
A click on a branch switches to it, the wheel moves the selection, a click outside closes the popup. The popup is hit-tested by its own position, as it is drawn after zone.Scan. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,7 @@ type Model struct {
|
||||
loading bool
|
||||
err error
|
||||
spinner string
|
||||
w, h int // size of the popup as last rendered, for hit tests
|
||||
}
|
||||
|
||||
// New returns a picker in the loading state.
|
||||
@@ -177,11 +178,40 @@ func (m *Model) View(termWidth, termHeight int) string {
|
||||
lines = append(lines, body[:m.rows]...)
|
||||
lines = append(lines, muted.Render(ansi.Truncate("↑↓ select · enter switch · esc cancel", inner, "…")))
|
||||
|
||||
return lipgloss.NewStyle().
|
||||
out := lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(m.theme.Border).
|
||||
Padding(0, 1).
|
||||
Render(strings.Join(lines, "\n"))
|
||||
m.w, m.h = lipgloss.Width(out), lipgloss.Height(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Origin is the top-left cell of the popup, centered in a terminal of the
|
||||
// given size. Valid after View.
|
||||
func (m *Model) Origin(termWidth, termHeight int) (x, y int) {
|
||||
return max((termWidth-m.w)/2, 0), max((termHeight-m.h)/2, 0)
|
||||
}
|
||||
|
||||
// listTop is the line of the popup (border, title, search, rule) where the
|
||||
// list starts.
|
||||
const listTop = 4
|
||||
|
||||
// Click handles a left click at cell (x, y) of a terminal of the given size.
|
||||
// inside reports whether the click was within the popup. If it hit a branch,
|
||||
// that branch is selected and hit is true.
|
||||
func (m *Model) Click(x, y, termWidth, termHeight int) (inside, hit bool) {
|
||||
ox, oy := m.Origin(termWidth, termHeight)
|
||||
x, y = x-ox, y-oy
|
||||
if x < 0 || y < 0 || x >= m.w || y >= m.h {
|
||||
return false, false
|
||||
}
|
||||
row := y - listTop
|
||||
if m.loading || m.err != nil || x == 0 || x == m.w-1 || row < 0 || row >= m.rows || m.offset+row >= len(m.shown) {
|
||||
return true, false
|
||||
}
|
||||
m.cursor = m.offset + row
|
||||
return true, true
|
||||
}
|
||||
|
||||
// row renders one branch: the name, right-aligned the short SHA and age.
|
||||
|
||||
@@ -127,3 +127,44 @@ func TestListScrollsInASmallTerminal(t *testing.T) {
|
||||
t.Errorf("cursor %d outside the window [%d,%d)", m.cursor, m.offset, m.offset+m.rows)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickSelectsTheBranchUnderTheMouse(t *testing.T) {
|
||||
m := New(theme.Default(), "src/app")
|
||||
m.SetBranches(list())
|
||||
m.View(100, 30)
|
||||
x, y := m.Origin(100, 30)
|
||||
|
||||
if inside, hit := m.Click(x+3, y+listTop+1, 100, 30); !inside || !hit || name(m) != "dev" {
|
||||
t.Errorf("click on the 2nd row: inside=%v hit=%v selected %q", inside, hit, name(m))
|
||||
}
|
||||
if inside, hit := m.Click(x+3, y+listTop+3, 100, 30); !inside || !hit || name(m) != "origin/feat/search" {
|
||||
t.Errorf("click on the 4th row: inside=%v hit=%v selected %q", inside, hit, name(m))
|
||||
}
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
x, y int
|
||||
}{
|
||||
{"title", x + 3, y + 1},
|
||||
{"search line", x + 3, y + 2},
|
||||
{"hint line", x + 3, y + m.h - 2},
|
||||
{"left border", x, y + listTop},
|
||||
} {
|
||||
before := name(m)
|
||||
if inside, hit := m.Click(c.x, c.y, 100, 30); !inside || hit || name(m) != before {
|
||||
t.Errorf("click on the %s: inside=%v hit=%v selected %q", c.name, inside, hit, name(m))
|
||||
}
|
||||
}
|
||||
if inside, hit := m.Click(0, 0, 100, 30); inside || hit {
|
||||
t.Errorf("click outside: inside=%v hit=%v", inside, hit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickBelowTheLastBranchHitsNothing(t *testing.T) {
|
||||
m := New(theme.Default(), "x")
|
||||
m.SetBranches(list()[:1]) // 1 branch, the list is padded to 3 rows
|
||||
m.View(100, 30)
|
||||
x, y := m.Origin(100, 30)
|
||||
if inside, hit := m.Click(x+3, y+listTop+2, 100, 30); !inside || hit {
|
||||
t.Errorf("inside=%v hit=%v on an empty row", inside, hit)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user