Terminal-based user interface toolkit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

179 lines
3.8 KiB

package cview
import "sync"
// Focusable provides a method which determines if a primitive has focus.
// Composed primitives may be focused based on the focused state of their
// contained primitives.
type Focusable interface {
HasFocus() bool
}
type focusElement struct {
primitive Primitive
disabled bool
}
// FocusManager manages application focus.
type FocusManager struct {
elements []*focusElement
focused int
wrapAround bool
setFocus func(p Primitive)
sync.RWMutex
}
// NewFocusManager returns a new FocusManager object.
func NewFocusManager(setFocus func(p Primitive)) *FocusManager {
return &FocusManager{setFocus: setFocus}
}
// SetWrapAround sets the flag that determines whether navigation will wrap
// around. That is, navigating forwards on the last field will move the
// selection to the first field (similarly in the other direction). If set to
// false, the focus won't change when navigating forwards on the last element
// or navigating backwards on the first element.
func (f *FocusManager) SetWrapAround(wrapAround bool) {
f.Lock()
defer f.Unlock()
f.wrapAround = wrapAround
}
// Add adds an element to the focus handler.
func (f *FocusManager) Add(p ...Primitive) {
f.Lock()
defer f.Unlock()
for _, primitive := range p {
f.elements = append(f.elements, &focusElement{primitive: primitive})
}
}
// AddAt adds an element to the focus handler at the specified index.
func (f *FocusManager) AddAt(index int, p Primitive) {
f.Lock()
defer f.Unlock()
if index < 0 || index > len(f.elements) {
panic("index out of range")
}
element := &focusElement{primitive: p}
if index == len(f.elements) {
f.elements = append(f.elements, element)
return
}
f.elements = append(f.elements[:index+1], f.elements[index:]...)
f.elements[index] = element
}
// Focus focuses the provided element.
func (f *FocusManager) Focus(p Primitive) {
f.Lock()
defer f.Unlock()
for i, element := range f.elements {
if p == element.primitive && !element.disabled {
f.focused = i
break
}
}
f.setFocus(f.elements[f.focused].primitive)
}
// FocusPrevious focuses the previous element.
func (f *FocusManager) FocusPrevious() {
f.Lock()
defer f.Unlock()
f.focused--
f.updateFocusIndex(true)
f.setFocus(f.elements[f.focused].primitive)
}
// FocusNext focuses the next element.
func (f *FocusManager) FocusNext() {
f.Lock()
defer f.Unlock()
f.focused++
f.updateFocusIndex(false)
f.setFocus(f.elements[f.focused].primitive)
}
// FocusAt focuses the element at the provided index.
func (f *FocusManager) FocusAt(index int) {
f.Lock()
defer f.Unlock()
f.focused = index
f.setFocus(f.elements[f.focused].primitive)
}
// GetFocusIndex returns the index of the currently focused element.
func (f *FocusManager) GetFocusIndex() int {
f.Lock()
defer f.Unlock()
return f.focused
}
// GetFocusedPrimitive returns the currently focused primitive.
func (f *FocusManager) GetFocusedPrimitive() Primitive {
f.Lock()
defer f.Unlock()
return f.elements[f.focused].primitive
}
func (f *FocusManager) updateFocusIndex(decreasing bool) {
for i := 0; i < len(f.elements); i++ {
if f.focused < 0 {
if f.wrapAround {
f.focused = len(f.elements) - 1
} else {
f.focused = 0
}
} else if f.focused >= len(f.elements) {
if f.wrapAround {
f.focused = 0
} else {
f.focused = len(f.elements) - 1
}
}
item := f.elements[f.focused]
if !item.disabled {
break
}
if decreasing {
f.focused--
} else {
f.focused++
}
}
}
// Transform modifies the current focus.
func (f *FocusManager) Transform(tr Transformation) {
var decreasing bool
switch tr {
case TransformFirstItem:
f.focused = 0
decreasing = true
case TransformLastItem:
f.focused = len(f.elements) - 1
case TransformPreviousItem:
f.focused--
decreasing = true
case TransformNextItem:
f.focused++
}
f.updateFocusIndex(decreasing)
}