mirror of
https://github.com/navidys/tvxwidgets.git
synced 2025-04-24 13:48:51 +08:00
Compare commits
171 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7b0dba28a6 | ||
![]() |
f7a965b9b1 | ||
![]() |
7a7632ee73 | ||
![]() |
5c75618ec1 | ||
![]() |
657ca691c4 | ||
![]() |
5c8253e8d9 | ||
![]() |
487c87b304 | ||
![]() |
d80a351c3c | ||
![]() |
9dc06ec28d | ||
![]() |
04fd610c5b | ||
![]() |
8a454d1c5e | ||
![]() |
b6def86254 | ||
![]() |
f97e54d849 | ||
![]() |
3ddb90bd10 | ||
![]() |
091fcae5c5 | ||
![]() |
12e9f953a3 | ||
![]() |
fb7e4a8141 | ||
![]() |
362ed76a2b | ||
![]() |
a6b632c4f6 | ||
![]() |
7e165355fc | ||
![]() |
00c6cca493 | ||
![]() |
543712cedb | ||
![]() |
329fcead2e | ||
![]() |
ca09c00451 | ||
![]() |
2036aacbcb | ||
![]() |
253e22ef1a | ||
![]() |
ac5e17721f | ||
![]() |
d3a7c777e0 | ||
![]() |
63d5ed1494 | ||
![]() |
0d4a60f69d | ||
![]() |
ed9012b1bf | ||
![]() |
5d0440879d | ||
![]() |
bb6ca0a79c | ||
![]() |
bba8fe74ca | ||
![]() |
6119df590d | ||
![]() |
004135d96f | ||
![]() |
cb36b86f14 | ||
![]() |
d3cf38290d | ||
![]() |
d46f48fbe0 | ||
![]() |
d3dc728102 | ||
![]() |
40e5944bdc | ||
![]() |
3cfbbb446a | ||
![]() |
718914020b | ||
![]() |
a89f40eb4c | ||
![]() |
d550a262e6 | ||
![]() |
e468a23392 | ||
![]() |
83dbdb49e6 | ||
![]() |
7e3024f613 | ||
![]() |
4632165f80 | ||
![]() |
9d6693060a | ||
![]() |
eafbe24268 | ||
![]() |
e95a6c091d | ||
![]() |
89b22f5d1d | ||
![]() |
5f3f2f99fc | ||
![]() |
6ec082d4f7 | ||
![]() |
444dcd17a9 | ||
![]() |
170954e0d7 | ||
![]() |
01b00ebe51 | ||
![]() |
3dd7ecf34b | ||
![]() |
ddfd01579e | ||
![]() |
1dfc3feec1 | ||
![]() |
ef7a0912d9 | ||
![]() |
4aedeab3b0 | ||
![]() |
1110c53694 | ||
![]() |
9cfa279d8d | ||
![]() |
5625bd86d5 | ||
![]() |
43529b16f2 | ||
![]() |
c35dc08b42 | ||
![]() |
984a311125 | ||
![]() |
335e9835f3 | ||
![]() |
a65ef0c1c5 | ||
![]() |
224c39bcd2 | ||
![]() |
3a2e991738 | ||
![]() |
1946bda711 | ||
![]() |
f1922269c0 | ||
![]() |
aa9f7893de | ||
![]() |
d5dda69eaa | ||
![]() |
770f88c853 | ||
![]() |
8987c247bb | ||
![]() |
7ee00ea004 | ||
![]() |
903d9edb12 | ||
![]() |
09f153c313 | ||
![]() |
c5e08f5545 | ||
![]() |
383bde043c | ||
![]() |
b555c093da | ||
![]() |
e3a8e9ee6f | ||
![]() |
24c34d8655 | ||
![]() |
578159ac6f | ||
![]() |
458cbc6c7c | ||
![]() |
0cac81fb0c | ||
![]() |
f98902679a | ||
![]() |
f797d8124c | ||
![]() |
e4b840b0d7 | ||
![]() |
87b5f8820b | ||
![]() |
78b9f38c5a | ||
![]() |
29c7875502 | ||
![]() |
03accf7634 | ||
![]() |
e9eb5cdbdc | ||
![]() |
f4a44f8b74 | ||
![]() |
bb2e442902 | ||
![]() |
c1149f4730 | ||
![]() |
9f6c73d584 | ||
![]() |
9a8419ba8b | ||
![]() |
852d3a1c2b | ||
![]() |
8c8addff08 | ||
![]() |
2ef68bb07e | ||
![]() |
f0f35dace5 | ||
![]() |
f50a0fb83b | ||
![]() |
36e3d86da4 | ||
![]() |
89ca96e1d6 | ||
![]() |
113ea8b938 | ||
![]() |
d640b32096 | ||
![]() |
1c80e02217 | ||
![]() |
55176a3e08 | ||
![]() |
fe7c2ef80c | ||
![]() |
172e1e9661 | ||
![]() |
6ef8527c29 | ||
![]() |
20d0b5b64b | ||
![]() |
0d11f83a24 | ||
![]() |
f492db7976 | ||
![]() |
bbacde1cec | ||
![]() |
d8a9aeb20e | ||
![]() |
82465e0608 | ||
![]() |
45b787ac4a | ||
![]() |
36cc9e3c83 | ||
![]() |
51013dd682 | ||
![]() |
d3cfaf38e9 | ||
![]() |
0b5b299832 | ||
![]() |
0ecdc1cf6f | ||
![]() |
02375f4dae | ||
![]() |
649e5a318f | ||
![]() |
d909054147 | ||
![]() |
c7479b197e | ||
![]() |
bdfbfe7785 | ||
![]() |
dd99cf47c5 | ||
![]() |
f2110b65ea | ||
![]() |
178e77cf79 | ||
![]() |
23229b36a7 | ||
![]() |
3578e68a90 | ||
![]() |
a75224ec82 | ||
![]() |
1f6c90cf5c | ||
![]() |
ae559d432d | ||
![]() |
17aafbbdb3 | ||
![]() |
077b765b6d | ||
![]() |
2a46635027 | ||
![]() |
2272eef37b | ||
![]() |
1623e315f8 | ||
![]() |
ac3d68d3ef | ||
![]() |
f2924b6431 | ||
![]() |
f1b3cfcf4d | ||
![]() |
e93d029785 | ||
![]() |
060941944a | ||
![]() |
63b1d41186 | ||
![]() |
5b5943da96 | ||
![]() |
ef53d4c169 | ||
![]() |
f74e16ce07 | ||
![]() |
71c13a7a36 | ||
![]() |
1e92566050 | ||
![]() |
25f8729bdf | ||
![]() |
a187d4ddc6 | ||
![]() |
ce6e3eea36 | ||
![]() |
4339ead54b | ||
![]() |
4b23c22c64 | ||
![]() |
4a0d6f3933 | ||
![]() |
759d88ec2f | ||
![]() |
48560bcf65 | ||
![]() |
2fead1154d | ||
![]() |
c3592690e1 | ||
![]() |
b1e54eab00 | ||
![]() |
f9a1efe3f5 | ||
![]() |
15b85032b3 |
50
.cirrus.yml
50
.cirrus.yml
@ -1,50 +0,0 @@
|
||||
---
|
||||
|
||||
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
|
49
.github/workflows/go.yml
vendored
Normal file
49
.github/workflows/go.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: Go
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
golangci-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.22'
|
||||
cache: false
|
||||
- run: |
|
||||
make .install.golangci-lint
|
||||
make lint
|
||||
|
||||
unit_test:
|
||||
name: Unit test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.22'
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
export GOBIN=$(pwd)/bin/
|
||||
make .install.ginkgo
|
||||
|
||||
- name: Generate coverage report
|
||||
run: |
|
||||
export GOBIN=$(pwd)/bin/
|
||||
make test-unit
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: .coverage/coverprofile
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: true
|
||||
slug: navidys/tvxwidgets
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
47
.github/workflows/pr.yml
vendored
47
.github/workflows/pr.yml
vendored
@ -32,7 +32,52 @@ jobs:
|
||||
- uses: codespell-project/actions-codespell@master
|
||||
with:
|
||||
check_filenames: true
|
||||
skip: ./.git,./vendor,*_test.go,go.sum,go.mod
|
||||
skip: ./.git,./vendor,*_test.go,go.sum,go.mod,*_test.go
|
||||
|
||||
golangci-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.22'
|
||||
cache: false
|
||||
- run: |
|
||||
make .install.golangci-lint
|
||||
make lint
|
||||
|
||||
unit_test:
|
||||
name: Unit test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.22'
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
export GOBIN=$(pwd)/bin/
|
||||
make .install.ginkgo
|
||||
|
||||
- name: Generate coverage report
|
||||
run: |
|
||||
export GOBIN=$(pwd)/bin/
|
||||
make test-unit
|
||||
|
||||
- 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: false
|
||||
|
||||
goreportcard:
|
||||
name: update reportcard
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,7 +6,7 @@
|
||||
*.dylib
|
||||
.vscode/*
|
||||
bin/*
|
||||
|
||||
.coverage
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
|
@ -1,21 +1,28 @@
|
||||
run:
|
||||
timeout: 10m
|
||||
deadline: 5m
|
||||
skip-dirs:
|
||||
- demos
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- exhaustruct
|
||||
- varnamelen
|
||||
- exhaustruct
|
||||
- depguard
|
||||
# deprecated
|
||||
- golint
|
||||
- maligned
|
||||
- interfacer
|
||||
- scopelint
|
||||
- exhaustivestruct
|
||||
- gomnd
|
||||
- execinquery
|
||||
- exportloopref
|
||||
- rowserrcheck
|
||||
- wastedassign
|
||||
linters-settings:
|
||||
errcheck:
|
||||
check-blank: false
|
||||
ignore: fmt:.*
|
||||
exclude-functions:
|
||||
- fmt:.*
|
||||
nolintlint:
|
||||
require-specific: true
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- demos
|
||||
exclude-files:
|
||||
- ".*_test.go"
|
||||
|
@ -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, socio-economic status,
|
||||
identity and expression, level of experience, education, socioeconomic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Contributing To Tvxwidgets
|
||||
|
||||
We'd love your contribtion on the project!
|
||||
We'd love your contribution on the project!
|
||||
|
||||
## Developer Certificate of Origin
|
||||
|
||||
|
37
Makefile
37
Makefile
@ -1,20 +1,29 @@
|
||||
TARGET := $(shell basename `pwd`)
|
||||
SRC = $(shell find . -type f -name '*.go' -not -path "./vendor/*")
|
||||
GO := go
|
||||
BIN := ./bin
|
||||
PRE_COMMIT = $(shell command -v bin/venv/bin/pre-commit ~/.local/bin/pre-commit pre-commit | head -n1)
|
||||
PKG_MANAGER ?= $(shell command -v dnf yum|head -n1)
|
||||
GINKO_CLI_VERSION = $(shell grep 'ginkgo/v2' go.mod | grep -o ' v.*' | sed 's/ //g' | sed 's|//indirect||g')
|
||||
COVERAGE_PATH ?= .coverage
|
||||
|
||||
#=================================================
|
||||
# Required tools installation tartgets
|
||||
#=================================================
|
||||
|
||||
.PHONY: install.tools
|
||||
install.tools: .install.pre-commit .install.codespell .install.golangci-lint ## Install needed tools
|
||||
install.tools: .install.pre-commit .install.codespell .install.golangci-lint .install.ginkgo ## Install needed tools
|
||||
|
||||
.PHONY: .install.codespell
|
||||
.install.codespell:
|
||||
sudo ${PKG_MANAGER} -y install codespell
|
||||
|
||||
.PHONY: .install.ginkgo
|
||||
.install.ginkgo:
|
||||
if [ ! -x "$(GOBIN)/ginkgo" ]; then \
|
||||
$(GO) install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@$(GINKO_CLI_VERSION) ; \
|
||||
fi
|
||||
|
||||
.PHONY: .install.pre-commit
|
||||
.install.pre-commit:
|
||||
if [ -z "$(PRE_COMMIT)" ]; then \
|
||||
@ -23,7 +32,29 @@ install.tools: .install.pre-commit .install.codespell .install.golangci-lint ##
|
||||
|
||||
.PHONY: .install.golangci-lint
|
||||
.install.golangci-lint:
|
||||
VERSION=1.46.2 ./hack/install_golangci.sh
|
||||
VERSION=1.61.0 ./hack/install_golangci.sh
|
||||
|
||||
#=================================================
|
||||
# Testing (units, functionality, ...) targets
|
||||
#=================================================
|
||||
|
||||
.PHONY: test
|
||||
test: test-unit
|
||||
|
||||
.PHONY: test-unit
|
||||
test-unit: ## Run unit tests
|
||||
rm -rf ${COVERAGE_PATH} && mkdir -p ${COVERAGE_PATH}
|
||||
$(GOBIN)/ginkgo \
|
||||
-r \
|
||||
--skip-package test/ \
|
||||
--cover \
|
||||
--covermode atomic \
|
||||
--coverprofile coverprofile \
|
||||
--output-dir ${COVERAGE_PATH} \
|
||||
--succinct
|
||||
$(GO) tool cover -html=${COVERAGE_PATH}/coverprofile -o ${COVERAGE_PATH}/coverage.html
|
||||
$(GO) tool cover -func=${COVERAGE_PATH}/coverprofile > ${COVERAGE_PATH}/functions
|
||||
cat ${COVERAGE_PATH}/functions | sed -n 's/\(total:\).*\([0-9][0-9].[0-9]\)/\1 \2/p'
|
||||
|
||||
#=================================================
|
||||
# Linting/Formatting/Code Validation targets
|
||||
@ -58,7 +89,7 @@ govet: ## Run govet
|
||||
.PHONY: codespell
|
||||
codespell: ## Run codespell
|
||||
@echo "running codespell"
|
||||
@codespell -S ./vendor,go.mod,go.sum,./.git
|
||||
@codespell -S ./vendor,go.mod,go.sum,./.git,*_test.go
|
||||
|
||||
#=================================================
|
||||
# Help menu
|
||||
|
24
README.md
24
README.md
@ -2,13 +2,26 @@
|
||||
|
||||
|
||||
[](https://pkg.go.dev/github.com/navidys/tvxwidgets)
|
||||

|
||||
[](https://codecov.io/gh/navidys/tvxwidgets)
|
||||
[](https://goreportcard.com/report/github.com/navidys/tvxwidgets)
|
||||
|
||||
tvxwidgets provides extra widgets for [tview](https://github.com/rivo/tview).
|
||||
`NOTE:` The project is at its early stages and under development, feel free to contribute and report bugs.
|
||||
|
||||

|
||||
|
||||
## Widgets
|
||||
|
||||
* [bar chart](./demos/barchart/)
|
||||
* [activity mode gauge](./demos/gauge_am/)
|
||||
* [percentage mode gauge](./demos/gauge_pm/)
|
||||
* [utilisation mode gauge](./demos/gauge_um/)
|
||||
* [message dialog (info and error)](./demos/dialog/)
|
||||
* [spinner](./demos/spinner/)
|
||||
* [plot (linechart, scatter)](./demos/plot/)
|
||||
* [sparkline](./demos/sparkline/)
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
@ -48,12 +61,3 @@ func main() {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Widgets
|
||||
|
||||
* bar chart
|
||||
* activity mode gauge
|
||||
* percentage mode gauge
|
||||
* utilisation mode gauge
|
||||
* message dialog (info and error)
|
||||
* spinner
|
||||
|
110
barchart.go
110
barchart.go
@ -1,7 +1,7 @@
|
||||
package tvxwidgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
@ -32,15 +32,19 @@ type BarChart struct {
|
||||
// barWidth width of bars
|
||||
barWidth int
|
||||
// hasBorder true if primitive has border
|
||||
hasBorder bool
|
||||
hasBorder bool
|
||||
axesColor tcell.Color
|
||||
axesLabelColor tcell.Color
|
||||
}
|
||||
|
||||
// NewBarChart returns a new bar chart primitive.
|
||||
func NewBarChart() *BarChart {
|
||||
chart := &BarChart{
|
||||
Box: tview.NewBox(),
|
||||
barGap: barGap,
|
||||
barWidth: barWidth,
|
||||
Box: tview.NewBox(),
|
||||
barGap: barGap,
|
||||
barWidth: barWidth,
|
||||
axesColor: tcell.ColorDimGray,
|
||||
axesLabelColor: tcell.ColorDimGray,
|
||||
}
|
||||
|
||||
return chart
|
||||
@ -57,17 +61,14 @@ func (c *BarChart) HasFocus() bool {
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (c *BarChart) Draw(screen tcell.Screen) { // nolint:funlen,cyclop
|
||||
style := tcell.StyleDefault
|
||||
style = style.Foreground(tview.Styles.BorderColor).Background(tview.Styles.PrimitiveBackgroundColor)
|
||||
|
||||
func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
|
||||
c.Box.DrawForSubclass(screen, c)
|
||||
|
||||
x, y, width, height := c.Box.GetInnerRect()
|
||||
// log.Printf("%d %d %d %d", x, y, width, height)
|
||||
|
||||
maxValY := y + 1
|
||||
xAxisStartY := y + height - 2 // nolint:gomnd
|
||||
barStartY := y + height - 3 // nolint:gomnd
|
||||
xAxisStartY := y + height - 2 //nolint:mnd
|
||||
barStartY := y + height - 3 //nolint:mnd
|
||||
borderPadding := 0
|
||||
|
||||
if c.hasBorder {
|
||||
@ -75,26 +76,40 @@ func (c *BarChart) Draw(screen tcell.Screen) { // nolint:funlen,cyclop
|
||||
}
|
||||
// set max value if not set
|
||||
c.initMaxValue()
|
||||
maxValueSr := fmt.Sprintf("%d", c.maxVal)
|
||||
maxValueSr := strconv.Itoa(c.maxVal)
|
||||
maxValLenght := len(maxValueSr) + 1
|
||||
|
||||
if maxValLenght < barChartYAxisLabelWidth {
|
||||
maxValLenght = barChartYAxisLabelWidth
|
||||
}
|
||||
// draw graph y-axis
|
||||
for i := borderPadding; i+y < y+height-borderPadding; i++ {
|
||||
tview.PrintJoinedSemigraphics(screen, x+maxValLenght, y+i, tview.Borders.Vertical, style)
|
||||
}
|
||||
// draw graph x-axix
|
||||
for i := maxValLenght + 1; i+x < x+width-borderPadding; i++ {
|
||||
tview.PrintJoinedSemigraphics(screen, x+i, xAxisStartY, tview.Borders.Horizontal, style)
|
||||
}
|
||||
tview.PrintJoinedSemigraphics(screen, x+maxValLenght, xAxisStartY, tview.BoxDrawingsLightVerticalAndRight, style)
|
||||
tview.PrintJoinedSemigraphics(screen, x+maxValLenght-1, xAxisStartY, '0', style)
|
||||
|
||||
axesStyle := tcell.StyleDefault.Background(c.GetBackgroundColor()).Foreground(c.axesColor)
|
||||
axesLabelStyle := tcell.StyleDefault.Background(c.GetBackgroundColor()).Foreground(c.axesLabelColor)
|
||||
|
||||
// draw Y axis line
|
||||
drawLine(screen,
|
||||
x+maxValLenght,
|
||||
y+borderPadding,
|
||||
height-borderPadding-1,
|
||||
verticalLine, axesStyle)
|
||||
|
||||
// draw X axis line
|
||||
drawLine(screen,
|
||||
x+maxValLenght+1,
|
||||
xAxisStartY,
|
||||
width-borderPadding-maxValLenght-1,
|
||||
horizontalLine, axesStyle)
|
||||
|
||||
tview.PrintJoinedSemigraphics(screen,
|
||||
x+maxValLenght,
|
||||
xAxisStartY,
|
||||
tview.BoxDrawingsLightUpAndRight, axesStyle)
|
||||
|
||||
tview.PrintJoinedSemigraphics(screen, x+maxValLenght-1, xAxisStartY, '0', axesLabelStyle)
|
||||
|
||||
mxValRune := []rune(maxValueSr)
|
||||
for i := 0; i < len(mxValRune); i++ {
|
||||
tview.PrintJoinedSemigraphics(screen, x+borderPadding+i, maxValY, mxValRune[i], style)
|
||||
for i := range mxValRune {
|
||||
tview.PrintJoinedSemigraphics(screen, x+borderPadding+i, maxValY, mxValRune[i], axesLabelStyle)
|
||||
}
|
||||
|
||||
// draw bars
|
||||
@ -108,23 +123,23 @@ func (c *BarChart) Draw(screen tcell.Screen) { // nolint:funlen,cyclop
|
||||
}
|
||||
// set labels
|
||||
r := []rune(item.label)
|
||||
for j := 0; j < len(r); j++ {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+j, labelY, r[j], style)
|
||||
for j := range r {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+j, labelY, r[j], axesLabelStyle)
|
||||
}
|
||||
// bar style
|
||||
bStyle := style.Foreground(item.color)
|
||||
bStyle := tcell.StyleDefault.Background(c.GetBackgroundColor()).Foreground(item.color)
|
||||
barHeight := c.getHeight(valueMaxHeight, item.value)
|
||||
|
||||
for k := 0; k < barHeight; k++ {
|
||||
for l := 0; l < c.barWidth; l++ {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+l, barStartY-k, '\u2588', bStyle)
|
||||
for k := range barHeight {
|
||||
for l := range c.barWidth {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+l, barStartY-k, fullBlockRune, bStyle)
|
||||
}
|
||||
}
|
||||
// bar value
|
||||
vSt := fmt.Sprintf("%d", item.value)
|
||||
vSt := strconv.Itoa(item.value)
|
||||
vRune := []rune(vSt)
|
||||
|
||||
for i := 0; i < len(vRune); i++ {
|
||||
for i := range vRune {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+i, barStartY-barHeight, vRune[i], bStyle)
|
||||
}
|
||||
|
||||
@ -155,8 +170,18 @@ func (c *BarChart) SetRect(x, y, width, height int) {
|
||||
}
|
||||
|
||||
// SetMaxValue sets maximum value of bars.
|
||||
func (c *BarChart) SetMaxValue(max int) {
|
||||
c.maxVal = max
|
||||
func (c *BarChart) SetMaxValue(maxValue int) {
|
||||
c.maxVal = maxValue
|
||||
}
|
||||
|
||||
// SetAxesColor sets axes x and y lines color.
|
||||
func (c *BarChart) SetAxesColor(color tcell.Color) {
|
||||
c.axesColor = color
|
||||
}
|
||||
|
||||
// SetAxesLabelColor sets axes x and y label color.
|
||||
func (c *BarChart) SetAxesLabelColor(color tcell.Color) {
|
||||
c.axesLabelColor = color
|
||||
}
|
||||
|
||||
// AddBar adds new bar item to the bar chart primitive.
|
||||
@ -168,9 +193,22 @@ 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 := 0; i < len(c.bars); i++ {
|
||||
for i := range c.bars {
|
||||
if c.bars[i].label == name {
|
||||
c.bars[i].value = value
|
||||
}
|
||||
|
67
barchart_test.go
Normal file
67
barchart_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/navidys/tvxwidgets"
|
||||
)
|
||||
|
||||
var _ = Describe("Barchart", Ordered, func() {
|
||||
var (
|
||||
app *tview.Application
|
||||
headerBox *tview.Box
|
||||
barchart *tvxwidgets.BarChart
|
||||
screen tcell.SimulationScreen
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
app = tview.NewApplication()
|
||||
headerBox = tview.NewBox().SetBorder(true)
|
||||
barchart = tvxwidgets.NewBarChart()
|
||||
screen = tcell.NewSimulationScreen("UTF-8")
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
appLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
appLayout.AddItem(headerBox, 1, 0, true)
|
||||
appLayout.AddItem(barchart, 50, 0, true)
|
||||
err := app.SetScreen(screen).SetRoot(appLayout, true).Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
Describe("Focus", func() {
|
||||
It("checks primitivie focus", func() {
|
||||
app.SetFocus(headerBox)
|
||||
app.Draw()
|
||||
Expect(barchart.HasFocus()).To(Equal(false))
|
||||
|
||||
app.SetFocus(barchart)
|
||||
app.Draw()
|
||||
Expect(barchart.HasFocus()).To(Equal(true))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetRect", func() {
|
||||
It("primitivie size", func() {
|
||||
x, y, width, heigth := barchart.GetRect()
|
||||
Expect(x).To(Equal(0))
|
||||
Expect(y).To(Equal(1))
|
||||
Expect(width).To(Equal(80))
|
||||
Expect(heigth).To(Equal(50))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
BIN
demo.gif
BIN
demo.gif
Binary file not shown.
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 345 KiB |
@ -19,6 +19,8 @@ func main() {
|
||||
barGraph.AddBar("swap", 40, tcell.ColorGreen)
|
||||
barGraph.AddBar("disk", 40, tcell.ColorOrange)
|
||||
barGraph.SetMaxValue(100)
|
||||
barGraph.SetAxesColor(tcell.ColorAntiqueWhite)
|
||||
barGraph.SetAxesLabelColor(tcell.ColorAntiqueWhite)
|
||||
|
||||
if err := app.SetRoot(barGraph, false).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
|
@ -2,6 +2,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
@ -13,15 +14,42 @@ import (
|
||||
func main() {
|
||||
app := tview.NewApplication()
|
||||
|
||||
// spinners
|
||||
spinners := []*tvxwidgets.Spinner{
|
||||
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerDotsCircling),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerDotsUpDown),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerBounce),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerLine),
|
||||
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCircleQuarters),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerSquareCorners),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCircleHalves),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerCorners),
|
||||
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerArrows),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerHamburger),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerStack),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerStar),
|
||||
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerGrowHorizontal),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerGrowVertical),
|
||||
tvxwidgets.NewSpinner().SetStyle(tvxwidgets.SpinnerBoxBounce),
|
||||
tvxwidgets.NewSpinner().SetCustomStyle([]rune{'🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚'}),
|
||||
}
|
||||
|
||||
spinnerRow := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
spinnerRow.SetBorder(true).SetTitle("spinners")
|
||||
|
||||
for _, spinner := range spinners {
|
||||
spinnerRow.AddItem(spinner, 0, 1, false)
|
||||
}
|
||||
|
||||
// bar graph
|
||||
barGraph := tvxwidgets.NewBarChart()
|
||||
barGraph.SetBorder(true)
|
||||
barGraph.SetTitle("bar chart")
|
||||
barGraph.AddBar("eth0", 20, tcell.ColorBlue)
|
||||
barGraph.AddBar("eth1", 60, tcell.ColorRed)
|
||||
barGraph.AddBar("eth2", 80, tcell.ColorGreen)
|
||||
barGraph.AddBar("eth3", 100, tcell.ColorOrange)
|
||||
barGraph := newBarChart()
|
||||
barGraph.SetMaxValue(100)
|
||||
barGraph.SetAxesColor(tcell.ColorAntiqueWhite)
|
||||
barGraph.SetAxesLabelColor(tcell.ColorAntiqueWhite)
|
||||
|
||||
// activity mode gauge
|
||||
amGauge := tvxwidgets.NewActivityModeGauge()
|
||||
@ -29,7 +57,7 @@ func main() {
|
||||
amGauge.SetPgBgColor(tcell.ColorOrange)
|
||||
amGauge.SetBorder(true)
|
||||
|
||||
// percetage mode gauge
|
||||
// percentage mode gauge
|
||||
pmGauge := tvxwidgets.NewPercentageModeGauge()
|
||||
pmGauge.SetTitle("percentage mode gauge")
|
||||
pmGauge.SetBorder(true)
|
||||
@ -51,15 +79,6 @@ func main() {
|
||||
swapGauge.SetLabelColor(tcell.ColorLightSkyBlue)
|
||||
swapGauge.SetBorder(false)
|
||||
|
||||
// dialogs
|
||||
errDialog := tvxwidgets.NewMessageDialog(tvxwidgets.ErrorDailog)
|
||||
errDialog.SetTitle("error dialog")
|
||||
errDialog.SetMessage("This is a sample tvxwidgets error dialog")
|
||||
|
||||
msgDialog := tvxwidgets.NewMessageDialog(tvxwidgets.InfoDialog)
|
||||
msgDialog.SetTitle("message dialog")
|
||||
msgDialog.SetMessage("[navy::]IMPORTANT MESSAGE[-::]\nThis is a sample tvxwidgets message dialog")
|
||||
|
||||
// utilisation flex
|
||||
utilFlex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
utilFlex.AddItem(cpuGauge, 1, 0, false)
|
||||
@ -68,20 +87,146 @@ func main() {
|
||||
utilFlex.SetTitle("utilisation mode gauge")
|
||||
utilFlex.SetBorder(true)
|
||||
|
||||
firstCol := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
firstCol.AddItem(barGraph, 11, 0, false)
|
||||
firstCol.AddItem(msgDialog, 12, 0, true)
|
||||
// plot (line charts)
|
||||
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] = 1 + math.Sin(float64(i)/5)
|
||||
data[1][i] = 1 + math.Cos(float64(i)/5)
|
||||
}
|
||||
return data
|
||||
}()
|
||||
|
||||
secondCol := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
secondCol.AddItem(amGauge, 3, 0, false)
|
||||
secondCol.AddItem(pmGauge, 3, 0, false)
|
||||
secondCol.AddItem(utilFlex, 5, 0, false)
|
||||
secondCol.AddItem(errDialog, 12, 0, false)
|
||||
bmLineChart := newBrailleModeLineChart()
|
||||
bmLineChart.SetData(sinData)
|
||||
|
||||
screenLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
screenLayout.AddItem(firstCol, 50, 0, false)
|
||||
screenLayout.AddItem(secondCol, 50, 0, false)
|
||||
dmLineChart := newDotModeLineChart()
|
||||
|
||||
sampleData1 := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
sampleData2 := []float64{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
|
||||
dotChartData := [][]float64{sampleData1}
|
||||
dotChartData[0] = append(dotChartData[0], sampleData2...)
|
||||
dotChartData[0] = append(dotChartData[0], sampleData1[:5]...)
|
||||
dotChartData[0] = append(dotChartData[0], sampleData2[5:]...)
|
||||
dotChartData[0] = append(dotChartData[0], sampleData1[:7]...)
|
||||
dotChartData[0] = append(dotChartData[0], sampleData2[3:]...)
|
||||
|
||||
dmLineChart.SetData(dotChartData)
|
||||
|
||||
// sparkline
|
||||
iowaitSparkline := tvxwidgets.NewSparkline()
|
||||
iowaitSparkline.SetBorder(false)
|
||||
iowaitSparkline.SetDataTitle("Disk IO (iowait)")
|
||||
iowaitSparkline.SetDataTitleColor(tcell.ColorDarkOrange)
|
||||
iowaitSparkline.SetLineColor(tcell.ColorMediumPurple)
|
||||
|
||||
systemSparkline := tvxwidgets.NewSparkline()
|
||||
systemSparkline.SetBorder(false)
|
||||
systemSparkline.SetDataTitle("Disk IO (system)")
|
||||
systemSparkline.SetDataTitleColor(tcell.ColorDarkOrange)
|
||||
systemSparkline.SetLineColor(tcell.ColorSteelBlue)
|
||||
|
||||
iowaitData := []float64{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}
|
||||
systemData := []float64{0, 0, 1, 2, 9, 5, 3, 1, 2, 0, 6, 3, 2, 2, 6, 8, 5, 2, 1, 5, 8, 6, 1, 4, 1, 1, 4, 3, 6}
|
||||
|
||||
ioSparkLineData := func() []float64 {
|
||||
for i := 0; i < 5; i++ {
|
||||
iowaitData = append(iowaitData, iowaitData...)
|
||||
}
|
||||
|
||||
return iowaitData
|
||||
}()
|
||||
|
||||
systemSparklineData := func() []float64 {
|
||||
for i := 0; i < 5; i++ {
|
||||
systemData = append(systemData, systemData...)
|
||||
}
|
||||
|
||||
return systemData
|
||||
}()
|
||||
|
||||
iowaitSparkline.SetData(ioSparkLineData)
|
||||
systemSparkline.SetData(systemSparklineData)
|
||||
|
||||
sparklineGroupLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
sparklineGroupLayout.SetBorder(true)
|
||||
sparklineGroupLayout.SetTitle("sparkline")
|
||||
sparklineGroupLayout.AddItem(iowaitSparkline, 0, 1, false)
|
||||
sparklineGroupLayout.AddItem(tview.NewBox(), 1, 0, false)
|
||||
sparklineGroupLayout.AddItem(systemSparkline, 0, 1, false)
|
||||
|
||||
// first row layout
|
||||
firstRowfirstCol := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
firstRowfirstCol.AddItem(barGraph, 0, 1, false)
|
||||
|
||||
firstRowSecondCol := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
firstRowSecondCol.AddItem(amGauge, 0, 3, false)
|
||||
firstRowSecondCol.AddItem(pmGauge, 0, 3, false)
|
||||
firstRowSecondCol.AddItem(utilFlex, 0, 5, false)
|
||||
|
||||
firstRow := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
firstRow.AddItem(firstRowfirstCol, 0, 1, false)
|
||||
firstRow.AddItem(firstRowSecondCol, 0, 1, false)
|
||||
|
||||
// second row
|
||||
plotRowLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
plotRowLayout.AddItem(bmLineChart, 0, 1, false)
|
||||
plotRowLayout.AddItem(dmLineChart, 0, 1, false)
|
||||
|
||||
screenLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
screenLayout.AddItem(firstRow, 11, 0, false)
|
||||
screenLayout.AddItem(plotRowLayout, 15, 0, false)
|
||||
screenLayout.AddItem(sparklineGroupLayout, 6, 0, false)
|
||||
screenLayout.AddItem(spinnerRow, 3, 0, false)
|
||||
|
||||
screenLayout.SetRect(0, 0, 100, 40)
|
||||
|
||||
// upgrade datat functions
|
||||
moveDotChartData := func() {
|
||||
newData := append(dotChartData[0], dotChartData[0][0])
|
||||
dotChartData[0] = newData[1:]
|
||||
}
|
||||
|
||||
moveDiskIOData := func() ([]float64, []float64) {
|
||||
|
||||
newIOWaitData := ioSparkLineData[1:]
|
||||
newIOWaitData = append(newIOWaitData, ioSparkLineData[0])
|
||||
ioSparkLineData = newIOWaitData
|
||||
|
||||
newSystemData := systemSparklineData[1:]
|
||||
newSystemData = append(newSystemData, systemSparklineData[0])
|
||||
systemSparklineData = newSystemData
|
||||
|
||||
return newIOWaitData, newSystemData
|
||||
}
|
||||
|
||||
moveSinData := func(data [][]float64) [][]float64 {
|
||||
newData := make([][]float64, 2)
|
||||
newData[0] = rotate(data[0], -1)
|
||||
newData[1] = rotate(data[1], -1)
|
||||
return newData
|
||||
}
|
||||
|
||||
updateSpinner := func() {
|
||||
spinnerTick := time.NewTicker(100 * time.Millisecond)
|
||||
for {
|
||||
select {
|
||||
case <-spinnerTick.C:
|
||||
// update spinners
|
||||
for _, spinner := range spinners {
|
||||
spinner.Pulse()
|
||||
}
|
||||
// update gauge
|
||||
amGauge.Pulse()
|
||||
app.Draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update screen ticker
|
||||
update := func() {
|
||||
value := 0
|
||||
maxValue := pmGauge.GetMaxValue()
|
||||
@ -90,8 +235,6 @@ func main() {
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
// update gauge
|
||||
amGauge.Pulse()
|
||||
|
||||
if value > maxValue {
|
||||
value = 0
|
||||
@ -100,27 +243,99 @@ func main() {
|
||||
}
|
||||
pmGauge.SetValue(value)
|
||||
|
||||
// update bar graph
|
||||
rangeLower := 0
|
||||
rangeUpper := 100
|
||||
randomNum := rangeLower + rand.Intn(rangeUpper-rangeLower+1)
|
||||
barGraph.SetBarValue("eth0", randomNum)
|
||||
cpuGauge.SetValue(float64(randomNum))
|
||||
randomNum = rangeLower + rand.Intn(rangeUpper-rangeLower+1)
|
||||
barGraph.SetBarValue("eth1", randomNum)
|
||||
memGauge.SetValue(float64(randomNum))
|
||||
randomNum = rangeLower + rand.Intn(rangeUpper-rangeLower+1)
|
||||
barGraph.SetBarValue("eth2", randomNum)
|
||||
swapGauge.SetValue(float64(randomNum))
|
||||
randomNum = rangeLower + rand.Intn(rangeUpper-rangeLower+1)
|
||||
barGraph.SetBarValue("eth3", randomNum)
|
||||
randNum1 := float64(rand.Float64() * 100)
|
||||
randNum2 := float64(rand.Float64() * 100)
|
||||
randNum3 := float64(rand.Float64() * 100)
|
||||
randNum4 := float64(rand.Float64() * 100)
|
||||
|
||||
barGraph.SetBarValue("eth0", int(randNum1))
|
||||
cpuGauge.SetValue(randNum1)
|
||||
barGraph.SetBarValue("eth1", int(randNum2))
|
||||
memGauge.SetValue(randNum2)
|
||||
barGraph.SetBarValue("eth2", int(randNum3))
|
||||
swapGauge.SetValue(randNum3)
|
||||
barGraph.SetBarValue("eth3", int(randNum4))
|
||||
|
||||
// move line charts
|
||||
sinData = moveSinData(sinData)
|
||||
bmLineChart.SetData(sinData)
|
||||
|
||||
moveDotChartData()
|
||||
dmLineChart.SetData(dotChartData)
|
||||
|
||||
d1, d2 := moveDiskIOData()
|
||||
iowaitSparkline.SetData(d1)
|
||||
systemSparkline.SetData(d2)
|
||||
|
||||
app.Draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go updateSpinner()
|
||||
go update()
|
||||
|
||||
if err := app.SetRoot(screenLayout, false).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func newDotModeLineChart() *tvxwidgets.Plot {
|
||||
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')
|
||||
|
||||
return dmLineChart
|
||||
}
|
||||
|
||||
func newBrailleModeLineChart() *tvxwidgets.Plot {
|
||||
bmLineChart := tvxwidgets.NewPlot()
|
||||
bmLineChart.SetBorder(true)
|
||||
bmLineChart.SetTitle("line chart (braille mode)")
|
||||
bmLineChart.SetLineColor([]tcell.Color{
|
||||
tcell.ColorSteelBlue,
|
||||
tcell.ColorGreen,
|
||||
})
|
||||
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
|
||||
|
||||
return bmLineChart
|
||||
}
|
||||
|
||||
func newBarChart() *tvxwidgets.BarChart {
|
||||
barGraph := tvxwidgets.NewBarChart()
|
||||
barGraph.SetBorder(true)
|
||||
barGraph.SetTitle("bar chart")
|
||||
barGraph.AddBar("eth0", 20, tcell.ColorBlue)
|
||||
barGraph.AddBar("eth1", 60, tcell.ColorRed)
|
||||
barGraph.AddBar("eth2", 80, tcell.ColorGreen)
|
||||
barGraph.AddBar("eth3", 100, tcell.ColorOrange)
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -25,10 +25,8 @@ func main() {
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
rangeLower := 0
|
||||
rangeUpper := 100
|
||||
randomNum := rangeLower + rand.Intn(rangeUpper-rangeLower+1)
|
||||
gauge.SetValue(float64(randomNum))
|
||||
randNum := float64(rand.Float64() * 100)
|
||||
gauge.SetValue(randNum)
|
||||
app.Draw()
|
||||
}
|
||||
}
|
||||
|
1
demos/plot/README.md
Normal file
1
demos/plot/README.md
Normal file
@ -0,0 +1 @@
|
||||

|
107
demos/plot/main.go
Normal file
107
demos/plot/main.go
Normal file
@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/navidys/tvxwidgets"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
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] = 1 + 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] = 1 + 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.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.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.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.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)
|
||||
}
|
||||
}
|
BIN
demos/plot/screenshot.png
Normal file
BIN
demos/plot/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
1
demos/plot_custom_range/README.md
Normal file
1
demos/plot_custom_range/README.md
Normal file
@ -0,0 +1 @@
|
||||

|
119
demos/plot_custom_range/main.go
Normal file
119
demos/plot_custom_range/main.go
Normal file
@ -0,0 +1,119 @@
|
||||
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)
|
||||
}
|
||||
}
|
BIN
demos/plot_custom_range/screenshot.png
Normal file
BIN
demos/plot_custom_range/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
1
demos/plot_xaxis_labels/README.md
Normal file
1
demos/plot_xaxis_labels/README.md
Normal file
@ -0,0 +1 @@
|
||||

|
133
demos/plot_xaxis_labels/main.go
Normal file
133
demos/plot_xaxis_labels/main.go
Normal file
@ -0,0 +1,133 @@
|
||||
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)
|
||||
}
|
||||
}
|
BIN
demos/plot_xaxis_labels/screenshot.png
Normal file
BIN
demos/plot_xaxis_labels/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
93
demos/sparkline/main.go
Normal file
93
demos/sparkline/main.go
Normal file
@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/navidys/tvxwidgets"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := tview.NewApplication()
|
||||
iowaitSparkline := tvxwidgets.NewSparkline()
|
||||
iowaitSparkline.SetBorder(false)
|
||||
iowaitSparkline.SetDataTitle("Disk I/O (iowait)")
|
||||
iowaitSparkline.SetBorderColor(tcell.ColorDimGray)
|
||||
iowaitSparkline.SetTitleColor(tcell.ColorDimGray)
|
||||
iowaitSparkline.SetDataTitleColor(tcell.ColorDarkOrange)
|
||||
iowaitSparkline.SetLineColor(tcell.ColorMediumPurple)
|
||||
|
||||
systemSparkline := tvxwidgets.NewSparkline()
|
||||
systemSparkline.SetBorder(false)
|
||||
systemSparkline.SetDataTitle("Disk I/O (system)")
|
||||
systemSparkline.SetBorderColor(tcell.ColorDimGray)
|
||||
systemSparkline.SetTitleColor(tcell.ColorDimGray)
|
||||
systemSparkline.SetDataTitleColor(tcell.ColorDarkOrange)
|
||||
systemSparkline.SetLineColor(tcell.ColorSteelBlue)
|
||||
|
||||
iowaitData := []float64{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}
|
||||
systemData := []float64{0, 0, 1, 2, 9, 5, 3, 1, 2, 0, 6, 3, 2, 2, 6, 8, 5, 2, 1, 5, 8, 6, 1, 4, 1, 1, 4, 3, 6}
|
||||
|
||||
ioSparkLineData := func() []float64 {
|
||||
for i := 0; i < 5; i++ {
|
||||
iowaitData = append(iowaitData, iowaitData...)
|
||||
}
|
||||
|
||||
return iowaitData
|
||||
}()
|
||||
|
||||
systemSparklineData := func() []float64 {
|
||||
for i := 0; i < 5; i++ {
|
||||
systemData = append(systemData, systemData...)
|
||||
}
|
||||
|
||||
return systemData
|
||||
}()
|
||||
|
||||
iowaitSparkline.SetData(ioSparkLineData)
|
||||
systemSparkline.SetData(systemSparklineData)
|
||||
|
||||
sparklineGroupLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
sparklineGroupLayout.SetBorder(true)
|
||||
sparklineGroupLayout.SetBorderColor(tcell.ColorDimGray)
|
||||
sparklineGroupLayout.SetTitle("DISK IO")
|
||||
sparklineGroupLayout.SetTitleColor(tcell.ColorDarkOrange)
|
||||
sparklineGroupLayout.AddItem(iowaitSparkline, 0, 1, false)
|
||||
sparklineGroupLayout.AddItem(tview.NewBox(), 1, 0, false)
|
||||
sparklineGroupLayout.AddItem(systemSparkline, 0, 1, false)
|
||||
|
||||
moveData := func() ([]float64, []float64) {
|
||||
|
||||
newIOWaitData := ioSparkLineData[1:]
|
||||
newIOWaitData = append(newIOWaitData, ioSparkLineData[0])
|
||||
ioSparkLineData = newIOWaitData
|
||||
|
||||
newSystemData := systemSparklineData[1:]
|
||||
newSystemData = append(newSystemData, systemSparklineData[0])
|
||||
systemSparklineData = newSystemData
|
||||
|
||||
return newIOWaitData, newSystemData
|
||||
}
|
||||
|
||||
update := func() {
|
||||
tick := time.NewTicker(500 * time.Millisecond)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
d1, d2 := moveData()
|
||||
iowaitSparkline.SetData(d1)
|
||||
systemSparkline.SetData(d2)
|
||||
|
||||
app.Draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go update()
|
||||
|
||||
if err := app.SetRoot(sparklineGroupLayout, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
1
demos/sparkline/readme.md
Normal file
1
demos/sparkline/readme.md
Normal file
@ -0,0 +1 @@
|
||||

|
BIN
demos/sparkline/screenshot.png
Normal file
BIN
demos/sparkline/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
28
dialog.go
28
dialog.go
@ -37,7 +37,7 @@ type MessageDialog struct {
|
||||
bgColor tcell.Color
|
||||
// message dialog text message to display.
|
||||
message string
|
||||
// callback for whwen user clicked on the the button or presses "enter" or "esc"
|
||||
// callback for when user clicked on the button or presses "enter" or "esc"
|
||||
doneHandler func()
|
||||
}
|
||||
|
||||
@ -68,18 +68,15 @@ func NewMessageDialog(dtype int) *MessageDialog {
|
||||
return dialog
|
||||
}
|
||||
|
||||
// SetBorder sets dialogs border - no effect always true.
|
||||
func (d *MessageDialog) SetBorder(status bool) {}
|
||||
|
||||
// SetType sets dialog type to info or error.
|
||||
func (d *MessageDialog) SetType(dtype int) {
|
||||
if dtype >= 0 && dtype <= 2 {
|
||||
if dtype >= 0 && dtype <= 1 {
|
||||
d.messageType = dtype
|
||||
d.setColor()
|
||||
}
|
||||
}
|
||||
|
||||
// SetTitle sets title for this primitive.
|
||||
// SetTitle sets dialog title.
|
||||
func (d *MessageDialog) SetTitle(title string) {
|
||||
d.layout.SetTitle(title)
|
||||
}
|
||||
@ -134,13 +131,14 @@ func (d *MessageDialog) Draw(screen tcell.Screen) {
|
||||
// InputHandler returns input handler function for this primitive.
|
||||
func (d *MessageDialog) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
if event.Key() == tcell.KeyDown || event.Key() == tcell.KeyUp || event.Key() == tcell.KeyPgDn || event.Key() == tcell.KeyPgUp { // nolint:lll
|
||||
if event.Key() == tcell.KeyDown || event.Key() == tcell.KeyUp || event.Key() == tcell.KeyPgDn || event.Key() == tcell.KeyPgUp { //nolint:lll
|
||||
if textHandler := d.textview.InputHandler(); textHandler != nil {
|
||||
textHandler(event, setFocus)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if formHandler := d.form.InputHandler(); formHandler != nil {
|
||||
formHandler(event, setFocus)
|
||||
|
||||
@ -150,12 +148,13 @@ func (d *MessageDialog) InputHandler() func(event *tcell.EventKey, setFocus func
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (d *MessageDialog) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { // nolint:lll
|
||||
return d.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { // nolint:lll,nonamedreturns
|
||||
func (d *MessageDialog) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { //nolint:lll
|
||||
return d.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { //nolint:lll,nonamedreturns
|
||||
// Pass mouse events on to the form.
|
||||
consumed, capture = d.form.MouseHandler()(action, event, setFocus)
|
||||
if !consumed && action == tview.MouseLeftClick && d.InRect(event.Position()) {
|
||||
setFocus(d)
|
||||
|
||||
consumed = true
|
||||
}
|
||||
|
||||
@ -164,7 +163,7 @@ func (d *MessageDialog) MouseHandler() func(action tview.MouseAction, event *tce
|
||||
}
|
||||
|
||||
// SetDoneFunc sets callback function for when user clicked on
|
||||
// the the button or presses "enter" or "esc".
|
||||
// the button or presses "enter" or "esc".
|
||||
func (d *MessageDialog) SetDoneFunc(handler func()) *MessageDialog {
|
||||
d.doneHandler = handler
|
||||
enterButton := d.form.GetButton(d.form.GetButtonCount() - 1)
|
||||
@ -173,6 +172,11 @@ func (d *MessageDialog) SetDoneFunc(handler func()) *MessageDialog {
|
||||
return d
|
||||
}
|
||||
|
||||
// GetBackgroundColor returns dialog background color.
|
||||
func (d *MessageDialog) GetBackgroundColor() tcell.Color {
|
||||
return d.bgColor
|
||||
}
|
||||
|
||||
func (d *MessageDialog) setColor() {
|
||||
var bgColor tcell.Color
|
||||
|
||||
@ -186,11 +190,13 @@ func (d *MessageDialog) setColor() {
|
||||
d.form.SetBackgroundColor(bgColor)
|
||||
d.textview.SetBackgroundColor(bgColor)
|
||||
d.layout.SetBackgroundColor(bgColor)
|
||||
|
||||
d.bgColor = bgColor
|
||||
}
|
||||
|
||||
func (d *MessageDialog) setRect() {
|
||||
maxHeight := d.height
|
||||
maxWidth := d.width // nolint:ifshort
|
||||
maxWidth := d.width //nolint:ifshort
|
||||
messageHeight := len(strings.Split(d.message, "\n"))
|
||||
messageWidth := getMessageWidth(d.message)
|
||||
|
||||
|
73
dialog_test.go
Normal file
73
dialog_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/navidys/tvxwidgets"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
var _ = Describe("Dialog", Ordered, func() {
|
||||
var (
|
||||
app *tview.Application
|
||||
headerBox *tview.Box
|
||||
msgDialog *tvxwidgets.MessageDialog
|
||||
screen tcell.SimulationScreen
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
app = tview.NewApplication()
|
||||
headerBox = tview.NewBox().SetBorder(true)
|
||||
msgDialog = tvxwidgets.NewMessageDialog(tvxwidgets.InfoDialog)
|
||||
screen = tcell.NewSimulationScreen("UTF-8")
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
appLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
appLayout.AddItem(headerBox, 0, 1, true)
|
||||
appLayout.AddItem(msgDialog, 0, 1, true)
|
||||
err := app.SetScreen(screen).SetRoot(appLayout, true).Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
Describe("NewMessageDialog", func() {
|
||||
It("returns a new message dialog primitive", func() {
|
||||
tests := []struct {
|
||||
msgType int
|
||||
bgColor tcell.Color
|
||||
}{
|
||||
{msgType: tvxwidgets.InfoDialog, bgColor: tcell.ColorSteelBlue},
|
||||
{msgType: tvxwidgets.ErrorDailog, bgColor: tcell.ColorOrangeRed},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
msgDialog.SetType(test.msgType)
|
||||
app.Draw()
|
||||
Expect(msgDialog.GetBackgroundColor()).To(Equal(test.bgColor))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Focus", func() {
|
||||
It("checks primitivie focus", func() {
|
||||
app.SetFocus(headerBox)
|
||||
app.Draw()
|
||||
Expect(msgDialog.HasFocus()).To(Equal(false))
|
||||
|
||||
app.SetFocus(msgDialog)
|
||||
app.Draw()
|
||||
Expect(msgDialog.HasFocus()).To(Equal(true))
|
||||
})
|
||||
})
|
||||
})
|
10
export_test.go
Normal file
10
export_test.go
Normal file
@ -0,0 +1,10 @@
|
||||
package tvxwidgets
|
||||
|
||||
var (
|
||||
GetColorName = getColorName
|
||||
GetMessageWidth = getMessageWidth
|
||||
GetMaxFloat64From2dSlice = getMaxFloat64From2dSlice
|
||||
GetMaxFloat64FromSlice = getMaxFloat64FromSlice
|
||||
DrawLine = drawLine
|
||||
AbsInt = absInt
|
||||
)
|
17
gauge_am.go
17
gauge_am.go
@ -34,18 +34,13 @@ func (g *ActivityModeGauge) Draw(screen tcell.Screen) {
|
||||
x, y, width, height := g.Box.GetInnerRect()
|
||||
tickStr := g.tickStr(width)
|
||||
|
||||
for i := 0; i < height; i++ {
|
||||
for i := range height {
|
||||
tview.Print(screen, tickStr, x, y+i, width, tview.AlignLeft, g.pgBgColor)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTitle sets title for this primitive.
|
||||
func (g *ActivityModeGauge) SetTitle(title string) {
|
||||
g.Box.SetTitle(title)
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (g *ActivityModeGauge) Focus(delegate func(p tview.Primitive)) {
|
||||
func (g *ActivityModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
@ -78,27 +73,27 @@ func (g *ActivityModeGauge) Reset() {
|
||||
g.counter = 0
|
||||
}
|
||||
|
||||
func (g *ActivityModeGauge) tickStr(max int) string {
|
||||
func (g *ActivityModeGauge) tickStr(maxCount int) string {
|
||||
var (
|
||||
prgHeadStr string
|
||||
prgEndStr string
|
||||
prgStr string
|
||||
)
|
||||
|
||||
if g.counter >= max-4 {
|
||||
if g.counter >= maxCount-4 {
|
||||
g.counter = 0
|
||||
}
|
||||
|
||||
hWidth := 0
|
||||
|
||||
for i := 0; i < g.counter; i++ {
|
||||
for range g.counter {
|
||||
prgHeadStr += fmt.Sprintf("[%s::]%s", getColorName(tview.Styles.PrimitiveBackgroundColor), prgCell)
|
||||
hWidth++
|
||||
}
|
||||
|
||||
prgStr = prgCell + prgCell + prgCell + prgCell
|
||||
|
||||
for i := 0; i < max+hWidth+4; i++ {
|
||||
for range maxCount + hWidth + 4 {
|
||||
prgEndStr += fmt.Sprintf("[%s::]%s", getColorName(tview.Styles.PrimitiveBackgroundColor), prgCell)
|
||||
}
|
||||
|
||||
|
68
gauge_am_test.go
Normal file
68
gauge_am_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/navidys/tvxwidgets"
|
||||
)
|
||||
|
||||
var _ = Describe("GaugeAm", Ordered, func() {
|
||||
var (
|
||||
app *tview.Application
|
||||
headerBox *tview.Box
|
||||
gaugeAm *tvxwidgets.ActivityModeGauge
|
||||
screen tcell.SimulationScreen
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
app = tview.NewApplication()
|
||||
headerBox = tview.NewBox().SetBorder(true)
|
||||
gaugeAm = tvxwidgets.NewActivityModeGauge()
|
||||
screen = tcell.NewSimulationScreen("UTF-8")
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
appLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
appLayout.AddItem(headerBox, 1, 0, true)
|
||||
appLayout.AddItem(gaugeAm, 50, 0, true)
|
||||
err := app.SetScreen(screen).SetRoot(appLayout, true).Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
Describe("Focus", func() {
|
||||
It("checks primitivie focus", func() {
|
||||
app.SetFocus(headerBox)
|
||||
app.Draw()
|
||||
Expect(gaugeAm.HasFocus()).To(Equal(false))
|
||||
|
||||
app.SetFocus(gaugeAm)
|
||||
gaugeAm.Pulse()
|
||||
app.Draw()
|
||||
// gauge will not get focus
|
||||
Expect(gaugeAm.HasFocus()).To(Equal(false))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetRect", func() {
|
||||
It("primitivie size", func() {
|
||||
x, y, width, heigth := gaugeAm.GetRect()
|
||||
Expect(x).To(Equal(0))
|
||||
Expect(y).To(Equal(1))
|
||||
Expect(width).To(Equal(80))
|
||||
Expect(heigth).To(Equal(50))
|
||||
})
|
||||
})
|
||||
})
|
20
gauge_pm.go
20
gauge_pm.go
@ -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 := 0; i < height; i++ {
|
||||
for j := 0; j < prgBlock; j++ {
|
||||
for i := range height {
|
||||
for j := range prgBlock {
|
||||
screen.SetContent(x+j, y+i, ' ', nil, style)
|
||||
}
|
||||
}
|
||||
@ -56,26 +56,22 @@ func (g *PercentageModeGauge) Draw(screen tcell.Screen) {
|
||||
// print percentage in middle of box
|
||||
|
||||
pcRune := []rune(pcString)
|
||||
for j := 0; j < len(pcRune); j++ {
|
||||
for j := range pcRune {
|
||||
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 := 0; i < height; i++ {
|
||||
for i := range height {
|
||||
screen.SetContent(tX+j, y+i, ' ', nil, style)
|
||||
}
|
||||
|
||||
screen.SetContent(tX+j, tY, pcRune[j], nil, style)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTitle sets title for this primitive.
|
||||
func (g *PercentageModeGauge) SetTitle(title string) {
|
||||
g.Box.SetTitle(title)
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (g *PercentageModeGauge) Focus(delegate func(p tview.Primitive)) {
|
||||
func (g *PercentageModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
@ -127,13 +123,13 @@ func (g *PercentageModeGauge) Reset() {
|
||||
g.value = 0
|
||||
}
|
||||
|
||||
func (g *PercentageModeGauge) progressBlock(max int) int {
|
||||
func (g *PercentageModeGauge) progressBlock(maxValue int) int {
|
||||
if g.maxValue == 0 {
|
||||
return g.maxValue
|
||||
}
|
||||
|
||||
pc := g.value * gaugeMaxPc / g.maxValue
|
||||
value := pc * max / gaugeMaxPc
|
||||
value := pc * maxValue / gaugeMaxPc
|
||||
|
||||
return value
|
||||
}
|
||||
|
67
gauge_pm_test.go
Normal file
67
gauge_pm_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/navidys/tvxwidgets"
|
||||
)
|
||||
|
||||
var _ = Describe("GaugePm", Ordered, func() {
|
||||
var (
|
||||
app *tview.Application
|
||||
headerBox *tview.Box
|
||||
gaugePm *tvxwidgets.PercentageModeGauge
|
||||
screen tcell.SimulationScreen
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
app = tview.NewApplication()
|
||||
headerBox = tview.NewBox().SetBorder(true)
|
||||
gaugePm = tvxwidgets.NewPercentageModeGauge()
|
||||
screen = tcell.NewSimulationScreen("UTF-8")
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
appLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
appLayout.AddItem(headerBox, 1, 0, true)
|
||||
appLayout.AddItem(gaugePm, 50, 0, true)
|
||||
err := app.SetScreen(screen).SetRoot(appLayout, true).Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
Describe("Focus", func() {
|
||||
It("checks primitivie focus", func() {
|
||||
app.SetFocus(headerBox)
|
||||
app.Draw()
|
||||
Expect(gaugePm.HasFocus()).To(Equal(false))
|
||||
|
||||
app.SetFocus(gaugePm)
|
||||
app.Draw()
|
||||
// gauge will not get focus
|
||||
Expect(gaugePm.HasFocus()).To(Equal(false))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetRect", func() {
|
||||
It("primitivie size", func() {
|
||||
x, y, width, heigth := gaugePm.GetRect()
|
||||
Expect(x).To(Equal(0))
|
||||
Expect(y).To(Equal(1))
|
||||
Expect(width).To(Equal(80))
|
||||
Expect(heigth).To(Equal(50))
|
||||
})
|
||||
})
|
||||
})
|
16
gauge_um.go
16
gauge_um.go
@ -48,11 +48,6 @@ func NewUtilModeGauge() *UtilModeGauge {
|
||||
return gauge
|
||||
}
|
||||
|
||||
// SetTitle sets title for this primitive.
|
||||
func (g *UtilModeGauge) SetTitle(title string) {
|
||||
g.Box.SetTitle(title)
|
||||
}
|
||||
|
||||
// SetLabel sets label for this primitive.
|
||||
func (g *UtilModeGauge) SetLabel(label string) {
|
||||
g.label = label
|
||||
@ -64,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)) {
|
||||
func (g *UtilModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
@ -102,8 +97,8 @@ func (g *UtilModeGauge) Draw(screen tcell.Screen) {
|
||||
labelWidth := len(g.label)
|
||||
barWidth := width - labelPCWidth - labelWidth
|
||||
|
||||
for i := 0; i < barWidth; i++ {
|
||||
for j := 0; j < height; j++ {
|
||||
for i := range barWidth {
|
||||
for j := range height {
|
||||
value := float64(i * 100 / barWidth)
|
||||
color := g.getBarColor(value)
|
||||
|
||||
@ -152,3 +147,8 @@ func (g *UtilModeGauge) getBarColor(percentage float64) tcell.Color {
|
||||
|
||||
return g.critColor
|
||||
}
|
||||
|
||||
// SetEmptyColor sets empty gauge color.
|
||||
func (g *UtilModeGauge) SetEmptyColor(color tcell.Color) {
|
||||
g.emptyColor = color
|
||||
}
|
||||
|
67
gauge_um_test.go
Normal file
67
gauge_um_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/navidys/tvxwidgets"
|
||||
)
|
||||
|
||||
var _ = Describe("GaugeUm", Ordered, func() {
|
||||
var (
|
||||
app *tview.Application
|
||||
headerBox *tview.Box
|
||||
gaugeUm *tvxwidgets.UtilModeGauge
|
||||
screen tcell.SimulationScreen
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
app = tview.NewApplication()
|
||||
headerBox = tview.NewBox().SetBorder(true)
|
||||
gaugeUm = tvxwidgets.NewUtilModeGauge()
|
||||
screen = tcell.NewSimulationScreen("UTF-8")
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
appLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
appLayout.AddItem(headerBox, 1, 0, true)
|
||||
appLayout.AddItem(gaugeUm, 50, 0, true)
|
||||
err := app.SetScreen(screen).SetRoot(appLayout, true).Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
Describe("Focus", func() {
|
||||
It("checks primitivie focus", func() {
|
||||
app.SetFocus(headerBox)
|
||||
app.Draw()
|
||||
Expect(gaugeUm.HasFocus()).To(Equal(false))
|
||||
|
||||
app.SetFocus(gaugeUm)
|
||||
app.Draw()
|
||||
// gauge will not get focus
|
||||
Expect(gaugeUm.HasFocus()).To(Equal(false))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetRect", func() {
|
||||
It("primitivie size", func() {
|
||||
x, y, width, heigth := gaugeUm.GetRect()
|
||||
Expect(x).To(Equal(0))
|
||||
Expect(y).To(Equal(1))
|
||||
Expect(width).To(Equal(80))
|
||||
Expect(heigth).To(Equal(50))
|
||||
})
|
||||
})
|
||||
})
|
27
go.mod
27
go.mod
@ -1,18 +1,27 @@
|
||||
module github.com/navidys/tvxwidgets
|
||||
|
||||
go 1.17
|
||||
go 1.22.6
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1
|
||||
github.com/rivo/tview v0.0.0-20211202162923-2a6de950f73b
|
||||
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
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
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/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 // indirect
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
|
||||
golang.org/x/text v0.3.6 // 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
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
117
go.sum
117
go.sum
@ -1,22 +1,107 @@
|
||||
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.4.1-0.20210905002822-f057f0a857a1 h1:QqwPZCwh/k1uYqq6uXSb9TRDhTkfQbO80v8zhnIe5zM=
|
||||
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
|
||||
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/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/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.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/rivo/tview v0.0.0-20211202162923-2a6de950f73b h1:EMgbQ+bOHWkl0Ptano8M0yrzVZkxans+Vfv7ox/EtO8=
|
||||
github.com/rivo/tview v0.0.0-20211202162923-2a6de950f73b/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
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/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/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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/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/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/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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/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/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=
|
||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
594
plot.go
Normal file
594
plot.go
Normal file
@ -0,0 +1,594 @@
|
||||
package tvxwidgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
// Marker represents plot drawing marker (braille or dot).
|
||||
type Marker uint
|
||||
|
||||
const (
|
||||
// plot marker.
|
||||
PlotMarkerBraille Marker = iota
|
||||
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
|
||||
|
||||
const (
|
||||
PlotTypeLineChart PlotType = iota
|
||||
PlotTypeScatter
|
||||
)
|
||||
|
||||
const (
|
||||
plotHorizontalScale = 1
|
||||
plotXAxisLabelsHeight = 1
|
||||
plotXAxisLabelsGap = 2
|
||||
plotYAxisLabelsGap = 1
|
||||
|
||||
gapRune = " "
|
||||
)
|
||||
|
||||
type brailleCell struct {
|
||||
cRune rune
|
||||
color tcell.Color
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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,
|
||||
lineColors: []tcell.Color{
|
||||
tcell.ColorSteelBlue,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (plot *Plot) Draw(screen tcell.Screen) {
|
||||
plot.Box.DrawForSubclass(screen, plot)
|
||||
|
||||
switch plot.marker {
|
||||
case PlotMarkerDot:
|
||||
plot.drawDotMarkerToScreen(screen)
|
||||
case PlotMarkerBraille:
|
||||
plot.drawBrailleMarkerToScreen(screen)
|
||||
}
|
||||
|
||||
plot.drawAxesToScreen(screen)
|
||||
}
|
||||
|
||||
// SetRect sets rect for this primitive.
|
||||
func (plot *Plot) SetRect(x, y, width, height int) {
|
||||
plot.Box.SetRect(x, y, width, height)
|
||||
}
|
||||
|
||||
// SetLineColor sets chart line color.
|
||||
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
|
||||
}
|
||||
|
||||
// SetAxesLabelColor sets axes x and y label color.
|
||||
func (plot *Plot) SetAxesLabelColor(color tcell.Color) {
|
||||
plot.axesLabelColor = color
|
||||
}
|
||||
|
||||
// SetDrawAxes set true in order to draw axes to screen.
|
||||
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
|
||||
}
|
||||
|
||||
// SetPlotType sets plot type (linechart or scatter).
|
||||
func (plot *Plot) SetPlotType(ptype PlotType) {
|
||||
plot.ptype = ptype
|
||||
}
|
||||
|
||||
// SetData sets plot data.
|
||||
func (plot *Plot) SetData(data [][]float64) {
|
||||
plot.mu.Lock()
|
||||
defer plot.mu.Unlock()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// SetDotMarkerRune sets dot marker rune.
|
||||
func (plot *Plot) SetDotMarkerRune(r rune) {
|
||||
plot.dotMarkerRune = r
|
||||
}
|
||||
|
||||
// Figure out the text width necessary to display the largest data value.
|
||||
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) {
|
||||
x, y, width, height := plot.Box.GetInnerRect()
|
||||
plotYAxisLabelsWidth := plot.getYAxisLabelsWidth()
|
||||
|
||||
if plot.drawAxes {
|
||||
x = x + plotYAxisLabelsWidth + 1
|
||||
width = width - plotYAxisLabelsWidth - 1
|
||||
height = height - plotXAxisLabelsHeight - 1
|
||||
} else {
|
||||
x++
|
||||
width--
|
||||
}
|
||||
|
||||
return x, y, width, height
|
||||
}
|
||||
|
||||
func (plot *Plot) getData() [][]float64 {
|
||||
plot.mu.Lock()
|
||||
data := plot.data
|
||||
plot.mu.Unlock()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (plot *Plot) drawAxesToScreen(screen tcell.Screen) {
|
||||
if !plot.drawAxes {
|
||||
return
|
||||
}
|
||||
|
||||
x, y, width, height := plot.Box.GetInnerRect()
|
||||
plotYAxisLabelsWidth := plot.getYAxisLabelsWidth()
|
||||
|
||||
axesStyle := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(plot.axesColor)
|
||||
|
||||
// draw Y axis line
|
||||
drawLine(screen,
|
||||
x+plotYAxisLabelsWidth,
|
||||
y,
|
||||
height-plotXAxisLabelsHeight-1,
|
||||
verticalLine, axesStyle)
|
||||
|
||||
// draw X axis line
|
||||
drawLine(screen,
|
||||
x+plotYAxisLabelsWidth+1,
|
||||
y+height-plotXAxisLabelsHeight-1,
|
||||
width-plotYAxisLabelsWidth-1,
|
||||
horizontalLine, axesStyle)
|
||||
|
||||
tview.PrintJoinedSemigraphics(screen,
|
||||
x+plotYAxisLabelsWidth,
|
||||
y+height-plotXAxisLabelsHeight-1,
|
||||
tview.BoxDrawingsLightUpAndRight, axesStyle)
|
||||
|
||||
if plot.drawXAxisLabel {
|
||||
plot.drawXAxisLabelsToScreen(screen, plotYAxisLabelsWidth, x, y, width, height)
|
||||
}
|
||||
|
||||
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 := ""
|
||||
|
||||
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
|
||||
|
||||
tview.Print(screen,
|
||||
label,
|
||||
x,
|
||||
y+height-(i*(plotYAxisLabelsGap+1))-2, //nolint:mnd
|
||||
plotYAxisLabelsWidth,
|
||||
tview.AlignLeft, plot.axesLabelColor)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:cyclop,gocognit
|
||||
func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
|
||||
x, y, width, height := plot.GetPlotRect()
|
||||
chartData := plot.getData()
|
||||
verticalOffset := -plot.minVal
|
||||
|
||||
switch plot.ptype {
|
||||
case PlotTypeLineChart:
|
||||
for i, line := range chartData {
|
||||
style := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(plot.lineColors[i])
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case PlotTypeScatter:
|
||||
for i, line := range chartData {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) drawBrailleMarkerToScreen(screen tcell.Screen) {
|
||||
x, y, width, height := plot.GetPlotRect()
|
||||
|
||||
plot.calcBrailleLines()
|
||||
|
||||
// print to screen
|
||||
for point, cell := range plot.getBrailleCells() {
|
||||
style := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(cell.color)
|
||||
if point.X < x+width && point.Y < y+height {
|
||||
tview.PrintJoinedSemigraphics(screen, point.X, point.Y, cell.cRune, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
chartData := plot.getData()
|
||||
|
||||
for i, line := range chartData {
|
||||
if len(line) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
previousHeight := 0
|
||||
lastValWasOk := false
|
||||
|
||||
for j, val := range line {
|
||||
lheight, currentValIsOk := calcDataPointHeightIfInBounds(val, plot.maxVal, plot.minVal, height)
|
||||
|
||||
if !lastValWasOk && !currentValIsOk {
|
||||
// nothing valid to draw, skip to next data point
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
plot.brailleCellMap[point] = brailleCell{
|
||||
plot.brailleCellMap[point].cRune | brailleRune[p.Y%4][p.X%2],
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) setBrailleLine(p0, p1 image.Point, color tcell.Color) {
|
||||
for _, p := range plot.brailleLine(p0, p1) {
|
||||
plot.setBraillePoint(p, color)
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) getBrailleCells() map[image.Point]brailleCell {
|
||||
cellMap := make(map[image.Point]brailleCell)
|
||||
for point, cvCell := range plot.brailleCellMap {
|
||||
cellMap[point] = brailleCell{cvCell.cRune + brailleOffsetRune, cvCell.color}
|
||||
}
|
||||
|
||||
return cellMap
|
||||
}
|
||||
|
||||
func (plot *Plot) brailleLine(p0, p1 image.Point) []image.Point {
|
||||
points := []image.Point{}
|
||||
leftPoint, rightPoint := p0, p1
|
||||
|
||||
if leftPoint.X > rightPoint.X {
|
||||
leftPoint, rightPoint = rightPoint, leftPoint
|
||||
}
|
||||
|
||||
xDistance := absInt(leftPoint.X - rightPoint.X)
|
||||
yDistance := absInt(leftPoint.Y - rightPoint.Y)
|
||||
slope := float64(yDistance) / float64(xDistance)
|
||||
slopeSign := 1
|
||||
|
||||
if rightPoint.Y < leftPoint.Y {
|
||||
slopeSign = -1
|
||||
}
|
||||
|
||||
targetYCoordinate := float64(leftPoint.Y)
|
||||
currentYCoordinate := leftPoint.Y
|
||||
|
||||
for i := leftPoint.X; i < rightPoint.X; i++ {
|
||||
points = append(points, image.Pt(i, currentYCoordinate))
|
||||
targetYCoordinate += (slope * float64(slopeSign))
|
||||
|
||||
for currentYCoordinate != int(targetYCoordinate) {
|
||||
points = append(points, image.Pt(i, currentYCoordinate))
|
||||
|
||||
currentYCoordinate += slopeSign
|
||||
}
|
||||
}
|
||||
|
||||
return points
|
||||
}
|
66
plot_test.go
Normal file
66
plot_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/navidys/tvxwidgets"
|
||||
)
|
||||
|
||||
var _ = Describe("Plot", Ordered, func() {
|
||||
var (
|
||||
app *tview.Application
|
||||
headerBox *tview.Box
|
||||
plot *tvxwidgets.Plot
|
||||
screen tcell.SimulationScreen
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
app = tview.NewApplication()
|
||||
headerBox = tview.NewBox().SetBorder(true)
|
||||
plot = tvxwidgets.NewPlot()
|
||||
screen = tcell.NewSimulationScreen("UTF-8")
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
appLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
appLayout.AddItem(headerBox, 1, 0, true)
|
||||
appLayout.AddItem(plot, 50, 0, true)
|
||||
err := app.SetScreen(screen).SetRoot(appLayout, true).Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
Describe("Focus", func() {
|
||||
It("checks primitivie focus", func() {
|
||||
app.SetFocus(headerBox)
|
||||
app.Draw()
|
||||
Expect(plot.HasFocus()).To(Equal(false))
|
||||
|
||||
app.SetFocus(plot)
|
||||
app.Draw()
|
||||
Expect(plot.HasFocus()).To(Equal(true))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetRect", func() {
|
||||
It("primitivie size", func() {
|
||||
x, y, width, heigth := plot.GetRect()
|
||||
Expect(x).To(Equal(0))
|
||||
Expect(y).To(Equal(1))
|
||||
Expect(width).To(Equal(80))
|
||||
Expect(heigth).To(Equal(50))
|
||||
})
|
||||
})
|
||||
})
|
118
sparkline.go
Normal file
118
sparkline.go
Normal file
@ -0,0 +1,118 @@
|
||||
package tvxwidgets
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
// Spartline represents a sparkline widgets.
|
||||
type Sparkline struct {
|
||||
*tview.Box
|
||||
|
||||
data []float64
|
||||
dataTitle string
|
||||
dataTitlecolor tcell.Color
|
||||
lineColor tcell.Color
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewSparkline returns a new sparkline widget.
|
||||
func NewSparkline() *Sparkline {
|
||||
return &Sparkline{
|
||||
Box: tview.NewBox(),
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (sl *Sparkline) Draw(screen tcell.Screen) {
|
||||
sl.Box.DrawForSubclass(screen, sl)
|
||||
|
||||
x, y, width, height := sl.Box.GetInnerRect()
|
||||
barHeight := height
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
tview.PrintJoinedSemigraphics(screen, i+x, y-1+height-j, sparkChar, style)
|
||||
}
|
||||
|
||||
if dHeight == 0 {
|
||||
sparkChar = barsRune[1]
|
||||
tview.PrintJoinedSemigraphics(screen, i+x, y-1+height, sparkChar, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetRect sets rect for this primitive.
|
||||
func (sl *Sparkline) SetRect(x, y, width, height int) {
|
||||
sl.Box.SetRect(x, y, width, height)
|
||||
}
|
||||
|
||||
// GetRect return primitive current rect.
|
||||
func (sl *Sparkline) GetRect() (int, int, int, int) {
|
||||
return sl.Box.GetRect()
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (sl *Sparkline) HasFocus() bool {
|
||||
return sl.Box.HasFocus()
|
||||
}
|
||||
|
||||
// SetData sets sparkline data.
|
||||
func (sl *Sparkline) SetData(data []float64) {
|
||||
sl.mu.Lock()
|
||||
defer sl.mu.Unlock()
|
||||
|
||||
sl.data = data
|
||||
}
|
||||
|
||||
// SetDataTitle sets sparkline data title.
|
||||
func (sl *Sparkline) SetDataTitle(title string) {
|
||||
sl.mu.Lock()
|
||||
defer sl.mu.Unlock()
|
||||
|
||||
sl.dataTitle = title
|
||||
}
|
||||
|
||||
// SetDataTitleColor sets sparkline data title color.
|
||||
func (sl *Sparkline) SetDataTitleColor(color tcell.Color) {
|
||||
sl.mu.Lock()
|
||||
defer sl.mu.Unlock()
|
||||
|
||||
sl.dataTitlecolor = color
|
||||
}
|
||||
|
||||
// SetLineColor sets sparkline line color.
|
||||
func (sl *Sparkline) SetLineColor(color tcell.Color) {
|
||||
sl.mu.Lock()
|
||||
defer sl.mu.Unlock()
|
||||
|
||||
sl.lineColor = color
|
||||
}
|
92
sparkline_test.go
Normal file
92
sparkline_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/navidys/tvxwidgets"
|
||||
)
|
||||
|
||||
var _ = Describe("Sparkline", Ordered, func() {
|
||||
var (
|
||||
app *tview.Application
|
||||
headerBox *tview.Box
|
||||
sparkline *tvxwidgets.Sparkline
|
||||
screen tcell.SimulationScreen
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
app = tview.NewApplication()
|
||||
headerBox = tview.NewBox().SetBorder(true)
|
||||
sparkline = tvxwidgets.NewSparkline()
|
||||
screen = tcell.NewSimulationScreen("UTF-8")
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
appLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
appLayout.AddItem(headerBox, 1, 0, true)
|
||||
appLayout.AddItem(sparkline, 50, 0, true)
|
||||
err := app.SetScreen(screen).SetRoot(appLayout, true).Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
Describe("Focus", func() {
|
||||
It("checks primitivie focus", func() {
|
||||
app.SetFocus(headerBox)
|
||||
app.Draw()
|
||||
Expect(sparkline.HasFocus()).To(Equal(false))
|
||||
|
||||
app.SetFocus(sparkline)
|
||||
app.Draw()
|
||||
Expect(sparkline.HasFocus()).To(Equal(true))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetRect", func() {
|
||||
It("primitivie size", func() {
|
||||
x, y, width, heigth := sparkline.GetRect()
|
||||
Expect(x).To(Equal(0))
|
||||
Expect(y).To(Equal(1))
|
||||
Expect(width).To(Equal(80))
|
||||
Expect(heigth).To(Equal(50))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DataTitle and Color", func() {
|
||||
It("checks data title text and color", func() {
|
||||
tests := []struct {
|
||||
title string
|
||||
color tcell.Color
|
||||
}{
|
||||
{title: "test01", color: tcell.ColorDarkOrange},
|
||||
{title: "abc123", color: tcell.ColorBlue},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
sparkline.SetDataTitle(test.title)
|
||||
sparkline.SetDataTitleColor(test.color)
|
||||
app.Draw()
|
||||
|
||||
for x := 0; x < len(test.title); x++ {
|
||||
prune, _, style, _ := screen.GetContent(x, 1)
|
||||
fg, _, _ := style.Decompose()
|
||||
|
||||
Expect(fg).To(Equal(test.color))
|
||||
Expect(string(prune)).To(Equal(string(test.title[x])))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
104
spinner_test.go
Normal file
104
spinner_test.go
Normal file
@ -0,0 +1,104 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/navidys/tvxwidgets"
|
||||
)
|
||||
|
||||
var _ = Describe("Spinner", Ordered, func() {
|
||||
var (
|
||||
app *tview.Application
|
||||
headerBox *tview.Box
|
||||
spinner *tvxwidgets.Spinner
|
||||
screen tcell.SimulationScreen
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
app = tview.NewApplication()
|
||||
headerBox = tview.NewBox().SetBorder(true)
|
||||
spinner = tvxwidgets.NewSpinner()
|
||||
screen = tcell.NewSimulationScreen("UTF-8")
|
||||
|
||||
if err := screen.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
appLayout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
appLayout.AddItem(headerBox, 1, 0, true)
|
||||
appLayout.AddItem(spinner, 50, 0, true)
|
||||
err := app.SetScreen(screen).SetRoot(appLayout, true).Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
Describe("Focus", func() {
|
||||
It("checks primitivie focus", func() {
|
||||
app.SetFocus(headerBox)
|
||||
app.Draw()
|
||||
Expect(spinner.HasFocus()).To(Equal(false))
|
||||
|
||||
app.SetFocus(spinner)
|
||||
app.Draw()
|
||||
Expect(spinner.HasFocus()).To(Equal(true))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetRect", func() {
|
||||
It("primitivie size", func() {
|
||||
x, y, width, heigth := spinner.GetRect()
|
||||
Expect(x).To(Equal(0))
|
||||
Expect(y).To(Equal(1))
|
||||
Expect(width).To(Equal(80))
|
||||
Expect(heigth).To(Equal(50))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Style", func() {
|
||||
It("checks style", func() {
|
||||
spinner.SetStyle(tvxwidgets.SpinnerGrowHorizontal)
|
||||
spinner.Reset()
|
||||
app.Draw()
|
||||
|
||||
prune, _, _, _ := screen.GetContent(0, 1)
|
||||
Expect(prune).To(Equal('▉'))
|
||||
|
||||
spinner.Pulse()
|
||||
app.Draw()
|
||||
prune, _, _, _ = screen.GetContent(0, 1)
|
||||
Expect(prune).To(Equal('▊'))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("CustomStyle", func() {
|
||||
It("checks custom style", func() {
|
||||
customStyle := []rune{'\u2705', '\u274C'}
|
||||
spinner.SetCustomStyle(customStyle)
|
||||
spinner.Reset()
|
||||
|
||||
app.Draw()
|
||||
prune, _, _, _ := screen.GetContent(0, 1)
|
||||
Expect(prune).To(Equal(customStyle[0]))
|
||||
|
||||
spinner.Pulse()
|
||||
app.Draw()
|
||||
prune, _, _, _ = screen.GetContent(0, 1)
|
||||
Expect(prune).To(Equal(customStyle[1]))
|
||||
|
||||
spinner.Pulse()
|
||||
app.Draw()
|
||||
prune, _, _, _ = screen.GetContent(0, 1)
|
||||
Expect(prune).To(Equal(customStyle[0]))
|
||||
})
|
||||
})
|
||||
})
|
13
tvxwidgets_suite_test.go
Normal file
13
tvxwidgets_suite_test.go
Normal file
@ -0,0 +1,13 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestTvxwidgets(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Tvxwidgets Suite")
|
||||
}
|
131
utils.go
131
utils.go
@ -1,9 +1,18 @@
|
||||
package tvxwidgets
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type drawLineMode int
|
||||
|
||||
const (
|
||||
horizontalLine drawLineMode = iota
|
||||
verticalLine
|
||||
)
|
||||
|
||||
const (
|
||||
@ -22,7 +31,21 @@ const (
|
||||
// dialog padding.
|
||||
dialogPadding = 2
|
||||
// empty space parts.
|
||||
emptySpaceParts = 2
|
||||
emptySpaceParts = 2
|
||||
brailleOffsetRune = '\u2800'
|
||||
dotRune = '\u25CF'
|
||||
fullBlockRune = '\u2588'
|
||||
)
|
||||
|
||||
var (
|
||||
brailleRune = [4][2]rune{ //nolint:gochecknoglobals
|
||||
{'\u0001', '\u0008'},
|
||||
{'\u0002', '\u0010'},
|
||||
{'\u0004', '\u0020'},
|
||||
{'\u0040', '\u0080'},
|
||||
}
|
||||
|
||||
barsRune = [...]rune{' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'} //nolint:gochecknoglobals
|
||||
)
|
||||
|
||||
// getColorName returns convert tcell color to its name.
|
||||
@ -47,3 +70,109 @@ func getMessageWidth(message string) int {
|
||||
|
||||
return messageWidth
|
||||
}
|
||||
|
||||
// returns max values in 2D float64 slices.
|
||||
func getMaxFloat64From2dSlice(slices [][]float64) float64 {
|
||||
if len(slices) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var (
|
||||
maxValue float64
|
||||
maxIsInit bool
|
||||
)
|
||||
|
||||
for _, slice := range slices {
|
||||
for _, val := range slice {
|
||||
if math.IsNaN(val) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !maxIsInit {
|
||||
maxIsInit = true
|
||||
maxValue = val
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if val > maxValue {
|
||||
maxValue = 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
|
||||
}
|
||||
|
||||
// returns max values in float64 slices.
|
||||
func getMaxFloat64FromSlice(slice []float64) float64 {
|
||||
if len(slice) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
maxValue := -1.0
|
||||
|
||||
for i := range slice {
|
||||
if math.IsNaN(slice[i]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if slice[i] > maxValue {
|
||||
maxValue = slice[i]
|
||||
}
|
||||
}
|
||||
|
||||
return maxValue
|
||||
}
|
||||
|
||||
func absInt(x int) int {
|
||||
if x >= 0 {
|
||||
return x
|
||||
}
|
||||
|
||||
return -x
|
||||
}
|
||||
|
||||
func drawLine(screen tcell.Screen, startX int, startY int, length int, mode drawLineMode, style tcell.Style) {
|
||||
if mode == horizontalLine {
|
||||
for i := range length {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+i, startY, tview.BoxDrawingsLightTripleDashHorizontal, style)
|
||||
}
|
||||
} else if mode == verticalLine {
|
||||
for i := range length {
|
||||
tview.PrintJoinedSemigraphics(screen, startX, startY+i, tview.BoxDrawingsLightTripleDashVertical, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
131
utils_test.go
Normal file
131
utils_test.go
Normal file
@ -0,0 +1,131 @@
|
||||
package tvxwidgets_test
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/navidys/tvxwidgets"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
var _ = Describe("Utils", func() {
|
||||
Describe("getColorName", func() {
|
||||
It("returns color name", func() {
|
||||
tests := []struct {
|
||||
color tcell.Color
|
||||
colorName string
|
||||
}{
|
||||
{color: tcell.ColorWhite, colorName: "white"},
|
||||
{color: tcell.ColorBlack, colorName: "black"},
|
||||
{color: tcell.NewRGBColor(0, 1, 2), colorName: ""},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
Expect(tvxwidgets.GetColorName(test.color)).To(Equal(test.colorName))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getMessageWidth", func() {
|
||||
It("returns width size for dialogs based on messages", func() {
|
||||
tests := []struct {
|
||||
msg string
|
||||
width int
|
||||
}{
|
||||
{msg: "test", width: 4},
|
||||
{msg: "test01\ntest001", width: 7},
|
||||
{msg: "", width: 0},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
Expect(tvxwidgets.GetMessageWidth(test.msg)).To(Equal(test.width))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getMaxFloat64From2dSlice", func() {
|
||||
It("returns max values in 2D float64 slices.", func() {
|
||||
tests := []struct {
|
||||
have [][]float64
|
||||
wants float64
|
||||
}{
|
||||
{have: [][]float64{}, wants: 0},
|
||||
{have: [][]float64{
|
||||
{5, -1, 0, -10, 12},
|
||||
{15, -11, 0, -110, 22},
|
||||
}, wants: 22},
|
||||
{have: [][]float64{
|
||||
{-5, -1, -2, -10, -12},
|
||||
{-15, -11, -1, -110, -22},
|
||||
}, wants: -1},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
Expect(tvxwidgets.GetMaxFloat64From2dSlice(test.have)).To(Equal(test.wants))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getMaxFloat64FromSlice", func() {
|
||||
It("returns max values in float64 slices", func() {
|
||||
tests := []struct {
|
||||
have []float64
|
||||
wants float64
|
||||
}{
|
||||
{have: []float64{}, wants: 0},
|
||||
{have: []float64{5, -1, 0, -10, 12}, wants: 12},
|
||||
{have: []float64{-10, -20, -9, -1}, wants: -1},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
Expect(tvxwidgets.GetMaxFloat64FromSlice(test.have)).To(Equal(test.wants))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Describe("absInt", func() {
|
||||
It("return absint", func() {
|
||||
tests := []struct {
|
||||
have int
|
||||
wants int
|
||||
}{
|
||||
{have: 2, wants: 2},
|
||||
{have: -2, wants: 2},
|
||||
{have: 0, wants: 0},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
Expect(tvxwidgets.AbsInt(test.have)).To(Equal(test.wants))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Describe("drawLine", func() {
|
||||
It("draws horizontal or vertival line on screen", func() {
|
||||
screen := tcell.NewSimulationScreen("UTF-8")
|
||||
screenWidth := 70
|
||||
screenHeight := 30
|
||||
lineStartX := 0
|
||||
lineStartY := 0
|
||||
lineLenght := 20
|
||||
screen.SetSize(screenWidth, screenHeight)
|
||||
screen.Init()
|
||||
screen.Clear()
|
||||
|
||||
// draw and test horizental line
|
||||
tvxwidgets.DrawLine(screen, lineStartX, lineStartY, lineLenght, 0, tcell.StyleDefault)
|
||||
screen.Show()
|
||||
|
||||
cellRune, _, _, _ := screen.GetContent(lineStartX, lineStartY)
|
||||
Expect(cellRune).To(Equal(tview.BoxDrawingsLightTripleDashHorizontal))
|
||||
|
||||
// draw and test vertical line
|
||||
screen.Clear()
|
||||
tvxwidgets.DrawLine(screen, lineStartX, lineStartY, lineLenght, 1, tcell.StyleDefault)
|
||||
screen.Show()
|
||||
|
||||
cellRune, _, _, _ = screen.GetContent(lineStartX, lineStartY)
|
||||
Expect(cellRune).To(Equal(tview.BoxDrawingsLightTripleDashVertical))
|
||||
})
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user