Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions focus.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ type Focusable interface {
// widgets simply do not embed it and stay non-focusable.
type focusState struct {
focused bool
// FocusRingRadius, when > 0, draws the focus ring as a rounded rectangle of
// that corner radius (in device units) instead of the default 1-pixel square
// inset — for a widget whose body is a rounded field (a pill-shaped search
// box). Zero keeps the classic square ring, so every widget that does not set
// it renders byte-for-byte as before.
FocusRingRadius int
// FocusRingWidth is the rounded ring's stroke thickness; it applies only when
// FocusRingRadius > 0, and a value < 1 is treated as 1. Ignored for the square
// ring.
FocusRingWidth int
}

// SetFocused records whether this widget holds keyboard focus.
Expand All @@ -51,6 +61,14 @@ func (f *focusState) drawFocusRing(p painter.Painter, theme *Theme, r Rect) {
if !f.focused {
return
}
if f.FocusRingRadius > 0 {
w := f.FocusRingWidth
if w < 1 {
w = 1
}
p.StrokeRoundRect(painter.Rect{X: r.X, Y: r.Y, W: r.W, H: r.H}, f.FocusRingRadius, theme.Accent, w)
return
}
strokeRect(p, r.X+1, r.Y+1, r.W-2, r.H-2, theme.Accent)
}

Expand Down
32 changes: 31 additions & 1 deletion focus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

package toolkit

import "testing"
import (
"testing"

"github.com/go-widgets/painter"
)

// fakeFocusable is a minimal Focusable test double that records every
// SetFocused call so tests can assert exactly when focus toggled.
Expand Down Expand Up @@ -267,3 +271,29 @@ func TestFocusRing_HandleKey(t *testing.T) {
t.Fatalf("unconsumed key must not move focus: Current() = %d, want 0", got)
}
}

func TestDrawFocusRingRounded(t *testing.T) {
th := DefaultLight()
r := Rect{X: 2, Y: 2, W: 16, H: 16}

// Unfocused: paints nothing (no accent anywhere).
buf := make([]byte, 4*20*20)
(&focusState{}).drawFocusRing(painter.NewPixelPainter(buf, 20, 20), th, r)
if hasColor(buf, 20, th.Accent) {
t.Fatal("an unfocused widget must paint no focus ring")
}

// Focused rounded ring (Radius > 0, Width < 1 -> defaults to 1): accent painted.
buf = make([]byte, 4*20*20)
(&focusState{focused: true, FocusRingRadius: 4}).drawFocusRing(painter.NewPixelPainter(buf, 20, 20), th, r)
if !hasColor(buf, 20, th.Accent) {
t.Fatal("a rounded focus ring should paint the accent")
}

// Focused square ring (Radius == 0): the classic branch also paints accent.
buf = make([]byte, 4*20*20)
(&focusState{focused: true}).drawFocusRing(painter.NewPixelPainter(buf, 20, 20), th, r)
if !hasColor(buf, 20, th.Accent) {
t.Fatal("the square focus ring should paint the accent")
}
}
Loading