Add FocusManager
This commit is contained in:
parent
ffd8b57dec
commit
597052a7e7
8 changed files with 274 additions and 24 deletions
|
@ -1,4 +1,5 @@
|
|||
v1.5.1 (WIP)
|
||||
- Add FocusManager
|
||||
- Add Slider
|
||||
- Add TabbedPanels
|
||||
- Add Application.GetScreen and Application.GetScreenSize
|
||||
|
|
|
@ -28,7 +28,7 @@ Available widgets:
|
|||
- Selectable __lists__ with __context menus__
|
||||
- Modal __dialogs__
|
||||
- Horizontal and vertical __progress bars__
|
||||
- __Grid__, __Flexbox__ and __panel layouts__
|
||||
- __Grid__, __Flexbox__ and __tabbed panel layouts__
|
||||
- Sophisticated navigable __table views__
|
||||
- Flexible __tree views__
|
||||
- Draggable and resizable __windows__
|
||||
|
|
|
@ -67,7 +67,7 @@ func (c *ContextMenu) AddContextItem(text string, shortcut rune, selected func(i
|
|||
if text == "" && shortcut == 0 {
|
||||
c.list.Lock()
|
||||
index := len(c.list.items) - 1
|
||||
c.list.items[index].enabled = false
|
||||
c.list.items[index].disabled = true
|
||||
c.list.Unlock()
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ func (c *ContextMenu) show(item int, x int, y int, setFocus func(Primitive)) {
|
|||
|
||||
c.list.Lock()
|
||||
for i, item := range c.list.items {
|
||||
if item.enabled {
|
||||
if !item.disabled {
|
||||
c.list.currentItem = i
|
||||
break
|
||||
}
|
||||
|
|
82
demos/focusmanager/main.go
Normal file
82
demos/focusmanager/main.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Demo code for the FocusManager utility.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cview.NewApplication()
|
||||
app.EnableMouse(true)
|
||||
|
||||
input1 := cview.NewInputField()
|
||||
input1.SetLabel("InputField 1")
|
||||
|
||||
input2 := cview.NewInputField()
|
||||
input2.SetLabel("InputField 2")
|
||||
|
||||
input3 := cview.NewInputField()
|
||||
input3.SetLabel("InputField 3")
|
||||
|
||||
input4 := cview.NewInputField()
|
||||
input4.SetLabel("InputField 4")
|
||||
|
||||
grid := cview.NewGrid()
|
||||
grid.SetBorder(true)
|
||||
grid.SetTitle(" Press Tab to advance focus ")
|
||||
grid.AddItem(input1, 0, 0, 1, 1, 0, 0, true)
|
||||
grid.AddItem(input2, 0, 1, 1, 1, 0, 0, false)
|
||||
grid.AddItem(input3, 1, 1, 1, 1, 0, 0, false)
|
||||
grid.AddItem(input4, 1, 0, 1, 1, 0, 0, false)
|
||||
|
||||
focusManager := cview.NewFocusManager(app.SetFocus)
|
||||
focusManager.SetWrapAround(true)
|
||||
focusManager.Add(input1, input2, input3, input4)
|
||||
|
||||
inputHandler := cbind.NewConfiguration()
|
||||
for _, key := range cview.Keys.MovePreviousField {
|
||||
mod, key, ch, err := cbind.Decode(key)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if key == tcell.KeyRune {
|
||||
inputHandler.SetRune(mod, ch, func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
focusManager.FocusPrevious()
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
inputHandler.SetKey(mod, key, func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
focusManager.FocusPrevious()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, key := range cview.Keys.MoveNextField {
|
||||
mod, key, ch, err := cbind.Decode(key)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if key == tcell.KeyRune {
|
||||
inputHandler.SetRune(mod, ch, func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
focusManager.FocusNext()
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
inputHandler.SetKey(mod, key, func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
focusManager.FocusNext()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
app.SetInputCapture(inputHandler.Capture)
|
||||
|
||||
app.SetRoot(grid, true)
|
||||
if err := app.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
179
focus.go
Normal file
179
focus.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
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)
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package cview
|
||||
|
||||
// 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
|
||||
}
|
|
@ -463,7 +463,6 @@ func (i *InputField) Autocomplete() {
|
|||
currentEntry := -1
|
||||
i.autocompleteList.Clear()
|
||||
for index, entry := range entries {
|
||||
entry.enabled = true
|
||||
i.autocompleteList.AddItem(entry)
|
||||
if currentEntry < 0 && entry.GetMainText() == string(i.text) {
|
||||
currentEntry = index
|
||||
|
|
21
list.go
21
list.go
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
// ListItem represents an item in a List.
|
||||
type ListItem struct {
|
||||
enabled bool // Whether or not the list item is selectable.
|
||||
disabled bool // Whether or not the list item is selectable.
|
||||
mainText []byte // The main text of the list item.
|
||||
secondaryText []byte // A secondary text to be shown underneath the main text.
|
||||
shortcut rune // The key to select the list item directly, 0 if there is no shortcut.
|
||||
|
@ -25,7 +25,6 @@ type ListItem struct {
|
|||
func NewListItem(mainText string) *ListItem {
|
||||
return &ListItem{
|
||||
mainText: []byte(mainText),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -550,8 +549,6 @@ func (l *List) AddItem(item *ListItem) {
|
|||
func (l *List) InsertItem(index int, item *ListItem) {
|
||||
l.Lock()
|
||||
|
||||
item.enabled = true
|
||||
|
||||
// Shift index to range.
|
||||
if index < 0 {
|
||||
index = len(l.items) + index + 1
|
||||
|
@ -627,7 +624,7 @@ func (l *List) SetItemEnabled(index int, enabled bool) {
|
|||
defer l.Unlock()
|
||||
|
||||
item := l.items[index]
|
||||
item.enabled = enabled
|
||||
item.disabled = !enabled
|
||||
}
|
||||
|
||||
// FindItems searches the main and secondary texts for the given strings and
|
||||
|
@ -772,7 +769,7 @@ func (l *List) transform(tr Transformation) {
|
|||
}
|
||||
|
||||
item := l.items[l.currentItem]
|
||||
if item.enabled && (item.shortcut > 0 || len(item.mainText) > 0 || len(item.secondaryText) > 0) {
|
||||
if !item.disabled && (item.shortcut > 0 || len(item.mainText) > 0 || len(item.secondaryText) > 0) {
|
||||
break
|
||||
}
|
||||
|
||||
|
@ -929,7 +926,7 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
|
||||
y++
|
||||
continue
|
||||
} else if !item.enabled { // Disabled item
|
||||
} else if item.disabled {
|
||||
// Shortcuts.
|
||||
if showShortcuts && item.shortcut != 0 {
|
||||
Print(screen, []byte(fmt.Sprintf("(%c)", item.shortcut)), x-5, y, 4, AlignRight, tcell.ColorDarkSlateGray.TrueColor())
|
||||
|
@ -1086,7 +1083,7 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
} else if HitShortcut(event, Keys.Select, Keys.Select2) {
|
||||
if l.currentItem >= 0 && l.currentItem < len(l.items) {
|
||||
item := l.items[l.currentItem]
|
||||
if item.enabled {
|
||||
if !item.disabled {
|
||||
if item.selected != nil {
|
||||
l.Unlock()
|
||||
item.selected()
|
||||
|
@ -1111,7 +1108,7 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
if ch != ' ' {
|
||||
// It's not a space bar. Is it a shortcut?
|
||||
for index, item := range l.items {
|
||||
if item.enabled && item.shortcut == ch {
|
||||
if !item.disabled && item.shortcut == ch {
|
||||
// We have a shortcut.
|
||||
l.currentItem = index
|
||||
|
||||
|
@ -1241,7 +1238,7 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
|||
index := l.indexAtPoint(event.Position())
|
||||
if index != -1 {
|
||||
item := l.items[index]
|
||||
if item.enabled {
|
||||
if !item.disabled {
|
||||
l.currentItem = index
|
||||
if item.selected != nil {
|
||||
l.Unlock()
|
||||
|
@ -1279,7 +1276,7 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
|||
index := l.indexAtPoint(event.Position())
|
||||
if index != -1 {
|
||||
item := l.items[index]
|
||||
if item.enabled {
|
||||
if !item.disabled {
|
||||
l.currentItem = index
|
||||
if index != l.currentItem && l.changed != nil {
|
||||
l.Unlock()
|
||||
|
@ -1298,7 +1295,7 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
|||
index := l.indexAtY(y)
|
||||
if index >= 0 {
|
||||
item := l.items[index]
|
||||
if item.enabled {
|
||||
if !item.disabled {
|
||||
l.currentItem = index
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue