feat(frame): add focus management fields, Focused(), Focus(), WithKeyMap()
This commit is contained in:
parent
02e8343ee5
commit
acfbc2aaee
2 changed files with 86 additions and 0 deletions
|
|
@ -8,6 +8,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
|
|
@ -40,6 +41,13 @@ type Frame struct {
|
|||
out io.Writer
|
||||
done chan struct{}
|
||||
mu sync.Mutex
|
||||
|
||||
// Focus management (bubbletea upgrade)
|
||||
focused Region
|
||||
keyMap KeyMap
|
||||
width int
|
||||
height int
|
||||
program *tea.Program
|
||||
}
|
||||
|
||||
// NewFrame creates a new Frame with the given HLCRF variant string.
|
||||
|
|
@ -53,6 +61,10 @@ func NewFrame(variant string) *Frame {
|
|||
models: make(map[Region]Model),
|
||||
out: os.Stdout,
|
||||
done: make(chan struct{}),
|
||||
focused: RegionContent,
|
||||
keyMap: DefaultKeyMap(),
|
||||
width: 80,
|
||||
height: 24,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +122,42 @@ func (f *Frame) Stop() {
|
|||
}
|
||||
}
|
||||
|
||||
// WithKeyMap sets custom key bindings for Frame navigation.
|
||||
func (f *Frame) WithKeyMap(km KeyMap) *Frame {
|
||||
f.keyMap = km
|
||||
return f
|
||||
}
|
||||
|
||||
// Focused returns the currently focused region.
|
||||
func (f *Frame) Focused() Region {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.focused
|
||||
}
|
||||
|
||||
// Focus sets focus to a specific region.
|
||||
// Ignores the request if the region is not in this Frame's variant.
|
||||
func (f *Frame) Focus(r Region) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if _, exists := f.layout.regions[r]; exists {
|
||||
f.focused = r
|
||||
}
|
||||
}
|
||||
|
||||
// buildFocusRing returns the ordered list of regions in this Frame's variant.
|
||||
// Order follows HLCRF convention.
|
||||
func (f *Frame) buildFocusRing() []Region {
|
||||
order := []Region{RegionHeader, RegionLeft, RegionContent, RegionRight, RegionFooter}
|
||||
var ring []Region
|
||||
for _, r := range order {
|
||||
if _, exists := f.layout.regions[r]; exists {
|
||||
ring = append(ring, r)
|
||||
}
|
||||
}
|
||||
return ring
|
||||
}
|
||||
|
||||
// Run renders the frame and blocks. In TTY mode, it live-refreshes at ~12fps.
|
||||
// In non-TTY mode, it renders once and returns immediately.
|
||||
func (f *Frame) Run() {
|
||||
|
|
|
|||
|
|
@ -261,6 +261,44 @@ func TestKeyMap_Good(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestFrameFocus_Good(t *testing.T) {
|
||||
t.Run("default focus is Content", func(t *testing.T) {
|
||||
f := NewFrame("HCF")
|
||||
assert.Equal(t, RegionContent, f.Focused())
|
||||
})
|
||||
|
||||
t.Run("Focus sets focused region", func(t *testing.T) {
|
||||
f := NewFrame("HCF")
|
||||
f.Focus(RegionHeader)
|
||||
assert.Equal(t, RegionHeader, f.Focused())
|
||||
})
|
||||
|
||||
t.Run("Focus ignores invalid region", func(t *testing.T) {
|
||||
f := NewFrame("HCF")
|
||||
f.Focus(RegionLeft) // Left not in "HCF"
|
||||
assert.Equal(t, RegionContent, f.Focused()) // unchanged
|
||||
})
|
||||
|
||||
t.Run("WithKeyMap returns frame for chaining", func(t *testing.T) {
|
||||
km := DefaultKeyMap()
|
||||
km.Quit = tea.KeyCtrlQ
|
||||
f := NewFrame("HCF").WithKeyMap(km)
|
||||
assert.Equal(t, tea.KeyCtrlQ, f.keyMap.Quit)
|
||||
})
|
||||
|
||||
t.Run("focusRing builds from variant", func(t *testing.T) {
|
||||
f := NewFrame("HLCRF")
|
||||
ring := f.buildFocusRing()
|
||||
assert.Equal(t, []Region{RegionHeader, RegionLeft, RegionContent, RegionRight, RegionFooter}, ring)
|
||||
})
|
||||
|
||||
t.Run("focusRing respects variant order", func(t *testing.T) {
|
||||
f := NewFrame("HCF")
|
||||
ring := f.buildFocusRing()
|
||||
assert.Equal(t, []Region{RegionHeader, RegionContent, RegionFooter}, ring)
|
||||
})
|
||||
}
|
||||
|
||||
// indexOf returns the position of substr in s, or -1 if not found.
|
||||
func indexOf(s, substr string) int {
|
||||
for i := range len(s) - len(substr) + 1 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue