282 lines
6.0 KiB
Go
282 lines
6.0 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 {
|
|
for _, child := range parent.Children() {
|
|
if fn(child) {
|
|
return child
|
|
}
|
|
|
|
ch := FindFirstControl(child, fn)
|
|
if ch != nil {
|
|
return ch
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 {
|
|
var last Control
|
|
for _, child := range parent.Children() {
|
|
if fn(child) {
|
|
last = child
|
|
}
|
|
|
|
ch := FindLastControl(child, fn)
|
|
if ch != nil {
|
|
last = ch
|
|
}
|
|
}
|
|
|
|
return last
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
func _nextControl(parent Control, curr, prev Control, foundPrev, next bool) (bool, Control) {
|
|
found := foundPrev
|
|
if parent == curr {
|
|
if next {
|
|
found = true
|
|
} else {
|
|
return false, prev
|
|
}
|
|
}
|
|
|
|
p := prev
|
|
for _, ctrl := range parent.Children() {
|
|
if ctrl == curr {
|
|
if next {
|
|
found = true
|
|
continue
|
|
} else {
|
|
return found, p
|
|
}
|
|
}
|
|
|
|
if ctrl.Enabled() && ctrl.TabStop() {
|
|
if found {
|
|
return found, ctrl
|
|
} else if !next {
|
|
p = ctrl
|
|
}
|
|
}
|
|
|
|
fnd, nn := _nextControl(ctrl, curr, p, found, next)
|
|
if nn != nil {
|
|
return fnd, nn
|
|
}
|
|
}
|
|
|
|
return found, nil
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
var defControl Control
|
|
if next {
|
|
defControl = FindFirstControl(parent, fnTab)
|
|
} else {
|
|
defControl = FindLastControl(parent, fnTab)
|
|
}
|
|
|
|
if defControl == nil {
|
|
return nil
|
|
}
|
|
if curr == nil {
|
|
return defControl
|
|
}
|
|
|
|
_, cNext := _nextControl(parent, curr, nil, false, next)
|
|
if cNext == nil {
|
|
cNext = defControl
|
|
}
|
|
|
|
return cNext
|
|
}
|
|
|
|
// 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
|
|
}
|