mirror of
https://github.com/VladimirMarkelov/clui.git
synced 2025-04-26 13:49:01 +08:00
It is not complete patch because there are more places to fix. At least all demos and full-featured application termfb2 works without race condition warnings.
This commit is contained in:
parent
bc8d838e86
commit
2c06a01186
110
barchart.go
110
barchart.go
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
xs "github.com/huandu/xstrings"
|
||||
term "github.com/nsf/termbox-go"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// BarData is info about one bar in the chart. Every
|
||||
@ -59,10 +60,10 @@ type BarChart struct {
|
||||
BaseControl
|
||||
data []BarData
|
||||
autosize bool
|
||||
gap int
|
||||
barWidth int
|
||||
legendWidth int
|
||||
valueWidth int
|
||||
gap int32
|
||||
barWidth int32
|
||||
legendWidth int32
|
||||
valueWidth int32
|
||||
showMarks bool
|
||||
showTitles bool
|
||||
onDrawCell func(*BarDataCell)
|
||||
@ -105,6 +106,9 @@ func CreateBarChart(parent Control, w, h int, scale int) *BarChart {
|
||||
|
||||
// Repaint draws the control on its View surface
|
||||
func (b *BarChart) Draw() {
|
||||
b.mtx.RLock()
|
||||
defer b.mtx.RUnlock()
|
||||
|
||||
PushAttributes()
|
||||
defer PopAttributes()
|
||||
|
||||
@ -214,7 +218,7 @@ func (b *BarChart) drawBars() {
|
||||
DrawRawText(b.x+pos+shift, b.y+h+1, s)
|
||||
}
|
||||
|
||||
pos += barW + b.gap
|
||||
pos += barW + int(b.BarGap())
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,7 +246,7 @@ func (b *BarChart) drawLegend() {
|
||||
SetTextColor(d.Fg)
|
||||
SetBackColor(d.Bg)
|
||||
PutChar(b.x+pos+width, b.y+idx, c)
|
||||
s := CutText(fmt.Sprintf(" - %v", d.Title), b.legendWidth)
|
||||
s := CutText(fmt.Sprintf(" - %v", d.Title), int(b.LegendWidth()))
|
||||
SetTextColor(fg)
|
||||
SetBackColor(bg)
|
||||
DrawRawText(b.x+pos+width+1, b.y+idx, s)
|
||||
@ -250,7 +254,8 @@ func (b *BarChart) drawLegend() {
|
||||
}
|
||||
|
||||
func (b *BarChart) drawValues() {
|
||||
if b.valueWidth <= 0 {
|
||||
valVal := int(b.ValueWidth())
|
||||
if valVal <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@ -266,11 +271,11 @@ func (b *BarChart) drawValues() {
|
||||
}
|
||||
|
||||
dy := 0
|
||||
format := fmt.Sprintf("%%%v.2f", b.valueWidth)
|
||||
format := fmt.Sprintf("%%%v.2f", valVal)
|
||||
for dy < h-1 {
|
||||
v := float64(h-dy) / float64(h) * max
|
||||
s := fmt.Sprintf(format, v)
|
||||
s = CutText(s, b.valueWidth)
|
||||
s = CutText(s, valVal)
|
||||
DrawRawText(b.x, b.y+dy, s)
|
||||
|
||||
dy += 2
|
||||
@ -278,7 +283,7 @@ func (b *BarChart) drawValues() {
|
||||
}
|
||||
|
||||
func (b *BarChart) drawRulers() {
|
||||
if b.valueWidth <= 0 && b.legendWidth <= 0 && !b.showTitles {
|
||||
if int(b.ValueWidth()) <= 0 && int(b.LegendWidth()) <= 0 && !b.showTitles {
|
||||
return
|
||||
}
|
||||
|
||||
@ -314,13 +319,15 @@ func (b *BarChart) calculateBarArea() (int, int) {
|
||||
w := b.width
|
||||
pos := 0
|
||||
|
||||
if b.valueWidth < w/2 {
|
||||
w = w - b.valueWidth - 1
|
||||
pos = b.valueWidth + 1
|
||||
valVal := int(b.ValueWidth())
|
||||
if valVal < w/2 {
|
||||
w = w - valVal - 1
|
||||
pos = valVal + 1
|
||||
}
|
||||
|
||||
if b.legendWidth < w/2 {
|
||||
w -= b.legendWidth
|
||||
legVal := int(b.LegendWidth())
|
||||
if legVal < w/2 {
|
||||
w -= legVal
|
||||
}
|
||||
|
||||
return pos, w
|
||||
@ -332,24 +339,28 @@ func (b *BarChart) calculateBarWidth() int {
|
||||
}
|
||||
|
||||
if !b.autosize {
|
||||
return b.barWidth
|
||||
return int(b.MinBarWidth())
|
||||
}
|
||||
|
||||
w := b.width
|
||||
if b.valueWidth < w/2 {
|
||||
w = w - b.valueWidth - 1
|
||||
legVal := int(b.LegendWidth())
|
||||
valVal := int(b.ValueWidth())
|
||||
if valVal < w/2 {
|
||||
w = w - valVal - 1
|
||||
}
|
||||
if b.legendWidth < w/2 {
|
||||
w -= b.legendWidth
|
||||
if legVal < w/2 {
|
||||
w -= legVal
|
||||
}
|
||||
|
||||
dataCount := len(b.data)
|
||||
minSize := dataCount*b.barWidth + (dataCount-1)*b.gap
|
||||
gapVal := int(b.BarGap())
|
||||
barVal := int(b.MinBarWidth())
|
||||
minSize := dataCount*barVal + (dataCount-1)*gapVal
|
||||
if minSize >= w {
|
||||
return b.barWidth
|
||||
return barVal
|
||||
}
|
||||
|
||||
sz := (w - (dataCount-1)*b.gap) / dataCount
|
||||
sz := (w - (dataCount-1)*gapVal) / dataCount
|
||||
if sz == 0 {
|
||||
sz = 1
|
||||
}
|
||||
@ -383,16 +394,25 @@ func (b *BarChart) calculateMultiplier() (float64, float64) {
|
||||
|
||||
// AddData appends a new bar to a chart
|
||||
func (b *BarChart) AddData(val BarData) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.data = append(b.data, val)
|
||||
}
|
||||
|
||||
// ClearData removes all bar from chart
|
||||
func (b *BarChart) ClearData() {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.data = make([]BarData, 0)
|
||||
}
|
||||
|
||||
// SetData assign a new bar list to a chart
|
||||
func (b *BarChart) SetData(data []BarData) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.data = make([]BarData, len(data))
|
||||
copy(b.data, data)
|
||||
}
|
||||
@ -410,39 +430,42 @@ func (b *BarChart) AutoSize() bool {
|
||||
// SetAutoSize enables or disables automatic bar
|
||||
// width calculation
|
||||
func (b *BarChart) SetAutoSize(auto bool) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.autosize = auto
|
||||
}
|
||||
|
||||
// Gap returns width of visual gap between two adjacent bars
|
||||
func (b *BarChart) BarGap() int {
|
||||
return b.gap
|
||||
func (b *BarChart) BarGap() int32 {
|
||||
return atomic.LoadInt32(&b.gap)
|
||||
}
|
||||
|
||||
// SetGap sets the space width between two adjacent bars
|
||||
func (b *BarChart) SetBarGap(gap int) {
|
||||
b.gap = gap
|
||||
func (b *BarChart) SetBarGap(gap int32) {
|
||||
atomic.StoreInt32(&b.gap, gap)
|
||||
}
|
||||
|
||||
// MinBarWidth returns current minimal bar width
|
||||
func (b *BarChart) MinBarWidth() int {
|
||||
return b.barWidth
|
||||
func (b *BarChart) MinBarWidth() int32 {
|
||||
return atomic.LoadInt32(&b.barWidth)
|
||||
}
|
||||
|
||||
// SetMinBarWidth changes the minimal bar width
|
||||
func (b *BarChart) SetMinBarWidth(size int) {
|
||||
b.barWidth = size
|
||||
func (b *BarChart) SetMinBarWidth(size int32) {
|
||||
atomic.StoreInt32(&b.barWidth, size)
|
||||
}
|
||||
|
||||
// ValueWidth returns the width of the area at the left of
|
||||
// chart used to draw values. Set it to 0 to turn off the
|
||||
// value panel
|
||||
func (b *BarChart) ValueWidth() int {
|
||||
return b.valueWidth
|
||||
func (b *BarChart) ValueWidth() int32 {
|
||||
return atomic.LoadInt32(&b.valueWidth)
|
||||
}
|
||||
|
||||
// SetValueWidth changes width of the value panel on the left
|
||||
func (b *BarChart) SetValueWidth(width int) {
|
||||
b.valueWidth = width
|
||||
func (b *BarChart) SetValueWidth(width int32) {
|
||||
atomic.StoreInt32(&b.valueWidth, width)
|
||||
}
|
||||
|
||||
// ShowTitles returns if chart displays horizontal axis and
|
||||
@ -453,18 +476,21 @@ func (b *BarChart) ShowTitles() bool {
|
||||
|
||||
// SetShowTitles turns on and off horizontal axis and bar titles
|
||||
func (b *BarChart) SetShowTitles(show bool) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.showTitles = show
|
||||
}
|
||||
|
||||
// LegendWidth returns width of chart legend displayed at the
|
||||
// right side of the chart. Set it to 0 to disable legend
|
||||
func (b *BarChart) LegendWidth() int {
|
||||
return b.legendWidth
|
||||
func (b *BarChart) LegendWidth() int32 {
|
||||
return atomic.LoadInt32(&b.legendWidth)
|
||||
}
|
||||
|
||||
// SetLegendWidth sets new legend panel width
|
||||
func (b *BarChart) SetLegendWidth(width int) {
|
||||
b.legendWidth = width
|
||||
func (b *BarChart) SetLegendWidth(width int32) {
|
||||
atomic.StoreInt32(&b.legendWidth, width)
|
||||
}
|
||||
|
||||
// OnDrawCell sets callback that allows to draw multicolored
|
||||
@ -475,6 +501,9 @@ func (b *BarChart) SetLegendWidth(width int) {
|
||||
// changing colors and rune makes sense. Changing anything else
|
||||
// does not affect the chart
|
||||
func (b *BarChart) OnDrawCell(fn func(*BarDataCell)) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.onDrawCell = fn
|
||||
}
|
||||
|
||||
@ -486,5 +515,8 @@ func (b *BarChart) ShowMarks() bool {
|
||||
|
||||
// SetShowMarks turns on and off marks under horizontal axis
|
||||
func (b *BarChart) SetShowMarks(show bool) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.showMarks = show
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package clui
|
||||
|
||||
import (
|
||||
term "github.com/nsf/termbox-go"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BaseControl is a base for all visible controls.
|
||||
@ -26,6 +27,7 @@ type BaseControl struct {
|
||||
gapX, gapY int
|
||||
pack PackType
|
||||
children []Control
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *BaseControl) Title() string {
|
||||
@ -103,10 +105,16 @@ func (c *BaseControl) SetTabStop(tabstop bool) {
|
||||
}
|
||||
|
||||
func (c *BaseControl) Enabled() bool {
|
||||
c.mtx.RLock()
|
||||
defer c.mtx.RUnlock()
|
||||
|
||||
return !c.disabled
|
||||
}
|
||||
|
||||
func (c *BaseControl) SetEnabled(enabled bool) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
c.disabled = !enabled
|
||||
}
|
||||
|
||||
|
39
button.go
39
button.go
@ -4,6 +4,7 @@ import (
|
||||
xs "github.com/huandu/xstrings"
|
||||
term "github.com/nsf/termbox-go"
|
||||
"time"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -15,7 +16,7 @@ type Button struct {
|
||||
BaseControl
|
||||
shadowColor term.Attribute
|
||||
bgActive term.Attribute
|
||||
pressed bool
|
||||
pressed int32
|
||||
onClick func(Event)
|
||||
}
|
||||
|
||||
@ -62,6 +63,8 @@ func CreateButton(parent Control, width, height int, title string, scale int) *B
|
||||
|
||||
// Repaint draws the control on its View surface
|
||||
func (b *Button) Draw() {
|
||||
b.mtx.RLock()
|
||||
defer b.mtx.RUnlock()
|
||||
PushAttributes()
|
||||
defer PopAttributes()
|
||||
|
||||
@ -70,7 +73,7 @@ func (b *Button) Draw() {
|
||||
|
||||
fg, bg := b.fg, b.bg
|
||||
shadow := RealColor(b.shadowColor, ColorButtonShadow)
|
||||
if !b.Enabled() {
|
||||
if b.disabled {
|
||||
fg, bg = RealColor(fg, ColorButtonDisabledText), RealColor(bg, ColorButtonDisabledBack)
|
||||
} else if b.Active() {
|
||||
fg, bg = RealColor(b.fgActive, ColorButtonActiveText), RealColor(b.bgActive, ColorButtonActiveBack)
|
||||
@ -81,7 +84,7 @@ func (b *Button) Draw() {
|
||||
dy := int((h - 1) / 2)
|
||||
SetTextColor(fg)
|
||||
shift, text := AlignColorizedText(b.title, w-1, b.align)
|
||||
if !b.pressed {
|
||||
if b.isPressed() == 0 {
|
||||
SetBackColor(shadow)
|
||||
FillRect(x+1, y+1, w-1, h-1, ' ')
|
||||
SetBackColor(bg)
|
||||
@ -94,6 +97,14 @@ func (b *Button) Draw() {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Button) isPressed() int32 {
|
||||
return atomic.LoadInt32(&b.pressed)
|
||||
}
|
||||
|
||||
func (b *Button) setPressed(pressed int32) {
|
||||
atomic.StoreInt32(&b.pressed, pressed)
|
||||
}
|
||||
|
||||
/*
|
||||
ProcessEvent processes all events come from the control parent. If a control
|
||||
processes an event it should return true. If the method returns false it means
|
||||
@ -106,39 +117,39 @@ func (b *Button) ProcessEvent(event Event) bool {
|
||||
}
|
||||
|
||||
if event.Type == EventKey {
|
||||
if event.Key == term.KeySpace && !b.pressed {
|
||||
b.pressed = true
|
||||
if event.Key == term.KeySpace && b.isPressed() == 0 {
|
||||
b.setPressed(1)
|
||||
ev := Event{Type: EventRedraw}
|
||||
|
||||
go func() {
|
||||
b.Draw()
|
||||
PutEvent(ev)
|
||||
PutEvent(ev)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
b.pressed = false
|
||||
b.Draw()
|
||||
b.setPressed(0)
|
||||
PutEvent(ev)
|
||||
}()
|
||||
|
||||
if b.onClick != nil {
|
||||
b.onClick(event)
|
||||
}
|
||||
return true
|
||||
} else if event.Key == term.KeyEsc && b.pressed {
|
||||
b.pressed = false
|
||||
} else if event.Key == term.KeyEsc && b.isPressed() != 0 {
|
||||
b.setPressed(0)
|
||||
ReleaseEvents()
|
||||
return true
|
||||
}
|
||||
} else if event.Type == EventMouse {
|
||||
if event.Key == term.MouseLeft {
|
||||
b.pressed = true
|
||||
b.setPressed(1)
|
||||
GrabEvents(b)
|
||||
return true
|
||||
} else if event.Key == term.MouseRelease && b.pressed {
|
||||
} else if event.Key == term.MouseRelease && b.isPressed() != 0 {
|
||||
ReleaseEvents()
|
||||
if event.X >= b.x && event.Y >= b.y && event.X < b.x+b.width && event.Y < b.y+b.height {
|
||||
if b.onClick != nil {
|
||||
b.onClick(event)
|
||||
}
|
||||
}
|
||||
b.pressed = false
|
||||
b.setPressed(0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
2017-09-07 - version 0.6.2
|
||||
[*] Fix races (that racy tool shows). The work is not completed but all demos
|
||||
and termfb2 application works without warnings
|
||||
|
||||
2017-09-07 - version 0.6.2
|
||||
[*] Setting the first button of confirmation dialog as default one did not work
|
||||
[*] TableView does not use 'go' to fire events that allows to create on the
|
||||
fly any required dialog. That is useful, e.g., to create a simple
|
||||
|
12
checkbox.go
12
checkbox.go
@ -52,6 +52,9 @@ func CreateCheckBox(parent Control, width int, title string, scale int) *CheckBo
|
||||
|
||||
// Repaint draws the control on its View surface
|
||||
func (c *CheckBox) Draw() {
|
||||
c.mtx.RLock()
|
||||
defer c.mtx.RUnlock()
|
||||
|
||||
PushAttributes()
|
||||
defer PopAttributes()
|
||||
|
||||
@ -120,6 +123,9 @@ func (c *CheckBox) ProcessEvent(event Event) bool {
|
||||
// Value must be 0 or 1 if Allow3State is off,
|
||||
// and 0, 1, or 2 if Allow3State is on
|
||||
func (c *CheckBox) SetState(val int) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
if val == c.state {
|
||||
return
|
||||
}
|
||||
@ -143,6 +149,9 @@ func (c *CheckBox) SetState(val int) {
|
||||
|
||||
// State returns current state of CheckBox
|
||||
func (c *CheckBox) State() int {
|
||||
c.mtx.RLock()
|
||||
defer c.mtx.RUnlock()
|
||||
|
||||
return c.state
|
||||
}
|
||||
|
||||
@ -185,5 +194,8 @@ func (c *CheckBox) SetSize(width, height int) {
|
||||
// of the CheckBox is changed. Argument of callback is the current
|
||||
// CheckBox state: 0 - off, 1 - on, 2 - third state
|
||||
func (c *CheckBox) OnChange(fn func(int)) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
|
||||
c.onChange = fn
|
||||
}
|
||||
|
@ -71,6 +71,8 @@ func CreateProgressBar(parent Control, width, height int, scale int) *ProgressBa
|
||||
// pb.SetTitle("{{value}} of {{max}}")
|
||||
// pb.SetTitle("{{percent}}%")
|
||||
func (b *ProgressBar) Draw() {
|
||||
b.mtx.RLock()
|
||||
defer b.mtx.RUnlock()
|
||||
if b.max <= b.min {
|
||||
return
|
||||
}
|
||||
@ -162,6 +164,8 @@ func (b *ProgressBar) Draw() {
|
||||
// SetValue sets new progress value. If value exceeds ProgressBar
|
||||
// limits then the limit value is used
|
||||
func (b *ProgressBar) SetValue(pos int) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
if pos < b.min {
|
||||
b.value = b.min
|
||||
} else if pos > b.max {
|
||||
@ -173,6 +177,8 @@ func (b *ProgressBar) SetValue(pos int) {
|
||||
|
||||
// Value returns the current ProgressBar value
|
||||
func (b *ProgressBar) Value() int {
|
||||
b.mtx.RLock()
|
||||
defer b.mtx.RUnlock()
|
||||
return b.value
|
||||
}
|
||||
|
||||
@ -198,6 +204,8 @@ func (b *ProgressBar) SetLimits(min, max int) {
|
||||
// Step increases ProgressBar value by 1 if the value is less
|
||||
// than ProgressBar high limit
|
||||
func (b *ProgressBar) Step() int {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
b.value++
|
||||
|
||||
if b.value > b.max {
|
||||
|
@ -72,6 +72,9 @@ func CreateSparkChart(parent Control, w, h int, scale int) *SparkChart {
|
||||
|
||||
// Repaint draws the control on its View surface
|
||||
func (b *SparkChart) Draw() {
|
||||
b.mtx.RLock()
|
||||
defer b.mtx.RUnlock()
|
||||
|
||||
PushAttributes()
|
||||
defer PopAttributes()
|
||||
|
||||
@ -212,6 +215,9 @@ func (b *SparkChart) calculateMultiplier() (float64, float64) {
|
||||
|
||||
// AddData appends a new bar to a chart
|
||||
func (b *SparkChart) AddData(val float64) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.data = append(b.data, val)
|
||||
|
||||
_, width := b.calculateBarArea()
|
||||
@ -222,11 +228,17 @@ func (b *SparkChart) AddData(val float64) {
|
||||
|
||||
// ClearData removes all bar from chart
|
||||
func (b *SparkChart) ClearData() {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.data = make([]float64, 0)
|
||||
}
|
||||
|
||||
// SetData assign a new bar list to a chart
|
||||
func (b *SparkChart) SetData(data []float64) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.data = make([]float64, len(data))
|
||||
copy(b.data, data)
|
||||
|
||||
@ -245,6 +257,9 @@ func (b *SparkChart) ValueWidth() int {
|
||||
|
||||
// SetValueWidth changes width of the value panel on the left
|
||||
func (b *SparkChart) SetValueWidth(width int) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.valueWidth = width
|
||||
}
|
||||
|
||||
@ -257,6 +272,9 @@ func (b *SparkChart) Top() float64 {
|
||||
// SetTop sets the theoretical highest value of data flow
|
||||
// to scale the chart
|
||||
func (b *SparkChart) SetTop(top float64) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.topValue = top
|
||||
}
|
||||
|
||||
@ -268,6 +286,9 @@ func (b *SparkChart) AutoScale() bool {
|
||||
|
||||
// SetAutoScale changes the way of scaling the data flow
|
||||
func (b *SparkChart) SetAutoScale(auto bool) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.autosize = auto
|
||||
}
|
||||
|
||||
@ -280,5 +301,8 @@ func (b *SparkChart) HilitePeaks() bool {
|
||||
// SetHilitePeaks enables or disables hiliting maximum
|
||||
// values with different colors
|
||||
func (b *SparkChart) SetHilitePeaks(hilite bool) {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
|
||||
b.hiliteMax = hilite
|
||||
}
|
||||
|
@ -348,6 +348,8 @@ func (l *TableView) drawCells() {
|
||||
|
||||
// Repaint draws the control on its View surface
|
||||
func (l *TableView) Draw() {
|
||||
l.mtx.RLock()
|
||||
defer l.mtx.RUnlock()
|
||||
PushAttributes()
|
||||
defer PopAttributes()
|
||||
|
||||
@ -924,6 +926,9 @@ func (l *TableView) OnKeyPress(fn func(term.Key) bool) {
|
||||
// OnDrawCell is called every time the table is going to display
|
||||
// a cell
|
||||
func (l *TableView) OnDrawCell(fn func(*ColumnDrawInfo)) {
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
|
||||
l.onDrawCell = fn
|
||||
}
|
||||
|
||||
|
54
theme.go
54
theme.go
@ -7,6 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
@ -87,6 +88,7 @@ const themeSuffix = ".theme"
|
||||
|
||||
var (
|
||||
themeManager *ThemeManager
|
||||
thememtx sync.RWMutex
|
||||
)
|
||||
|
||||
// ThemeDesc is a detailed information about theme:
|
||||
@ -122,6 +124,9 @@ func initThemeManager() {
|
||||
// Reset removes all loaded themes from cache and reinitialize
|
||||
// the default theme
|
||||
func ThemeReset() {
|
||||
thememtx.Lock()
|
||||
defer thememtx.Unlock()
|
||||
|
||||
themeManager.current = defaultTheme
|
||||
themeManager.themes = make(map[string]theme, 0)
|
||||
|
||||
@ -209,10 +214,12 @@ func ThemeReset() {
|
||||
// The method panics if theme loop is detected - check if
|
||||
// parent attribute is correct
|
||||
func SysColor(color string) term.Attribute {
|
||||
thememtx.RLock()
|
||||
sch, ok := themeManager.themes[themeManager.current]
|
||||
if !ok {
|
||||
sch = themeManager.themes[defaultTheme]
|
||||
}
|
||||
thememtx.RUnlock()
|
||||
|
||||
clr, okclr := sch.colors[color]
|
||||
if !okclr {
|
||||
@ -226,9 +233,11 @@ func SysColor(color string) term.Attribute {
|
||||
if sch.parent == "" {
|
||||
break
|
||||
}
|
||||
themeManager.LoadTheme(sch.parent)
|
||||
themeManager.loadTheme(sch.parent)
|
||||
thememtx.RLock()
|
||||
sch = themeManager.themes[sch.parent]
|
||||
clr, okclr = sch.colors[color]
|
||||
thememtx.RUnlock()
|
||||
|
||||
if ok {
|
||||
break
|
||||
@ -250,10 +259,12 @@ func SysColor(color string) term.Attribute {
|
||||
// The method panics if theme loop is detected - check if
|
||||
// parent attribute is correct
|
||||
func SysObject(object string) string {
|
||||
thememtx.RLock()
|
||||
sch, ok := themeManager.themes[themeManager.current]
|
||||
if !ok {
|
||||
sch = themeManager.themes[defaultTheme]
|
||||
}
|
||||
thememtx.RUnlock()
|
||||
|
||||
obj, okobj := sch.objects[object]
|
||||
if !okobj {
|
||||
@ -268,9 +279,11 @@ func SysObject(object string) string {
|
||||
break
|
||||
}
|
||||
|
||||
themeManager.LoadTheme(sch.parent)
|
||||
themeManager.loadTheme(sch.parent)
|
||||
thememtx.RLock()
|
||||
sch = themeManager.themes[sch.parent]
|
||||
obj, okobj = sch.objects[object]
|
||||
thememtx.RUnlock()
|
||||
|
||||
if ok {
|
||||
break
|
||||
@ -313,22 +326,32 @@ func ThemeNames() []string {
|
||||
|
||||
// CurrentTheme returns name of the current theme
|
||||
func CurrentTheme() string {
|
||||
thememtx.RLock()
|
||||
defer thememtx.RUnlock()
|
||||
|
||||
return themeManager.current
|
||||
}
|
||||
|
||||
// SetCurrentTheme changes the current theme.
|
||||
// Returns false if changing failed - e.g, theme does not exist
|
||||
func SetCurrentTheme(name string) bool {
|
||||
if _, ok := themeManager.themes[name]; !ok {
|
||||
thememtx.RLock()
|
||||
_, ok := themeManager.themes[name]
|
||||
thememtx.RUnlock()
|
||||
|
||||
if !ok {
|
||||
tnames := ThemeNames()
|
||||
for _, theme := range tnames {
|
||||
if theme == name {
|
||||
themeManager.LoadTheme(theme)
|
||||
themeManager.loadTheme(theme)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thememtx.Lock()
|
||||
defer thememtx.Unlock()
|
||||
|
||||
if _, ok := themeManager.themes[name]; ok {
|
||||
themeManager.current = name
|
||||
return true
|
||||
@ -352,9 +375,12 @@ func SetThemePath(path string) {
|
||||
ThemeReset()
|
||||
}
|
||||
|
||||
// LoadTheme loads the theme if it is not in the cache already.
|
||||
// If theme is in the cache LoadTheme does nothing
|
||||
func (s *ThemeManager) LoadTheme(name string) {
|
||||
// loadTheme loads the theme if it is not in the cache already.
|
||||
// If theme is in the cache loadTheme does nothing
|
||||
func (s *ThemeManager) loadTheme(name string) {
|
||||
thememtx.Lock()
|
||||
defer thememtx.Unlock()
|
||||
|
||||
if _, ok := s.themes[name]; ok {
|
||||
return
|
||||
}
|
||||
@ -442,25 +468,31 @@ func (s *ThemeManager) LoadTheme(name string) {
|
||||
s.themes[name] = theme
|
||||
}
|
||||
|
||||
// ReLoadTheme refresh cache entry for the theme with new
|
||||
// ReloadTheme refresh cache entry for the theme with new
|
||||
// data loaded from file. Use it to apply theme changes on
|
||||
// the fly without resetting manager or restarting application
|
||||
func ReLoadTheme(name string) {
|
||||
func ReloadTheme(name string) {
|
||||
if name == defaultTheme {
|
||||
// default theme cannot be reloaded
|
||||
return
|
||||
}
|
||||
|
||||
thememtx.Lock()
|
||||
if _, ok := themeManager.themes[name]; ok {
|
||||
delete(themeManager.themes, name)
|
||||
}
|
||||
thememtx.Unlock()
|
||||
|
||||
themeManager.LoadTheme(name)
|
||||
themeManager.loadTheme(name)
|
||||
}
|
||||
|
||||
// ThemeInfo returns detailed info about theme
|
||||
func ThemeInfo(name string) ThemeDesc {
|
||||
themeManager.LoadTheme(name)
|
||||
themeManager.loadTheme(name)
|
||||
|
||||
thememtx.RLock()
|
||||
defer thememtx.RUnlock()
|
||||
|
||||
var theme ThemeDesc
|
||||
if t, ok := themeManager.themes[name]; !ok {
|
||||
theme.parent = t.parent
|
||||
|
Loading…
x
Reference in New Issue
Block a user