Compare commits

..

No commits in common. "main" and "v0.5.0" have entirely different histories.
main ... v0.5.0

25 changed files with 255 additions and 787 deletions

50
.cirrus.yml Normal file
View File

@ -0,0 +1,50 @@
---
env:
DEST_BRANCH: "main"
CIRRUS_SHELL: "/bin/bash"
timeout_in: 30m
# Run on PRs and main branch post submit only. Don't run tests when tagging.
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'main')
clone_script: &full_clone |
if [ -z "$CIRRUS_PR" ]; then
git clone --recursive --branch=$CIRRUS_BRANCH https://x-access-token:${CIRRUS_REPO_CLONE_TOKEN}@github.com/${CIRRUS_REPO_FULL_NAME}.git $CIRRUS_WORKING_DIR
git reset --hard $CIRRUS_CHANGE_IN_REPO
else
git clone --recursive https://x-access-token:${CIRRUS_REPO_CLONE_TOKEN}@github.com/${CIRRUS_REPO_FULL_NAME}.git $CIRRUS_WORKING_DIR
git fetch origin pull/$CIRRUS_PR/head:pull/$CIRRUS_PR
git reset --hard $CIRRUS_CHANGE_IN_REPO
fi
precommit_test_task:
name: "Precommit"
alias: precommit
clone_script: *full_clone
container:
image: python:3.10
script: |
python3 -m pip install pre-commit
pre-commit run -a
gofmt_task:
name: "Gofmt"
alias: gofmt
clone_script: *full_clone
container:
image: golang:1.18
script: |
SRC=$(find . -type f -name '*.go' -not -path "./vendor/*")
gofmt -w ${SRC}
golangci_lint_task:
name: "Golangci-lint"
alias: lint
clone_script: *full_clone
container:
image: golang:1.18
script: |
make .install.golangci-lint
make lint

View File

@ -10,11 +10,13 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '>=1.22'
# cannot use 1.21.X latest version since golangci-lint has an issue.
go-version: '1.21.4'
cache: false
- run: |
make .install.golangci-lint
make lint
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.51
unit_test:
name: Unit test
@ -23,7 +25,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '>=1.22'
go-version: '>=1.19.0'
id: go
- name: Check out code into the Go module directory
@ -41,9 +43,9 @@ jobs:
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
file: .coverage/coverprofile
name: codecov-umbrella
fail_ci_if_error: true
slug: navidys/tvxwidgets
token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -40,7 +40,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '>=1.22'
# cannot use 1.21.X latest version since golangci-lint has an issue.
go-version: '1.21.4'
cache: false
- run: |
make .install.golangci-lint
@ -54,7 +55,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '>=1.22'
go-version: '>=1.19.0'
id: go
- name: Check out code into the Go module directory
@ -77,7 +78,7 @@ jobs:
with:
file: .coverage/coverprofile
name: codecov-umbrella
fail_ci_if_error: false
fail_ci_if_error: true
goreportcard:
name: update reportcard

View File

@ -1,28 +1,30 @@
run:
timeout: 10m
deadline: 5m
skip-dirs:
- demos
skip-files:
- ".*_test.go"
linters:
enable-all: true
disable:
- varnamelen
- exhaustruct
- depguard
# deprecated
- gomnd
- execinquery
- exportloopref
- rowserrcheck
- wastedassign
- structcheck
- deadcode
- varcheck
- nosnakecase
- ifshort
- golint
- maligned
- interfacer
- scopelint
- exhaustivestruct
linters-settings:
errcheck:
check-blank: false
exclude-functions:
- fmt:.*
ignore: fmt:.*
nolintlint:
require-specific: true
issues:
exclude-dirs:
- demos
exclude-files:
- ".*_test.go"

View File

@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

View File

@ -32,7 +32,7 @@ install.tools: .install.pre-commit .install.codespell .install.golangci-lint .in
.PHONY: .install.golangci-lint
.install.golangci-lint:
VERSION=1.61.0 ./hack/install_golangci.sh
VERSION=1.51.1 ./hack/install_golangci.sh
#=================================================
# Testing (units, functionality, ...) targets

View File

