304 lines
6.8 KiB
Go
304 lines
6.8 KiB
Go
package clui
|
|
|
|
import (
|
|
term "github.com/nsf/termbox-go"
|
|
"strings"
|
|
)
|
|
|
|
// CalculateMinimalSize return the minimal width and height
|
|
// of the Control based on control's children minial sizes
|
|
// and control's paddings
|
|
func CalculateMinimalSize(c Control) (int, int) {
|
|
w, h := c.Constraints()
|
|
|
|
kids := c.Children()
|
|
if len(kids) == 0 {
|
|
return w, h
|
|
}
|
|
|
|
w, h = 0, 0
|
|
pk := c.Pack()
|
|
top, side, dx, dy := c.Paddings()
|
|
|
|
for _, child := range kids {
|
|
cw, ch := child.Constraints()
|
|
|
|
if pk == Vertical {
|
|
if h != 0 {
|
|
h += dy
|
|
}
|
|
h += ch
|
|
if cw > w {
|
|
w = cw
|
|
}
|
|
} else {
|
|
if w != 0 {
|
|
w += dx
|
|
}
|
|
w += cw
|
|
if ch > h {
|
|
h = ch
|
|
}
|
|
}
|
|
}
|
|
|
|
w += side * 2
|
|
h += top * 2
|
|
|
|
return w, h
|
|
}
|
|
|
|
// CalculateTotalScale return sum of all
|
|
// control children scale coefficients
|
|
func CalculateTotalScale(c Control) int {
|
|
scale := 0
|
|
|
|
for _, child := range c.Children() {
|
|
sc := child.Scale()
|
|
if sc > DoNotScale {
|
|
scale += sc
|
|
}
|
|
}
|
|
|
|
return scale
|
|
}
|
|
|
|
// RepositionControls calculates position of all
|
|
// control children and moves them to a new positions.
|
|
// Call the funtion after the control is resized.
|
|
// dx and dy are position of the container control
|
|
// relative to parent View. Initial calculation start
|
|
// point is 0, 0. After calculation all childen are
|
|
// moved by dx and dy
|
|
func RepositionControls(dx, dy int, c Control) {
|
|
if len(c.Children()) == 0 {
|
|
return
|
|
}
|
|
|
|
scale := CalculateTotalScale(c)
|
|
|
|
minW, minH := c.Constraints()
|
|
width, height := c.Size()
|
|
xDiff, yDiff := width-minW, height-minH
|
|
pk := c.Pack()
|
|
top, side, xx, yy := c.Paddings()
|
|
|
|
calcShift := func(height int, ctrl Control) int {
|
|
_, h := ctrl.Size()
|
|
if h < height {
|
|
return int((height - h) / 2)
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
currX, currY := top, side
|
|
if pk == Vertical {
|
|
delta := float64(yDiff) / float64(scale)
|
|
curr := 0.0
|
|
for _, child := range c.Children() {
|
|
cs := child.Scale()
|
|
cw, ch := child.Constraints()
|
|
if cs != 0 {
|
|
var dh int
|
|
if cs == scale {
|
|
dh = yDiff
|
|
} else {
|
|
curr += float64(cs) * delta
|
|
dh = int(curr)
|
|
curr = curr - float64(dh)
|
|
}
|
|
scale -= cs
|
|
ch += dh
|
|
yDiff -= dh
|
|
}
|
|
cw = width - 2*side
|
|
child.SetSize(cw, ch)
|
|
|
|
yShift := calcShift(ch, child)
|
|
child.SetPos(currX+dx, currY+dy+yShift)
|
|
currY += ch + yy
|
|
}
|
|
} else {
|
|
delta := float64(xDiff) / float64(scale)
|
|
curr := 0.0
|
|
for _, child := range c.Children() {
|
|
cs := child.Scale()
|
|
cw, ch := child.Constraints()
|
|
if cs != 0 {
|
|
var dw int
|
|
if cs == scale {
|
|
dw = xDiff
|
|
} else {
|
|
curr += float64(cs) * delta
|
|
dw = int(curr)
|
|
curr = curr - float64(dw)
|
|
}
|
|
scale -= cs
|
|
cw += dw
|
|
// log.Printf("Child width %v: %v", child.Title(), cw)
|
|
xDiff -= dw
|
|
}
|
|
ch = height - 2*top
|
|
child.SetSize(cw, ch)
|
|
|
|
yShift := calcShift(ch, child)
|
|
child.SetPos(currX+dx, currY+dy+yShift)
|
|
// log.Printf("ChildH %v pos %v:%v width: %v", child.Title(), currX+dx, currY+dy, cw)
|
|
currX += cw + xx
|
|
}
|
|
}
|
|
|
|
for _, child := range c.Children() {
|
|
childX, childY := child.Pos()
|
|
RepositionControls(childX, childY, child)
|
|
}
|
|
}
|
|
|
|
// RealColor returns attribute that should be applied to an
|
|
// object. By default all attributes equal ColorDefault and
|
|
// the real color should be retrieved from the current theme.
|
|
// Attribute selection work this way: if color is not ColorDefault,
|
|
// it is returned as is, otherwise the function tries to load
|
|
// color from the theme.
|
|
// tm - the theme to retrieve color from
|
|
// clr - current object color
|
|
// id - color ID in theme
|
|
func RealColor(tm Theme, clr term.Attribute, id string) term.Attribute {
|
|
if clr != ColorDefault {
|
|
return clr
|
|
}
|
|
|
|
if clr == ColorDefault {
|
|
return tm.SysColor(id)
|
|
}
|
|
|
|
return clr
|
|
}
|
|
|
|
// StringToColor returns attribute by its string description.
|
|
// Description is the list of attributes separated with
|
|
// spaces, plus or pipe symbols. You can use 8 base colors:
|
|
// black, white, red, green, blue, magenta, yellow, cyan
|
|
// and a few modifiers:
|
|
// bold or bright, underline or underlined, reverse
|
|
// Note: some terminals do not support all modifiers, e.g,
|
|
// Windows one understands only bold/bright - it makes the
|
|
// color brighter with the modidierA
|
|
// Examples: "red bold", "green+underline+bold"
|
|
func StringToColor(str string) term.Attribute {
|
|
var parts []string
|
|
if strings.ContainsRune(str, '+') {
|
|
parts = strings.Split(str, "+")
|
|
} else if strings.ContainsRune(str, '|') {
|
|
parts = strings.Split(str, "|")
|
|
} else if strings.ContainsRune(str, ' ') {
|
|
parts = strings.Split(str, " ")
|
|
} else {
|
|
parts = append(parts, str)
|
|
}
|
|
|
|
var cmap = map[string]term.Attribute{
|
|
"default": term.ColorDefault,
|
|
"black": term.ColorBlack,
|
|
"red": term.ColorRed,
|
|
"green": term.ColorGreen,
|
|
"yellow": term.ColorYellow,
|
|
"blue": term.ColorBlue,
|
|
"magenta": term.ColorMagenta,
|
|
"cyan": term.ColorCyan,
|
|
"white": term.ColorWhite,
|
|
"bold": term.AttrBold,
|
|
"bright": term.AttrBold, // windows make color brighter when it is bold
|
|
"underline": term.AttrUnderline,
|
|
"underlined": term.AttrUnderline,
|
|
"reverse": term.AttrReverse,
|
|
}
|
|
|
|
var clr term.Attribute
|
|
for _, item := range parts {
|
|
item = strings.Trim(item, " ")
|
|
item = strings.ToLower(item)
|
|
|
|
c, ok := cmap[item]
|
|
if ok {
|
|
clr |= c
|
|
}
|
|
}
|
|
|
|
return clr
|
|
}
|
|
|
|
// ColorToString returns string representation of the attribute
|
|
func ColorToString(attr term.Attribute) string {
|
|
var out string
|
|
colors := []string{
|
|
"", "black", "red", "green", "yellow",
|
|
"blue", "magenta", "cyan", "white"}
|
|
|
|
rawClr := attr & 15
|
|
if rawClr < 8 {
|
|
out += colors[rawClr] + " "
|
|
}
|
|
|
|
if attr&term.AttrBold != 0 {
|
|
out += "bold "
|
|
}
|
|
if attr&term.AttrUnderline != 0 {
|
|
out += "underline "
|
|
}
|
|
if attr&term.AttrReverse != 0 {
|
|
out += "reverse "
|
|
}
|
|
|
|
return strings.TrimSpace(out)
|
|
}
|
|
|
|
// 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 {
|
|
if itemNo < 0 {
|
|
return -1
|
|
}
|
|
if itemNo >= itemCount-1 {
|
|
return length - 2
|
|
}
|
|
|
|
if length < 4 {
|
|
return 1
|
|
}
|
|
|
|
ydiff := int(float32(itemNo) / float32(itemCount-1.0) * float32(length-3))
|
|
return ydiff + 1
|
|
}
|
|
|
|
// 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 {
|
|
if position < 1 {
|
|
return -1
|
|
}
|
|
if itemCount < 1 {
|
|
return -1
|
|
}
|
|
if itemCount == 1 {
|
|
return 1
|
|
}
|
|
|
|
newPos := int(float32(itemCount-1)*float32(position-1)/float32(length-3) + 0.9)
|
|
|
|
if newPos < 0 {
|
|
newPos = 0
|
|
} else if newPos >= itemCount {
|
|
newPos = itemCount - 1
|
|
}
|
|
|
|
return newPos
|
|
}
|