Draw embedded views as one focused unit

A view can only be drawn with the focused frame and title colors while it
is the current view, but a panel made of an outer view and an editable
field embedded in it has to look focused as a whole, whichever of the two
the keyboard is pointed at.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-08-25 09:17:54 +02:00
parent 9e98b3d2f3
commit f9ec7adb61
3 changed files with 38 additions and 2 deletions

View file

@ -1615,6 +1615,20 @@ func (g *Gui) ForceFlushViewsContentOnly(views []*View) error {
return g.flushContentOnly(views)
}
// hasFocus reports whether a view is drawn as focused. Views that are embedded
// in one another (see View.ParentView) form a single unit, so they are all drawn
// as focused while any one of them is the current view.
func (g *Gui) hasFocus(v *View) bool {
return g.currentView != nil && outermostView(v) == outermostView(g.currentView)
}
func outermostView(v *View) *View {
for v.ParentView != nil {
v = v.ParentView
}
return v
}
// draw manages the cursor and calls the draw function of a view.
func (g *Gui) draw(v *View) error {
if !v.Visible || v.y1 < v.y0 || v.x1 < v.x0 {
@ -1639,7 +1653,7 @@ func (g *Gui) draw(v *View) error {
if v.Frame {
var fgColor, bgColor, frameColor Attribute
if g.Highlight && v == g.currentView && g.IsFocused() {
if g.Highlight && g.hasFocus(v) && g.IsFocused() {
fgColor = g.SelFgColor
bgColor = g.SelBgColor
frameColor = g.SelFrameColor

View file

@ -56,6 +56,26 @@ func TestFirstMatchingKeybindingOfParentViewWins(t *testing.T) {
assert.Equal(t, []string{"first"}, pressed)
}
func TestEmbeddedViewsAreFocusedTogether(t *testing.T) {
g := newTestGui(t)
parent, child := setupParentAndChildView(t, g)
sibling, _ := g.SetView("sibling", 0, 12, 20, 14, 0)
sibling.ParentView = parent
unrelated, _ := g.SetView("unrelated", 30, 0, 50, 10, 0)
assert.True(t, g.hasFocus(child))
assert.True(t, g.hasFocus(parent))
assert.True(t, g.hasFocus(sibling))
assert.False(t, g.hasFocus(unrelated))
_, err := g.SetCurrentView(unrelated.Name())
assert.NoError(t, err)
assert.True(t, g.hasFocus(unrelated))
assert.False(t, g.hasFocus(parent))
assert.False(t, g.hasFocus(child))
}
func TestPrintableKeysGoToTheFieldBeingTypedIn(t *testing.T) {
for _, test := range []struct {
name string

View file

@ -210,7 +210,9 @@ type View struct {
// Overlaps describes which edges are overlapping with another view's edges
Overlaps byte
// ParentView is the view which catches events bubbled up from the given view if there's no matching handler
// ParentView is the view which catches events bubbled up from the given view if there's no matching handler.
// Views related this way are also drawn as a single focused unit: while one of
// them is the current view, they all get the focused frame and title colors.
ParentView *View
searcher *searcher