@ -1,7 +1,7 @@
package tvxwidgets
import (
"strconv"
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
@ -67,8 +67,8 @@ func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
x, y, width, height := c.Box.GetInnerRect()
maxValY := y + 1
xAxisStartY := y + height - 2 //nolint:mnd
barStartY := y + height - 3 //nolint:mnd
xAxisStartY := y + height - 2 //nolint:gomnd
barStartY := y + height - 3 //nolint:gomnd
borderPadding := 0
if c.hasBorder {
@ -76,7 +76,7 @@ func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
}
// set max value if not set
c.initMaxValue()
maxValueSr := strconv.Itoa(c.maxVal)
maxValueSr := fmt.Sprintf("%d", c.maxVal)
maxValLenght := len(maxValueSr) + 1
if maxValLenght < barChartYAxisLabelWidth {
@ -108,7 +108,7 @@ func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
tview.PrintJoinedSemigraphics(screen, x+maxValLenght-1, xAxisStartY, '0', axesLabelStyle)
mxValRune := []rune(maxValueSr)
for i := range mxValRune {
for i := 0; i < len(mxValRune); i++ {
tview.PrintJoinedSemigraphics(screen, x+borderPadding+i, maxValY, mxValRune[i], axesLabelStyle)
}
@ -123,23 +123,23 @@ func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
}
// set labels
r := []rune(item.label)
for j := range r {
for j := 0; j < len(r); j++ {
tview.PrintJoinedSemigraphics(screen, startX+j, labelY, r[j], axesLabelStyle)
}
// bar style
bStyle := tcell.StyleDefault.Background(c.GetBackgroundColor()).Foreground(item.color)
barHeight := c.getHeight(valueMaxHeight, item.value)
for k := range barHeight {
for l := range c.barWidth {
for k := 0; k < barHeight; k++ {
for l := 0; l < c.barWidth; l++ {
tview.PrintJoinedSemigraphics(screen, startX+l, barStartY-k, fullBlockRune, bStyle)
}
}
// bar value
vSt := strconv.Itoa(item.value)
vSt := fmt.Sprintf("%d", item.value)
vRune := []rune(vSt)
for i := range vRune {
for i := 0; i < len(vRune); i++ {
tview.PrintJoinedSemigraphics(screen, startX+i, barStartY-barHeight, vRune[i], bStyle)
}
@ -170,8 +170,8 @@ func (c *BarChart) SetRect(x, y, width, height int) {
}
// SetMaxValue sets maximum value of bars.
func (c *BarChart) SetMaxValue(maxValue int) {
c.maxVal = maxValue
func (c *BarChart) SetMaxValue(max int) {
c.maxVal = max
}
// SetAxesColor sets axes x and y lines color.
@ -193,22 +193,9 @@ func (c *BarChart) AddBar(label string, value int, color tcell.Color) {
})
}
// RemoveBar removes a bar item from the bar chart.
func (c *BarChart) RemoveBar(label string) {
bars := c.bars[:0]
for _, barItem := range c.bars {
if barItem.label != label {
bars = append(bars, barItem)
}
}
c.bars = bars
}
// SetBarValue sets bar values.
func (c *BarChart) SetBarValue(name string, value int) {
for i := range c.bars {
for i := 0; i < len(c.bars); i++ {
if c.bars[i].label == name {
c.bars[i].value = value
}

View File

@ -204,9 +204,20 @@ func main() {
}
moveSinData := func(data [][]float64) [][]float64 {
n := 220
newData := make([][]float64, 2)
newData[0] = rotate(data[0], -1)
newData[1] = rotate(data[1], -1)
newData[0] = make([]float64, n)
newData[1] = make([]float64, n)
for i := 0; i < n; i++ {
if i+1 < len(data[0]) {
newData[0][i] = data[0][i+1]
}
if i+1 < len(data[1]) {
newData[1][i] = data[1][i+1]
}
}
return newData
}
@ -319,23 +330,3 @@ func newBarChart() *tvxwidgets.BarChart {
return barGraph
}
// Source: https://stackoverflow.com/questions/50833673/rotate-array-in-go/79079760#79079760
// rotate rotates the given slice by k positions to the left or right.
func rotate[T any](slice []T, k int) []T {
if len(slice) == 0 {
return slice
}
var r int
if k > 0 {
r = len(slice) - k%len(slice)
} else {
kAbs := int(math.Abs(float64(k)))
r = kAbs % len(slice)
}
slice = append(slice[r:], slice[:r]...)
return slice
}

View File

@ -35,7 +35,6 @@ func main() {
})
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
bmLineChart.SetData(sinData)
bmLineChart.SetDrawXAxisLabel(false)
dmLineChart := tvxwidgets.NewPlot()
dmLineChart.SetBorder(true)
@ -73,7 +72,6 @@ func main() {
dmScatterPlot.SetPlotType(tvxwidgets.PlotTypeScatter)
dmScatterPlot.SetMarker(tvxwidgets.PlotMarkerDot)
dmScatterPlot.SetData(scatterPlotData)
dmScatterPlot.SetDrawYAxisLabel(false)
bmScatterPlot := tvxwidgets.NewPlot()
bmScatterPlot.SetBorder(true)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -1 +0,0 @@
![Screenshot](screenshot.png)

View File

@ -1,119 +0,0 @@
package main
import (
"github.com/gdamore/tcell/v2"
"github.com/navidys/tvxwidgets"
"github.com/rivo/tview"
"math"
)
func main() {
app := tview.NewApplication()
sinData := func() [][]float64 {
n := 220
data := make([][]float64, 2)
data[0] = make([]float64, n)
data[1] = make([]float64, n)
for i := 0; i < n; i++ {
data[0][i] = math.Sin(float64(i+1) / 5)
// Avoid taking Cos(0) because it creates a high point of 2 that
// will never be hit again and makes the graph look a little funny
data[1][i] = math.Cos(float64(i+1) / 5)
}
return data
}()
bmLineChart := tvxwidgets.NewPlot()
bmLineChart.SetBorder(true)
bmLineChart.SetTitle("line chart (braille mode)")
bmLineChart.SetLineColor([]tcell.Color{
tcell.ColorSteelBlue,
tcell.ColorGreen,
})
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
bmLineChart.SetYAxisAutoScaleMin(false)
bmLineChart.SetYAxisAutoScaleMax(false)
bmLineChart.SetYRange(-1.5, 1.5)
bmLineChart.SetData(sinData)
bmLineChart.SetDrawXAxisLabel(false)
dmLineChart := tvxwidgets.NewPlot()
dmLineChart.SetBorder(true)
dmLineChart.SetTitle("line chart (dot mode)")
dmLineChart.SetLineColor([]tcell.Color{
tcell.ColorDarkOrange,
})
dmLineChart.SetAxesLabelColor(tcell.ColorGold)
dmLineChart.SetAxesColor(tcell.ColorGold)
dmLineChart.SetMarker(tvxwidgets.PlotMarkerDot)
dmLineChart.SetDotMarkerRune('\u25c9')
sampleData1 := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
sampleData2 := []float64{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
dotModeChartData := [][]float64{sampleData1}
dotModeChartData[0] = append(dotModeChartData[0], sampleData2...)
dotModeChartData[0] = append(dotModeChartData[0], sampleData1[:5]...)
dotModeChartData[0] = append(dotModeChartData[0], sampleData2[5:]...)
dotModeChartData[0] = append(dotModeChartData[0], sampleData1[:7]...)
dotModeChartData[0] = append(dotModeChartData[0], sampleData2[3:]...)
dmLineChart.SetYAxisAutoScaleMin(false)
dmLineChart.SetYAxisAutoScaleMax(false)
dmLineChart.SetYRange(0, 3)
dmLineChart.SetData(dotModeChartData)
scatterPlotData := make([][]float64, 2)
scatterPlotData[0] = []float64{1, 2, 3, 4, 5}
scatterPlotData[1] = sinData[1][4:]
dmScatterPlot := tvxwidgets.NewPlot()
dmScatterPlot.SetBorder(true)
dmScatterPlot.SetTitle("scatter plot (dot mode)")
dmScatterPlot.SetLineColor([]tcell.Color{
tcell.ColorMediumSlateBlue,
tcell.ColorLightSkyBlue,
})
dmScatterPlot.SetPlotType(tvxwidgets.PlotTypeScatter)
dmScatterPlot.SetMarker(tvxwidgets.PlotMarkerDot)
dmScatterPlot.SetYAxisAutoScaleMin(false)
dmScatterPlot.SetYAxisAutoScaleMax(false)
dmScatterPlot.SetYRange(-1, 3)
dmScatterPlot.SetData(scatterPlotData)
dmScatterPlot.SetDrawYAxisLabel(false)
bmScatterPlot := tvxwidgets.NewPlot()
bmScatterPlot.SetBorder(true)
bmScatterPlot.SetTitle("scatter plot (braille mode)")
bmScatterPlot.SetLineColor([]tcell.Color{
tcell.ColorGold,
tcell.ColorLightSkyBlue,
})
bmScatterPlot.SetPlotType(tvxwidgets.PlotTypeScatter)
bmScatterPlot.SetMarker(tvxwidgets.PlotMarkerBraille)
bmScatterPlot.SetYAxisAutoScaleMin(false)
bmScatterPlot.SetYAxisAutoScaleMax(false)
bmScatterPlot.SetYRange(-1, 5)
bmScatterPlot.SetData(scatterPlotData)
firstRow := tview.NewFlex().SetDirection(tview.FlexColumn)
firstRow.AddItem(dmLineChart, 0, 1, false)
firstRow.AddItem(bmLineChart, 0, 1, false)
firstRow.SetRect(0, 0, 100, 15)
secondRow := tview.NewFlex().SetDirection(tview.FlexColumn)
secondRow.AddItem(dmScatterPlot, 0, 1, false)
secondRow.AddItem(bmScatterPlot, 0, 1, false)
secondRow.SetRect(0, 0, 100, 15)
layout := tview.NewFlex().SetDirection(tview.FlexRow)
layout.AddItem(firstRow, 0, 1, false)
layout.AddItem(secondRow, 0, 1, false)
layout.SetRect(0, 0, 100, 30)
if err := app.SetRoot(layout, false).EnableMouse(true).Run(); err != nil {
panic(err)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@ -1 +0,0 @@
![Screenshot](screenshot.png)

View File

@ -1,133 +0,0 @@
package main
import (
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/navidys/tvxwidgets"
"github.com/rivo/tview"
"math"
"time"
)
func main() {
app := tview.NewApplication()
// >>> Data Function <<<
// With these values, the curve will start with a value of 0 and reach a
// high point of 2 at x = 3.14 (Pi) and then return to 0 at x = 6.28 (2*Pi).
// Play around with these values to get a feel for how they affect the curve
// and how you might adapt this code to plot other functions.
period := 2 * math.Pi
horizontalStretchFactor := 1.0
verticalStretchFactor := 1.0
xOffset := 0.0
yOffset := 0.0
// >>> Graph View/Camera Controls <<<
// These values influence which part of the curve is shown in
// what "zoom level".
xAxisZoomFactor := 3.0
yAxisZoomFactor := 1.0
xAxisShift := 0.0
yAxisShift := 0.0
// xFunc1 defines the x values that should be used for each vertical "slot" in the graph.
xFunc1 := func(i int) float64 {
return (float64(i) / xAxisZoomFactor) + xAxisShift
}
// yFunc1 defines the y values that result from a given input value x (this is the actual function).
yFunc1 := func(x float64) float64 {
return (math.Sin((x+xOffset)/horizontalStretchFactor) + yOffset) * verticalStretchFactor
}
// xLabelFunc1 defines a label for each vertical "slot". Which labels are shown is determined automatically
// based on the available space.
xLabelFunc1 := func(i int) string {
xVal := xFunc1(i)
labelVal := xVal
label := fmt.Sprintf("%.1f", labelVal)
return label
}
// computeDataArray computes the y values for n vertical slots based on the definitions above.
computeDataArray := func() [][]float64 {
n := 150
data := make([][]float64, 1)
data[0] = make([]float64, n)
for i := 0; i < n; i++ {
xVal := xFunc1(i)
yVal := yFunc1(xVal)
data[0][i] = yVal
}
return data
}
data := computeDataArray()
bmLineChart := tvxwidgets.NewPlot()
bmLineChart.SetBorder(true)
bmLineChart.SetTitle("line chart (braille mode)")
bmLineChart.SetLineColor([]tcell.Color{
tcell.ColorSteelBlue,
tcell.ColorGreen,
})
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
bmLineChart.SetXAxisLabelFunc(xLabelFunc1)
bmLineChart.SetYAxisAutoScaleMin(false)
bmLineChart.SetYAxisAutoScaleMax(false)
bmLineChart.SetYRange(
(-1+yOffset+yAxisShift)/yAxisZoomFactor,
(1+yOffset+yAxisShift)/yAxisZoomFactor,
)
bmLineChart.SetData(data)
firstRow := tview.NewFlex().SetDirection(tview.FlexColumn)
firstRow.AddItem(bmLineChart, 0, 1, false)
firstRow.SetRect(0, 0, 100, 15)
layout := tview.NewFlex().SetDirection(tview.FlexRow)
layout.AddItem(firstRow, 0, 1, false)
layout.SetRect(0, 0, 100, 30)
animate := true
rotateDataContinuously := func() {
tick := time.NewTicker(100 * time.Millisecond)
go func() {
initialxAxisShift := xAxisShift
for {
select {
case <-tick.C:
if !animate {
continue
}
xAxisShift = xAxisShift + 0.1
if xAxisShift >= initialxAxisShift+period*4 {
xAxisShift = initialxAxisShift
}
data = computeDataArray()
bmLineChart.SetData(data)
app.Draw()
}
}
}()
}
go rotateDataContinuously()
if err := app.SetRoot(layout, false).EnableMouse(true).SetMouseCapture(func(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) {
if action == tview.MouseLeftClick {
animate = !animate
}
return event, action
}).Run(); err != nil {
panic(err)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -76,11 +76,6 @@ func (d *MessageDialog) SetType(dtype int) {
}
}
// SetTitle sets dialog title.
func (d *MessageDialog) SetTitle(title string) {
d.layout.SetTitle(title)
}
// SetBackgroundColor sets dialog background color.
func (d *MessageDialog) SetBackgroundColor(color tcell.Color) {
d.bgColor = color
@ -138,7 +133,6 @@ func (d *MessageDialog) InputHandler() func(event *tcell.EventKey, setFocus func
return
}
}
if formHandler := d.form.InputHandler(); formHandler != nil {
formHandler(event, setFocus)
@ -154,7 +148,6 @@ func (d *MessageDialog) MouseHandler() func(action tview.MouseAction, event *tce
consumed, capture = d.form.MouseHandler()(action, event, setFocus)
if !consumed && action == tview.MouseLeftClick && d.InRect(event.Position()) {
setFocus(d)
consumed = true
}

View File

@ -34,13 +34,13 @@ func (g *ActivityModeGauge) Draw(screen tcell.Screen) {
x, y, width, height := g.Box.GetInnerRect()
tickStr := g.tickStr(width)
for i := range height {
for i := 0; i < height; i++ {
tview.Print(screen, tickStr, x, y+i, width, tview.AlignLeft, g.pgBgColor)
}
}
// Focus is called when this primitive receives focus.
func (g *ActivityModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
func (g *ActivityModeGauge) Focus(delegate func(p tview.Primitive)) {
}
// HasFocus returns whether or not this primitive has focus.
@ -73,27 +73,27 @@ func (g *ActivityModeGauge) Reset() {
g.counter = 0
}
func (g *ActivityModeGauge) tickStr(maxCount int) string {
func (g *ActivityModeGauge) tickStr(max int) string {
var (
prgHeadStr string
prgEndStr string
prgStr string
)
if g.counter >= maxCount-4 {
if g.counter >= max-4 {
g.counter = 0
}
hWidth := 0
for range g.counter {
for i := 0; i < g.counter; i++ {
prgHeadStr += fmt.Sprintf("[%s::]%s", getColorName(tview.Styles.PrimitiveBackgroundColor), prgCell)
hWidth++
}
prgStr = prgCell + prgCell + prgCell + prgCell
for range maxCount + hWidth + 4 {
for i := 0; i < max+hWidth+4; i++ {
prgEndStr += fmt.Sprintf("[%s::]%s", getColorName(tview.Styles.PrimitiveBackgroundColor), prgCell)
}

View File

@ -47,8 +47,8 @@ func (g *PercentageModeGauge) Draw(screen tcell.Screen) {
prgBlock := g.progressBlock(width)
style := tcell.StyleDefault.Background(g.pgBgColor).Foreground(tview.Styles.PrimaryTextColor)
for i := range height {
for j := range prgBlock {
for i := 0; i < height; i++ {
for j := 0; j < prgBlock; j++ {
screen.SetContent(x+j, y+i, ' ', nil, style)
}
}
@ -56,22 +56,21 @@ func (g *PercentageModeGauge) Draw(screen tcell.Screen) {
// print percentage in middle of box
pcRune := []rune(pcString)
for j := range pcRune {
for j := 0; j < len(pcRune); j++ {
style = tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.PrimaryTextColor)
if x+prgBlock >= tX+j {
style = tcell.StyleDefault.Background(g.pgBgColor).Foreground(tview.Styles.PrimaryTextColor)
}
for i := range height {
for i := 0; i < height; i++ {
screen.SetContent(tX+j, y+i, ' ', nil, style)
}
screen.SetContent(tX+j, tY, pcRune[j], nil, style)
}
}
// Focus is called when this primitive receives focus.
func (g *PercentageModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
func (g *PercentageModeGauge) Focus(delegate func(p tview.Primitive)) {
}
// HasFocus returns whether or not this primitive has focus.
@ -123,13 +122,13 @@ func (g *PercentageModeGauge) Reset() {
g.value = 0
}
func (g *PercentageModeGauge) progressBlock(maxValue int) int {
func (g *PercentageModeGauge) progressBlock(max int) int {
if g.maxValue == 0 {
return g.maxValue
}
pc := g.value * gaugeMaxPc / g.maxValue
value := pc * maxValue / gaugeMaxPc
value := pc * max / gaugeMaxPc
return value
}

View File

@ -59,7 +59,7 @@ func (g *UtilModeGauge) SetLabelColor(color tcell.Color) {
}
// Focus is called when this primitive receives focus.
func (g *UtilModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
func (g *UtilModeGauge) Focus(delegate func(p tview.Primitive)) {
}
// HasFocus returns whether or not this primitive has focus.
@ -97,8 +97,8 @@ func (g *UtilModeGauge) Draw(screen tcell.Screen) {
labelWidth := len(g.label)
barWidth := width - labelPCWidth - labelWidth
for i := range barWidth {
for j := range height {
for i := 0; i < barWidth; i++ {
for j := 0; j < height; j++ {
value := float64(i * 100 / barWidth)
color := g.getBarColor(value)

32
go.mod
View File

@ -1,27 +1,27 @@
module github.com/navidys/tvxwidgets
go 1.22.6
go 1.18
require (
github.com/gdamore/tcell/v2 v2.8.1
github.com/onsi/ginkgo/v2 v2.22.2
github.com/onsi/gomega v1.36.2
github.com/rivo/tview v0.0.0-20240616192244-23476fa0bab2
github.com/gdamore/tcell/v2 v2.7.0
github.com/onsi/ginkgo/v2 v2.13.2
github.com/onsi/gomega v1.30.0
github.com/rivo/tview v0.0.0-20231206124440-5f078138442e
)
require (
github.com/gdamore/encoding v1.0.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

103
go.sum
View File

@ -1,107 +1,86 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.0 h1:I5LiGTQuwrysAt1KS9wg1yFfOI3arI3ucFrxtd/xqaA=
github.com/gdamore/tcell/v2 v2.7.0/go.mod h1:hl/KtAANGBecfIPxk+FzKvThTqI84oplgbPEmVX60b8=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/tview v0.0.0-20240616192244-23476fa0bab2 h1:LXMiBMxtuXw8e2paN61dI2LMp8JZYyH4UXDwssRI3ys=
github.com/rivo/tview v0.0.0-20240616192244-23476fa0bab2/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
github.com/rivo/tview v0.0.0-20231206124440-5f078138442e h1:mPy47VW9tkqImnSPgcjnEHJuG3XHDBtXj2hDb1qBrRs=
github.com/rivo/tview v0.0.0-20231206124440-5f078138442e/go.mod h1:c0SPlNPXkM+/Zgjn/0vD3W0Ds1yxstN7lpquqLDpWCg=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

355
plot.go
View File

@ -3,9 +3,6 @@ package tvxwidgets
import (
"fmt"
"image"
"math"
"strconv"
"strings"
"sync"
"github.com/gdamore/tcell/v2"
@ -21,14 +18,6 @@ const (
PlotMarkerDot
)
// PlotYAxisLabelDataType represents plot y axis type (integer or float).
type PlotYAxisLabelDataType uint
const (
PlotYAxisLabelDataInt PlotYAxisLabelDataType = iota
PlotYAxisLabelDataFloat
)
// PlotType represents plot type (line chart or scatter).
type PlotType uint
@ -42,8 +31,6 @@ const (
plotXAxisLabelsHeight = 1
plotXAxisLabelsGap = 2
plotYAxisLabelsGap = 1
gapRune = " "
)
type brailleCell struct {
@ -54,44 +41,29 @@ type brailleCell struct {
// Plot represents a plot primitive used for different charts.
type Plot struct {
*tview.Box
data [][]float64
// maxVal is the maximum y-axis (vertical) value found in any of the lines in the data set.
maxVal float64
// minVal is the minimum y-axis (vertical) value found in any of the lines in the data set.
minVal float64
marker Marker
ptype PlotType
dotMarkerRune rune
lineColors []tcell.Color
axesColor tcell.Color
axesLabelColor tcell.Color
drawAxes bool
drawXAxisLabel bool
xAxisLabelFunc func(int) string
drawYAxisLabel bool
yAxisLabelDataType PlotYAxisLabelDataType
yAxisAutoScaleMin bool
yAxisAutoScaleMax bool
brailleCellMap map[image.Point]brailleCell
mu sync.Mutex
data [][]float64
maxVal float64
marker Marker
ptype PlotType
dotMarkerRune rune
lineColors []tcell.Color
axesColor tcell.Color
axesLabelColor tcell.Color
drawAxes bool
brailleCellMap map[image.Point]brailleCell
mu sync.Mutex
}
// NewPlot returns a plot widget.
func NewPlot() *Plot {
return &Plot{
Box: tview.NewBox(),
marker: PlotMarkerDot,
ptype: PlotTypeLineChart,
dotMarkerRune: dotRune,
axesColor: tcell.ColorDimGray,
axesLabelColor: tcell.ColorDimGray,
drawAxes: true,
drawXAxisLabel: true,
xAxisLabelFunc: strconv.Itoa,
drawYAxisLabel: true,
yAxisLabelDataType: PlotYAxisLabelDataFloat,
yAxisAutoScaleMin: false,
yAxisAutoScaleMax: true,
Box: tview.NewBox(),
marker: PlotMarkerDot,
ptype: PlotTypeLineChart,
dotMarkerRune: dotRune,
axesColor: tcell.ColorDimGray,
axesLabelColor: tcell.ColorDimGray,
drawAxes: true,
lineColors: []tcell.Color{
tcell.ColorSteelBlue,
},
@ -122,21 +94,6 @@ func (plot *Plot) SetLineColor(color []tcell.Color) {
plot.lineColors = color
}
// SetYAxisLabelDataType sets Y axis label data type (integer or float).
func (plot *Plot) SetYAxisLabelDataType(dataType PlotYAxisLabelDataType) {
plot.yAxisLabelDataType = dataType
}
// SetYAxisAutoScaleMin enables YAxis min value autoscale.
func (plot *Plot) SetYAxisAutoScaleMin(autoScale bool) {
plot.yAxisAutoScaleMin = autoScale
}
// SetYAxisAutoScaleMax enables YAxix max value autoscale.
func (plot *Plot) SetYAxisAutoScaleMax(autoScale bool) {
plot.yAxisAutoScaleMax = autoScale
}
// SetAxesColor sets axes x and y lines color.
func (plot *Plot) SetAxesColor(color tcell.Color) {
plot.axesColor = color
@ -152,21 +109,6 @@ func (plot *Plot) SetDrawAxes(draw bool) {
plot.drawAxes = draw
}
// SetDrawXAxisLabel set true in order to draw x axis label to screen.
func (plot *Plot) SetDrawXAxisLabel(draw bool) {
plot.drawXAxisLabel = draw
}
// SetXAxisLabelFunc sets x axis label function.
func (plot *Plot) SetXAxisLabelFunc(f func(int) string) {
plot.xAxisLabelFunc = f
}
// SetDrawYAxisLabel set true in order to draw y axis label to screen.
func (plot *Plot) SetDrawYAxisLabel(draw bool) {
plot.drawYAxisLabel = draw
}
// SetMarker sets marker type braille or dot mode.
func (plot *Plot) SetMarker(marker Marker) {
plot.marker = marker
@ -184,27 +126,7 @@ func (plot *Plot) SetData(data [][]float64) {
plot.brailleCellMap = make(map[image.Point]brailleCell)
plot.data = data
if plot.yAxisAutoScaleMax {
plot.maxVal = getMaxFloat64From2dSlice(data)
}
if plot.yAxisAutoScaleMin {
plot.minVal = getMinFloat64From2dSlice(data)
}
}
func (plot *Plot) SetMaxVal(maxVal float64) {
plot.maxVal = maxVal
}
func (plot *Plot) SetMinVal(minVal float64) {
plot.minVal = minVal
}
func (plot *Plot) SetYRange(minVal float64, maxVal float64) {
plot.minVal = minVal
plot.maxVal = maxVal
plot.maxVal = getMaxFloat64From2dSlice(data)
}
// SetDotMarkerRune sets dot marker rune.
@ -217,8 +139,7 @@ func (plot *Plot) getYAxisLabelsWidth() int {
return len(fmt.Sprintf("%.2f", plot.maxVal))
}
// GetPlotRect returns the rect for the inner part of the plot, ie not including axes.
func (plot *Plot) GetPlotRect() (int, int, int, int) {
func (plot *Plot) getChartAreaRect() (int, int, int, int) {
x, y, width, height := plot.Box.GetInnerRect()
plotYAxisLabelsWidth := plot.getYAxisLabelsWidth()
@ -271,137 +192,43 @@ func (plot *Plot) drawAxesToScreen(screen tcell.Screen) {
y+height-plotXAxisLabelsHeight-1,
tview.BoxDrawingsLightUpAndRight, axesStyle)
if plot.drawXAxisLabel {
plot.drawXAxisLabelsToScreen(screen, plotYAxisLabelsWidth, x, y, width, height)
// draw x axis labels
tview.Print(screen, "0",
x+plotYAxisLabelsWidth,
y+height-plotXAxisLabelsHeight,
1,
tview.AlignLeft, plot.axesLabelColor)
for labelX := x + plotYAxisLabelsWidth +
(plotXAxisLabelsGap)*plotHorizontalScale + 1; labelX < x+width-1; {
label := fmt.Sprintf(
"%d",
(labelX-(x+plotYAxisLabelsWidth)-1)/(plotHorizontalScale)+1,
)
tview.Print(screen, label, labelX, y+height-plotXAxisLabelsHeight, width, tview.AlignLeft, plot.axesLabelColor)
labelX += (len(label) + plotXAxisLabelsGap) * plotHorizontalScale
}
if plot.drawYAxisLabel {
plot.drawYAxisLabelsToScreen(screen, plotYAxisLabelsWidth, x, y, height)
}
}
//nolint:funlen,cyclop
func (plot *Plot) drawXAxisLabelsToScreen(
screen tcell.Screen, plotYAxisLabelsWidth int, x int, y int, width int, height int,
) {
xAxisAreaStartX := x + plotYAxisLabelsWidth + 1
xAxisAreaEndX := x + width
xAxisAvailableWidth := xAxisAreaEndX - xAxisAreaStartX
labelMap := map[int]string{}
labelStartMap := map[int]int{}
maxDataPoints := 0
for _, d := range plot.data {
maxDataPoints = max(maxDataPoints, len(d))
}
// determine the width needed for the largest label
maxXAxisLabelWidth := 0
for _, d := range plot.data {
for i := range d {
label := plot.xAxisLabelFunc(i)
labelMap[i] = label
maxXAxisLabelWidth = max(maxXAxisLabelWidth, len(label))
}
}
// determine the start position for each label, if they were
// to be centered below the data point.
// Note: not all of these labels will be printed, as they would
// overlap with each other
for i, label := range labelMap {
expectedLabelWidth := len(label)
if i == 0 {
expectedLabelWidth += plotXAxisLabelsGap / 2 //nolint:mnd
} else {
expectedLabelWidth += plotXAxisLabelsGap
}
currentLabelStart := i - int(math.Round(float64(expectedLabelWidth)/2)) //nolint:mnd
labelStartMap[i] = currentLabelStart
}
// print the labels, skipping those that would overlap,
// stopping when there is no more space
lastUsedLabelEnd := math.MinInt
initialOffset := xAxisAreaStartX
for i := range maxDataPoints {
labelStart := labelStartMap[i]
if labelStart < lastUsedLabelEnd {
// the label would overlap with the previous label
continue
}
rawLabel := labelMap[i]
labelWithGap := rawLabel
if i == 0 {
labelWithGap += strings.Repeat(gapRune, plotXAxisLabelsGap/2) //nolint:mnd
} else {
labelWithGap = strings.Repeat(gapRune, plotXAxisLabelsGap/2) + labelWithGap + strings.Repeat(gapRune, plotXAxisLabelsGap/2) //nolint:lll,mnd
}
expectedLabelWidth := len(labelWithGap)
remainingWidth := xAxisAvailableWidth - labelStart
if expectedLabelWidth > remainingWidth {
// the label would be too long to fit in the remaining space
if expectedLabelWidth-1 <= remainingWidth {
// if we omit the last gap, it fits, so we draw that before stopping
labelWithoutGap := labelWithGap[:len(labelWithGap)-1]
plot.printXAxisLabel(screen, labelWithoutGap, initialOffset+labelStart, y+height-plotXAxisLabelsHeight)
}
break
}
lastUsedLabelEnd = labelStart + expectedLabelWidth
plot.printXAxisLabel(screen, labelWithGap, initialOffset+labelStart, y+height-plotXAxisLabelsHeight)
}
}
func (plot *Plot) printXAxisLabel(screen tcell.Screen, label string, x, y int) {
tview.Print(screen, label, x, y, len(label), tview.AlignLeft, plot.axesLabelColor)
}
func (plot *Plot) drawYAxisLabelsToScreen(screen tcell.Screen, plotYAxisLabelsWidth int, x int, y int, height int) {
verticalOffset := plot.minVal
verticalScale := (plot.maxVal - plot.minVal) / float64(height-plotXAxisLabelsHeight-1)
previousLabel := ""
// draw Y axis labels
verticalScale := plot.maxVal / float64(height-plotXAxisLabelsHeight-1)
for i := 0; i*(plotYAxisLabelsGap+1) < height-1; i++ {
var label string
if plot.yAxisLabelDataType == PlotYAxisLabelDataFloat {
label = fmt.Sprintf("%.2f", float64(i)*verticalScale*(plotYAxisLabelsGap+1)+verticalOffset)
} else {
label = strconv.Itoa(int(float64(i)*verticalScale*(plotYAxisLabelsGap+1) + verticalOffset))
}
// Prevent same label being shown twice.
// Mainly relevant for integer labels with small data sets (in value)
if label == previousLabel {
continue
}
previousLabel = label
label := fmt.Sprintf("%.2f", float64(i)*verticalScale*(plotYAxisLabelsGap+1))
tview.Print(screen,
label,
x,
y+height-(i*(plotYAxisLabelsGap+1))-2, //nolint:mnd
y+height-(i*(plotYAxisLabelsGap+1))-2, //nolint:gomnd
plotYAxisLabelsWidth,
tview.AlignLeft, plot.axesLabelColor)
}
}
//nolint:cyclop,gocognit
//nolint:gocognit,cyclop
func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
x, y, width, height := plot.GetPlotRect()
x, y, width, height := plot.getChartAreaRect()
chartData := plot.getData()
verticalOffset := -plot.minVal
switch plot.ptype {
case PlotTypeLineChart:
@ -410,14 +237,7 @@ func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
for j := 0; j < len(line) && j*plotHorizontalScale < width; j++ {
val := line[j]
if math.IsNaN(val) {
continue
}
lheight := int(((val + verticalOffset) / plot.maxVal) * float64(height-1))
if lheight > height {
continue
}
lheight := int((val / plot.maxVal) * float64(height-1))
if (x+(j*plotHorizontalScale) < x+width) && (y+height-1-lheight < y+height) {
tview.PrintJoinedSemigraphics(screen, x+(j*plotHorizontalScale), y+height-1-lheight, plot.dotMarkerRune, style)
@ -430,14 +250,7 @@ func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
style := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(plot.lineColors[i])
for j, val := range line {
if math.IsNaN(val) {
continue
}
lheight := int(((val + verticalOffset) / plot.maxVal) * float64(height-1))
if lheight > height {
continue
}
lheight := int((val / plot.maxVal) * float64(height-1))
if (x+(j*plotHorizontalScale) < x+width) && (y+height-1-lheight < y+height) {
tview.PrintJoinedSemigraphics(screen, x+(j*plotHorizontalScale), y+height-1-lheight, plot.dotMarkerRune, style)
@ -448,7 +261,7 @@ func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
}
func (plot *Plot) drawBrailleMarkerToScreen(screen tcell.Screen) {
x, y, width, height := plot.GetPlotRect()
x, y, width, height := plot.getChartAreaRect()
plot.calcBrailleLines()
@ -461,25 +274,8 @@ func (plot *Plot) drawBrailleMarkerToScreen(screen tcell.Screen) {
}
}
func calcDataPointHeight(val, maxVal, minVal float64, height int) int {
return int(((val - minVal) / (maxVal - minVal)) * float64(height-1))
}
func calcDataPointHeightIfInBounds(val float64, maxVal float64, minVal float64, height int) (int, bool) {
if math.IsNaN(val) {
return 0, false
}
result := calcDataPointHeight(val, maxVal, minVal, height)
if (val > maxVal) || (val < minVal) || (result > height) {
return result, false
}
return result, true
}
func (plot *Plot) calcBrailleLines() {
x, y, _, height := plot.GetPlotRect()
x, y, _, height := plot.getChartAreaRect()
chartData := plot.getData()
for i, line := range chartData {
@ -487,57 +283,30 @@ func (plot *Plot) calcBrailleLines() {
continue
}
previousHeight := 0
lastValWasOk := false
previousHeight := int((line[0] / plot.maxVal) * float64(height-1))
for j, val := range line {
lheight, currentValIsOk := calcDataPointHeightIfInBounds(val, plot.maxVal, plot.minVal, height)
for j, val := range line[1:] {
lheight := int((val / plot.maxVal) * float64(height-1))
if !lastValWasOk && !currentValIsOk {
// nothing valid to draw, skip to next data point
continue
}
plot.setBrailleLine(
image.Pt(
(x+(j*plotHorizontalScale))*2, //nolint:gomnd
(y+height-previousHeight-1)*4, //nolint:gomnd
),
image.Pt(
(x+((j+1)*plotHorizontalScale))*2, //nolint:gomnd
(y+height-lheight-1)*4, //nolint:gomnd
),
plot.lineColors[i],
)
if !lastValWasOk { //nolint:gocritic
// current data point is single valid data point, draw it individually
plot.setBraillePoint(
calcBraillePoint(x, j+1, y, height, lheight),
plot.lineColors[i],
)
} else if !currentValIsOk {
// last data point was single valid data point, draw it individually
plot.setBraillePoint(
calcBraillePoint(x, j, y, height, previousHeight),
plot.lineColors[i],
)
} else {
// we have two valid data points, draw a line between them
plot.setBrailleLine(
calcBraillePoint(x, j, y, height, previousHeight),
calcBraillePoint(x, j+1, y, height, lheight),
plot.lineColors[i],
)
}
lastValWasOk = currentValIsOk
previousHeight = lheight
}
}
}
func calcBraillePoint(x, j, y, maxY, height int) image.Point {
return image.Pt(
(x+(j*plotHorizontalScale))*2, //nolint:mnd
(y+maxY-height-1)*4, //nolint:mnd
)
}
func (plot *Plot) setBraillePoint(p image.Point, color tcell.Color) {
if p.X < 0 || p.Y < 0 {
return
}
point := image.Pt(p.X/2, p.Y/4) //nolint:mnd
point := image.Pt(p.X/2, p.Y/4) //nolint:gomnd
plot.brailleCellMap[point] = brailleCell{
plot.brailleCellMap[point].cRune | brailleRune[p.Y%4][p.X%2],
color,

View File

@ -1,7 +1,6 @@
package tvxwidgets
import (
"math"
"sync"
"github.com/gdamore/tcell/v2"
@ -36,30 +35,24 @@ func (sl *Sparkline) Draw(screen tcell.Screen) {
// print label
if sl.dataTitle != "" {
tview.Print(screen, sl.dataTitle, x, y, width, tview.AlignLeft, sl.dataTitlecolor)
barHeight--
}
maxVal := getMaxFloat64FromSlice(sl.data)
if maxVal < 0 {
if maxVal == 0 {
return
}
// print lines
for i := 0; i < len(sl.data) && i+x < x+width; i++ {
data := sl.data[i]
if math.IsNaN(data) {
continue
}
dHeight := int((data / maxVal) * float64(barHeight))
sparkChar := barsRune[len(barsRune)-1]
style := tcell.StyleDefault.Background(sl.GetBackgroundColor()).Foreground(sl.lineColor)
for j := range dHeight {
for j := 0; j < dHeight; j++ {
tview.PrintJoinedSemigraphics(screen, i+x, y-1+height-j, sparkChar, style)
}

View File

@ -1,7 +1,6 @@
package tvxwidgets
import (
"math"
"strings"
"github.com/gdamore/tcell/v2"
@ -78,62 +77,26 @@ func getMaxFloat64From2dSlice(slices [][]float64) float64 {
}
var (
maxValue float64
max float64
maxIsInit bool
)
for _, slice := range slices {
for _, val := range slice {
if math.IsNaN(val) {
continue
}
if !maxIsInit {
maxIsInit = true
maxValue = val
max = val
continue
}
if val > maxValue {
maxValue = val
if val > max {
max = val
}
}
}
return maxValue
}
func getMinFloat64From2dSlice(slices [][]float64) float64 {
if len(slices) == 0 {
return 0
}
var (
minValue float64
minIsInit bool
)
for _, slice := range slices {
for _, val := range slice {
if math.IsNaN(val) {
continue
}
if !minIsInit {
minIsInit = true
minValue = val
continue
}
if val < minValue {
minValue = val
}
}
}
return minValue
return max
}
// returns max values in float64 slices.
@ -142,19 +105,14 @@ func getMaxFloat64FromSlice(slice []float64) float64 {
return 0
}
maxValue := -1.0
for i := range slice {
if math.IsNaN(slice[i]) {
continue
}
if slice[i] > maxValue {
maxValue = slice[i]
max := slice[0]
for i := 1; i < len(slice); i++ {
if slice[i] > max {
max = slice[i]
}
}
return maxValue
return max
}
func absInt(x int) int {
@ -167,11 +125,11 @@ func absInt(x int) int {
func drawLine(screen tcell.Screen, startX int, startY int, length int, mode drawLineMode, style tcell.Style) {
if mode == horizontalLine {
for i := range length {
for i := 0; i < length; i++ {
tview.PrintJoinedSemigraphics(screen, startX+i, startY, tview.BoxDrawingsLightTripleDashHorizontal, style)
}
} else if mode == verticalLine {
for i := range length {
for i := 0; i < length; i++ {
tview.PrintJoinedSemigraphics(screen, startX, startY+i, tview.BoxDrawingsLightTripleDashVertical, style)
}
}