mirror of
https://github.com/VladimirMarkelov/clui.git
synced 2025-04-28 13:48:50 +08:00

Currently when setting a control visible no matter if this control has active children new KeyTab events will be dispatched. This results in errors when an application has explicitly set the active control - i.e with clui.ActivateControl(). In this case the expected behavior is to have that control active instead of the next one, since we are not considering the the children state then we always emit the KeyTab event resulting into a focus/active status change. This patch changes this behavior by querying the children's status and checking if there is a child with active state set and only triggering the KeyTab event when no child is active. Signed-off-by: Leandro Dorileo <leandro.maciel.dorileo@intel.com>
270 lines
5.9 KiB
Go
270 lines
5.9 KiB
Go
package clui
|
|
|
|
import (
|
|
term "github.com/nsf/termbox-go"
|
|
)
|
|
|
|
// ThumbPosition returns a scrollbar thumb position depending
|
|
// on currently active item(itemNo), total number of items
|
|
// (itemCount), and length/height of the scrollbar(length)
|
|
// including arrows. Returns position in interval of (1..lenght-2)
|
|
// or -1 if the thumb is not visible
|
|
func ThumbPosition(itemNo, itemCount, length int) int {
|
|
length -= 2
|
|
if itemNo < 0 {
|
|
return -1
|
|
}
|
|
if itemNo >= itemCount-1 {
|
|
return length - 1
|
|
}
|
|
|
|
if length < 4 {
|
|
return 0
|
|
}
|
|
|
|
ydiff := int(float32(itemNo) / float32(itemCount-1.0) * float32(length-1))
|
|
return ydiff
|
|
}
|
|
|
|
// ItemByThumbPosition calculates item number by scrollbar
|
|
// thumb position. Position - thumb position inside scrollbar,
|
|
// itemCount - total number of items, lenght - lenght or heigth
|
|
// of scrollbar. Return -1 if it is not possible to calculate:
|
|
// e.g, itemCount equals zero
|
|
func ItemByThumbPosition(position, itemCount, length int) int {
|
|
length -= 2
|
|
if position < 1 {
|
|
return -1
|
|
}
|
|
if itemCount < 1 {
|
|
return -1
|
|
}
|
|
if itemCount == 1 {
|
|
return 0
|
|
}
|
|
|
|
newPos := int(float32(itemCount-1)*float32(position-1)/float32(length-1) + 0.9)
|
|
|
|
if newPos < 0 {
|
|
newPos = 0
|
|
} else if newPos >= itemCount {
|
|
newPos = itemCount - 1
|
|
}
|
|
|
|
return newPos
|
|
}
|
|
|
|
// ChildAt returns the children of parent control that is at absolute
|
|
// coordinates x, y. Returns nil if x, y are outside parent control and
|
|
// returns parent if no child is at x, y
|
|
func ChildAt(parent Control, x, y int) Control {
|
|
px, py := parent.Pos()
|
|
pw, ph := parent.Size()
|
|
if px > x || py > y || px+pw <= x || py+ph <= y {
|
|
return nil
|
|
}
|
|
|
|
if len(parent.Children()) == 0 {
|
|
return parent
|
|
}
|
|
|
|
var ctrl Control
|
|
ctrl = parent
|
|
for _, child := range parent.Children() {
|
|
check := ChildAt(child, x, y)
|
|
if check != nil {
|
|
ctrl = check
|
|
break
|
|
}
|
|
}
|
|
|
|
return ctrl
|
|
}
|
|
|
|
// DeactivateControls makes all children of parent inactive
|
|
func DeactivateControls(parent Control) {
|
|
for _, ctrl := range parent.Children() {
|
|
if ctrl.Active() {
|
|
ctrl.SetActive(false)
|
|
ctrl.ProcessEvent(Event{Type: EventActivate, X: 0})
|
|
}
|
|
|
|
DeactivateControls(ctrl)
|
|
}
|
|
}
|
|
|
|
// ActivateControl makes control active and disables all other children of
|
|
// the parent. Returns true if control was found and activated
|
|
func ActivateControl(parent, control Control) bool {
|
|
DeactivateControls(parent)
|
|
res := false
|
|
ctrl := FindChild(parent, control)
|
|
if ctrl != nil {
|
|
res = true
|
|
if !ctrl.Active() {
|
|
ctrl.ProcessEvent(Event{Type: EventActivate, X: 1})
|
|
ctrl.SetActive(true)
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// FindChild returns control if it is a child of the parent and nil otherwise
|
|
func FindChild(parent, control Control) Control {
|
|
var res Control
|
|
|
|
if parent == control {
|
|
return parent
|
|
}
|
|
|
|
for _, ctrl := range parent.Children() {
|
|
if ctrl == control {
|
|
res = ctrl
|
|
break
|
|
}
|
|
|
|
res = FindChild(ctrl, control)
|
|
if res != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// IsMouseClickEvent returns if a user action can be treated as mouse click.
|
|
func IsMouseClickEvent(ev Event) bool {
|
|
if ev.Type == EventClick {
|
|
return true
|
|
}
|
|
if ev.Type == EventMouse && ev.Key == term.MouseLeft {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// FindFirstControl returns the first child for that fn returns true.
|
|
// The function is used to find active or tab-stop control
|
|
func FindFirstControl(parent Control, fn func(Control) bool) Control {
|
|
linear := getLinearControlList(parent, fn)
|
|
if len(linear) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return linear[0]
|
|
}
|
|
|
|
// FindLastControl returns the first child for that fn returns true.
|
|
// The function is used by TAB processing method if a user goes backwards
|
|
// with TAB key - not supported now
|
|
func FindLastControl(parent Control, fn func(Control) bool) Control {
|
|
linear := getLinearControlList(parent, fn)
|
|
|
|
if len(linear) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return linear[len(linear)-1]
|
|
}
|
|
|
|
// ActiveControl returns the active child of the parent or nil if no child is
|
|
// active
|
|
func ActiveControl(parent Control) Control {
|
|
fnActive := func(c Control) bool {
|
|
return c.Active()
|
|
}
|
|
return FindFirstControl(parent, fnActive)
|
|
}
|
|
|
|
// FindFirstActiveControl returns the first active control of a parent
|
|
func FindFirstActiveControl(parent Control) Control {
|
|
for _, curr := range getLinearControlList(parent, nil) {
|
|
if curr.Active() {
|
|
return curr
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getLinearControlList(parent Control, fn func(Control) bool) []Control {
|
|
result := []Control{}
|
|
|
|
for _, curr := range parent.Children() {
|
|
if fn(curr) {
|
|
result = append(result, curr)
|
|
}
|
|
|
|
if len(curr.Children()) == 0 {
|
|
continue
|
|
}
|
|
|
|
ch := getLinearControlList(curr, fn)
|
|
if len(ch) != 0 {
|
|
result = append(result, ch...)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// NextControl returns the next or previous child (depends on next parameter)
|
|
// that has tab-stop feature on. Used by library when processing TAB key
|
|
func NextControl(parent Control, curr Control, next bool) Control {
|
|
fnTab := func(c Control) bool {
|
|
return c.TabStop() && c.Visible() && c.Enabled()
|
|
}
|
|
|
|
linear := getLinearControlList(parent, fnTab)
|
|
|
|
var pIndex, nIndex int
|
|
|
|
for i, ch := range linear {
|
|
if ch != curr {
|
|
continue
|
|
}
|
|
|
|
pIndex = i - 1
|
|
nIndex = i + 1
|
|
break
|
|
}
|
|
|
|
if nIndex > len(linear)-1 {
|
|
nIndex = 0
|
|
}
|
|
|
|
if pIndex < 0 {
|
|
pIndex = len(linear) - 1
|
|
}
|
|
|
|
if next {
|
|
return linear[nIndex]
|
|
} else {
|
|
return linear[pIndex]
|
|
}
|
|
}
|
|
|
|
// SendEventToChild tries to find a child control that should recieve the evetn
|
|
// For mouse click events it looks for a control at coordinates of event,
|
|
// makes it active, and then sends the event to it.
|
|
// If it is not mouse click event then it looks for the first active child and
|
|
// sends the event to it if it is not nil
|
|
func SendEventToChild(parent Control, ev Event) bool {
|
|
var child Control
|
|
if IsMouseClickEvent(ev) {
|
|
child = ChildAt(parent, ev.X, ev.Y)
|
|
if child != nil && !child.Active() {
|
|
ActivateControl(parent, child)
|
|
}
|
|
} else {
|
|
child = ActiveControl(parent)
|
|
}
|
|
|
|
if child != nil && child != parent {
|
|
return child.ProcessEvent(ev)
|
|
}
|
|
|
|
return false
|
|
}
|