clui/base_control.go

697 lines
12 KiB
Go
Raw Normal View History

package clui
import (
мКнст "./пакКонстанты"
term "github.com/nsf/termbox-go"
"sync"
"sync/atomic"
)
// BaseControl is a base for all visible controls.
// Every new control must inherit it or implement
// the same set of methods
type BaseControl struct {
refID int64
title string
x, y int
width, height int
minW, minH int
scale int
fg, bg term.Attribute
fgActive term.Attribute
bgActive term.Attribute
tabSkip bool
disabled bool
2018-03-13 22:14:40 -07:00
hidden bool
align мКнст.Align
parent Control
inactive bool
modal bool
padX, padY int
gapX, gapY int
pack мКнст.PackType
children []Control
mtx sync.RWMutex
onActive func(active bool)
style string
clipped bool
clipper *rect
}
var (
globalRefId int64
)
func nextRefId() int64 {
return atomic.AddInt64(&globalRefId, 1)
}
//NewBaseControl --
func NewBaseControl() BaseControl {
return BaseControl{refID: nextRefId()}
}
//SetClipped --
func (c *BaseControl) SetClipped(clipped bool) {
c.clipped = clipped
}
//Clipped --
func (c *BaseControl) Clipped() bool {
return c.clipped
}
//SetStyle --
func (c *BaseControl) SetStyle(style string) {
c.style = style
}
//Style --
func (c *BaseControl) Style() string {
return c.style
}
//RefID --
func (c *BaseControl) RefID() int64 {
return c.refID
}
//Title --
func (c *BaseControl) Title() string {
return c.title
}
//SetTitle --
func (c *BaseControl) SetTitle(title string) {
c.title = title
}
//Size --
func (c *BaseControl) Size() (widht int, height int) {
return c.width, c.height
}
//SetSize --
func (c *BaseControl) SetSize(width, height int) {
if width < c.minW {
width = c.minW
}
if height < c.minH {
height = c.minH
}
if height != c.height || width != c.width {
c.height = height
c.width = width
}
}
//Pos --
func (c *BaseControl) Pos() (x int, y int) {
return c.x, c.y
}
//SetPos --
func (c *BaseControl) SetPos(x, y int) {
if c.clipped && c.clipper != nil {
cx, cy, _, _ := c.Clipper()
px, py := c.Paddings()
distX := cx - c.x
distY := cy - c.y
c.clipper.x = x + px
c.clipper.y = y + py
c.x = (x - distX) + px
c.y = (y - distY) + py
} else {
c.x = x
c.y = y
}
}
//applyConstraints --
func (c *BaseControl) applyConstraints() {
ww, hh := c.width, c.height
if ww < c.minW {
ww = c.minW
}
if hh < c.minH {
hh = c.minH
}
if hh != c.height || ww != c.width {
c.SetSize(ww, hh)
}
}
//Constraints --
func (c *BaseControl) Constraints() (minw int, minh int) {
return c.minW, c.minH
}
//SetConstraints --
func (c *BaseControl) SetConstraints(minw, minh int) {
c.minW = minw
c.minH = minh
c.applyConstraints()
}
//Active --
func (c *BaseControl) Active() bool {
return !c.inactive
}
//SetActive --
func (c *BaseControl) SetActive(active bool) {
c.inactive = !active
if c.onActive != nil {
c.onActive(active)
}
}
//OnActive --
func (c *BaseControl) OnActive(fn func(active bool)) {
c.onActive = fn
}
//TabStop --
func (c *BaseControl) TabStop() bool {
return !c.tabSkip
}
//SetTabStop --
func (c *BaseControl) SetTabStop(tabstop bool) {
c.tabSkip = !tabstop
}
//Enabled --
func (c *BaseControl) Enabled() bool {
c.mtx.RLock()
defer c.mtx.RUnlock()
return !c.disabled
}
//SetEnabled --
func (c *BaseControl) SetEnabled(enabled bool) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.disabled = !enabled
}
//Visible --
2018-03-13 22:14:40 -07:00
func (c *BaseControl) Visible() bool {
c.mtx.RLock()
defer c.mtx.RUnlock()
return !c.hidden
}
//SetVisible --
2018-03-13 22:14:40 -07:00
func (c *BaseControl) SetVisible(visible bool) {
c.mtx.Lock()
defer c.mtx.Unlock()
if visible == !c.hidden {
return
}
c.hidden = !visible
if c.parent == nil {
return
}
p := c.Parent()
for p.Parent() != nil {
p = p.Parent()
}
2018-03-15 20:56:29 -07:00
go func() {
if FindFirstActiveControl(c) != nil && !c.inactive {
2018-03-15 20:56:29 -07:00
PutEvent(Event{Type: EventKey, Key: term.KeyTab})
}
PutEvent(Event{Type: EventLayout, Target: p})
}()
2018-03-13 22:14:40 -07:00
}
//Parent --
func (c *BaseControl) Parent() Control {
return c.parent
}
//SetParent --
func (c *BaseControl) SetParent(parent Control) {
if c.parent == nil {
c.parent = parent
}
}
//Modal --
func (c *BaseControl) Modal() bool {
return c.modal
}
//SetModal --
func (c *BaseControl) SetModal(modal bool) {
c.modal = modal
}
//Paddings --
func (c *BaseControl) Paddings() (px int, py int) {
return c.padX, c.padY
}
//SetPaddings __
func (c *BaseControl) SetPaddings(px, py int) {
if px >= 0 {
c.padX = px
}
if py >= 0 {
c.padY = py
}
}
//Gaps --
func (c *BaseControl) Gaps() (dx int, dy int) {
return c.gapX, c.gapY
}
//SetGaps --
func (c *BaseControl) SetGaps(dx, dy int) {
if dx >= 0 {
c.gapX = dx
}
if dy >= 0 {
c.gapY = dy
}
}
//Pack --
func (c *BaseControl) Pack() мКнст.PackType {
return c.pack
}
//SetPack --
func (c *BaseControl) SetPack(pack мКнст.PackType) {
c.pack = pack
}
//Scale --
func (c *BaseControl) Scale() int {
return c.scale
}
//SetScale --
func (c *BaseControl) SetScale(scale int) {
if scale >= 0 {
c.scale = scale
}
}
//Align --
func (c *BaseControl) Align() мКнст.Align {
return c.align
}
//SetAlign --
func (c *BaseControl) SetAlign(align мКнст.Align) {
c.align = align
}
//TextColor --
func (c *BaseControl) TextColor() term.Attribute {
return c.fg
}
//SetTextColor --
func (c *BaseControl) SetTextColor(clr term.Attribute) {
c.fg = clr
}
//BackColor --
func (c *BaseControl) BackColor() term.Attribute {
return c.bg
}
//SetBackColor --
func (c *BaseControl) SetBackColor(clr term.Attribute) {
c.bg = clr
}
//childCount --
2018-03-13 22:14:40 -07:00
func (c *BaseControl) childCount() int {
cnt := 0
for _, child := range c.children {
if child.Visible() {
cnt++
}
}
return cnt
}
//ResizeChildren --
func (c *BaseControl) ResizeChildren() {
2018-03-13 22:14:40 -07:00
children := c.childCount()
if children == 0 {
return
}
fullWidth := c.width - 2*c.padX
fullHeight := c.height - 2*c.padY
if c.pack == Horizontal {
2018-03-13 22:14:40 -07:00
fullWidth -= (children - 1) * c.gapX
} else {
2018-03-13 22:14:40 -07:00
fullHeight -= (children - 1) * c.gapY
}
totalSc := c.ChildrenScale()
minWidth := 0
minHeight := 0
for _, child := range c.children {
2018-03-13 22:14:40 -07:00
if !child.Visible() {
continue
}
cw, ch := child.MinimalSize()
if c.pack == Horizontal {
minWidth += cw
} else {
minHeight += ch
}
}
aStep := 0
diff := fullWidth - minWidth
if c.pack == Vertical {
diff = fullHeight - minHeight
}
if totalSc > 0 {
aStep = int(float32(diff) / float32(totalSc))
}
for _, ctrl := range c.children {
2018-03-13 22:14:40 -07:00
if !ctrl.Visible() {
continue
}
tw, th := ctrl.MinimalSize()
sc := ctrl.Scale()
d := int(ctrl.Scale() * aStep)
if c.pack == Horizontal {
if sc != 0 {
if sc == totalSc {
tw += diff
d = diff
} else {
tw += d
}
}
th = fullHeight
} else {
if sc != 0 {
if sc == totalSc {
th += diff
d = diff
} else {
th += d
}
}
tw = fullWidth
}
diff -= d
totalSc -= sc
ctrl.SetSize(tw, th)
ctrl.ResizeChildren()
}
}
//AddChild --
func (c *BaseControl) AddChild(control Control) {
if c.children == nil {
c.children = make([]Control, 1)
c.children[0] = control
} else {
if c.ChildExists(control) {
panic("Double adding a child")
}
c.children = append(c.children, control)
}
var ctrl Control
var mainCtrl Control
ctrl = c
for ctrl != nil {
ww, hh := ctrl.MinimalSize()
cw, ch := ctrl.Size()
if ww > cw || hh > ch {
if ww > cw {
cw = ww
}
if hh > ch {
ch = hh
}
ctrl.SetConstraints(cw, ch)
}
if ctrl.Parent() == nil {
mainCtrl = ctrl
}
ctrl = ctrl.Parent()
}
if mainCtrl != nil {
mainCtrl.ResizeChildren()
mainCtrl.PlaceChildren()
}
if c.clipped && c.clipper == nil {
c.setClipper()
}
}
//Children --
func (c *BaseControl) Children() []Control {
child := make([]Control, len(c.children))
copy(child, c.children)
return child
}
//ChildExists --
func (c *BaseControl) ChildExists(control Control) bool {
if len(c.children) == 0 {
return false
}
for _, ctrl := range c.children {
if ctrl == control {
return true
}
}
return false
}
//ChildrenScale --
func (c *BaseControl) ChildrenScale() int {
2018-03-13 22:14:40 -07:00
if c.childCount() == 0 {
return c.scale
}
total := 0
for _, ctrl := range c.children {
2018-03-13 22:14:40 -07:00
if ctrl.Visible() {
total += ctrl.Scale()
}
}
return total
}
//MinimalSize --
func (c *BaseControl) MinimalSize() (w int, h int) {
2018-03-13 22:14:40 -07:00
children := c.childCount()
if children == 0 {
return c.minW, c.minH
}
totalX := 2 * c.padX
totalY := 2 * c.padY
if c.pack == Vertical {
2018-03-13 22:14:40 -07:00
totalY += (children - 1) * c.gapY
} else {
2018-03-13 22:14:40 -07:00
totalX += (children - 1) * c.gapX
}
for _, ctrl := range c.children {
if ctrl.Clipped() {
continue
}
2018-03-13 22:14:40 -07:00
if !ctrl.Visible() {
continue
}
ww, hh := ctrl.MinimalSize()
if c.pack == Vertical {
totalY += hh
if ww+2*c.padX > totalX {
totalX = ww + 2*c.padX
}
} else {
totalX += ww
if hh+2*c.padY > totalY {
totalY = hh + 2*c.padY
}
}
}
if totalX < c.minW {
totalX = c.minW
}
if totalY < c.minH {
totalY = c.minH
}
return totalX, totalY
}
//Draw --
func (c *BaseControl) Draw() {
panic("BaseControl Draw Called")
}
//DrawChildren --
func (c *BaseControl) DrawChildren() {
2018-03-13 22:14:40 -07:00
if c.hidden {
return
}
PushClip()
defer PopClip()
cp := ClippedParent(c)
var cTarget Control
cTarget = c
if cp != nil {
cTarget = cp
}
x, y, w, h := cTarget.Clipper()
SetClipRect(x, y, w, h)
for _, child := range c.children {
child.Draw()
}
}
//Clipper --
func (c *BaseControl) Clipper() (int, int, int, int) {
clipped := ClippedParent(c)
if clipped == nil || (c.clipped && c.clipper != nil) {
return c.clipper.x, c.clipper.y, c.clipper.w, c.clipper.h
}
return CalcClipper(c)
}
func (c *BaseControl) setClipper() {
x, y, w, h := CalcClipper(c)
c.clipper = &rect{x: x, y: y, w: w, h: h}
}
2019-05-07 10:29:34 +03:00
func (c *BaseControl) HitTest(x, y int) мКнст.HitResult {
if x > c.x && x < c.x+c.width-1 &&
y > c.y && y < c.y+c.height-1 {
return HitInside
}
if (x == c.x || x == c.x+c.width-1) &&
y >= c.y && y < c.y+c.height {
return HitBorder
}
if (y == c.y || y == c.y+c.height-1) &&
x >= c.x && x < c.x+c.width {
return HitBorder
}
return HitOutside
}
//ProcessEvent --
func (c *BaseControl) ProcessEvent(ev мКнст.Event) bool {
return SendEventToChild(c, ev)
}
//PlaceChildren --
func (c *BaseControl) PlaceChildren() {
2018-03-13 22:14:40 -07:00
children := c.childCount()
if c.children == nil || children == 0 {
return
}
xx, yy := c.x+c.padX, c.y+c.padY
for _, ctrl := range c.children {
2018-03-13 22:14:40 -07:00
if !ctrl.Visible() {
continue
}
2018-03-13 22:14:40 -07:00
ctrl.SetPos(xx, yy)
ww, hh := ctrl.Size()
if c.pack == Vertical {
yy += c.gapY + hh
} else {
xx += c.gapX + ww
}
ctrl.PlaceChildren()
}
}
// ActiveColors return the attrubutes for the controls when it
// is active: text and background colors
func (c *BaseControl) ActiveColors() (term.Attribute, term.Attribute) {
return c.fgActive, c.bgActive
}
// SetActiveTextColor changes text color of the active control
func (c *BaseControl) SetActiveTextColor(clr term.Attribute) {
c.fgActive = clr
}
// SetActiveBackColor changes background color of the active control
func (c *BaseControl) SetActiveBackColor(clr term.Attribute) {
c.bgActive = clr
}
func (c *BaseControl) removeChild(control Control) {
children := []Control{}
for _, child := range c.children {
if child.RefID() == control.RefID() {
continue
}
children = append(children, child)
}
c.children = nil
for _, child := range children {
c.AddChild(child)
}
}
// Destroy removes an object from its parental chain
func (c *BaseControl) Destroy() {
c.parent.removeChild(c)
c.parent.SetConstraints(0, 0)
}