summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean E. Russell <seanerussell@gmail.com>2019-01-01 11:04:31 -0600
committerSean E. Russell <seanerussell@gmail.com>2019-01-01 11:04:31 -0600
commit2a6a479d119f41c0ac1a4304755127da3542392e (patch)
treecd6e595614dee70db30c89e0af3cbea692eee27e
parent5ee4b4a978f3b27513b680fe3297cb7bea7bcb1e (diff)
parent2934d6fd484b3c58b547a44296f5bf860bc1ded9 (diff)
Merge https://github.com/cjbassi/gotop
Disable all battery code if battery not selected in options. Refactors widgetCount to localize use.
-rw-r--r--README.md10
-rw-r--r--go.mod11
-rw-r--r--go.sum33
-rw-r--r--main.go180
-rw-r--r--src/termui/linegraph.go131
-rw-r--r--src/termui/sparkline.go99
-rw-r--r--src/termui/table.go206
-rw-r--r--src/widgets/battery.go4
-rw-r--r--src/widgets/cpu.go4
-rw-r--r--src/widgets/disk.go8
-rw-r--r--src/widgets/help.go33
-rw-r--r--src/widgets/mem.go4
-rw-r--r--src/widgets/net.go4
-rw-r--r--src/widgets/proc.go11
-rw-r--r--src/widgets/statusbar.go50
-rw-r--r--src/widgets/temp.go37
16 files changed, 678 insertions, 147 deletions
diff --git a/README.md b/README.md
index c1393de..959b96a 100644
--- a/README.md
+++ b/README.md
@@ -115,11 +115,11 @@ This will place the built packages into the `dist` folder.
## Built With
-- [cjbassi/termui](https://github.com/cjbassi/termui)
- - [drawille-go](https://github.com/exrook/drawille-go)
- - [termbox](https://github.com/nsf/termbox-go)
-- [gopsutil](https://github.com/shirou/gopsutil)
-- [goreleaser](https://github.com/goreleaser/goreleaser)
+- [gizak/termui](https://github.com/gizak/termui)
+ - [nsf/termbox](https://github.com/nsf/termbox-go)
+- [exrook/drawille-go](https://github.com/exrook/drawille-go)
+- [shirou/gopsutil](https://github.com/shirou/gopsutil)
+- [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser)
- [battery](https://github.com/distatus/battery)
## Stargazers over time
diff --git a/go.mod b/go.mod
index e0dace1..4366d90 100644
--- a/go.mod
+++ b/go.mod
@@ -1,22 +1,17 @@
module github.com/cjbassi/gotop
require (
- github.com/0xAX/notificator v0.0.0-20181105090803-d81462e38c21 // indirect
github.com/ProtonMail/go-appdir v0.0.0-20180220133335-7c788d1b45c6
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
- github.com/cjbassi/drawille-go v0.0.0-20180329221028-ad535d0f92cd // indirect
- github.com/cjbassi/termui v0.0.0-20181208033036-e8dd23f6146c
+ github.com/cjbassi/drawille-go v0.0.0-20180329221028-ad535d0f92cd
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distatus/battery v0.0.0-20170521010419-916919eb43bd
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815
+ github.com/gizak/termui v0.0.0-20190101100649-ffdef4cb72e9
github.com/go-ole/go-ole v1.2.1 // indirect
- github.com/mattn/go-runewidth v0.0.2 // indirect
- github.com/nexulh/batmond v0.0.0-20180213215845-395ac7fdba8c // indirect
- github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 // indirect
- github.com/nsf/termbox-go v0.0.0-20180407224525-3e24a7b6661e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shirou/gopsutil v2.18.11+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/stretchr/testify v1.2.2 // indirect
- golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 // indirect
+ golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a // indirect
)
diff --git a/go.sum b/go.sum
index f1007cc..7043b02 100644
--- a/go.sum
+++ b/go.sum
@@ -1,48 +1,35 @@
-github.com/0xAX/notificator v0.0.0-20181105090803-d81462e38c21 h1:moSC7ACaTejHmVRRwfDTMgByRSwjg2vZooncdWLj7o8=
github.com/0xAX/notificator v0.0.0-20181105090803-d81462e38c21/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
github.com/ProtonMail/go-appdir v0.0.0-20180220133335-7c788d1b45c6 h1:de/SvQsi6Oou9TJYp6Kp17S+JjXGn1w8XVYWFlE0z/U=
github.com/ProtonMail/go-appdir v0.0.0-20180220133335-7c788d1b45c6/go.mod h1:3d8Y9F5mbEUjrYbcJ3rcDxcWbqbttF+011nVZmdRdzc=
-github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/cjbassi/drawille-go v0.0.0-20180329221028-ad535d0f92cd h1:nSJpATLVvFa19BEHX4ys+VGNWfI4FUGMweEI6QXs8wg=
github.com/cjbassi/drawille-go v0.0.0-20180329221028-ad535d0f92cd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4=
github.com/cjbassi/termui v0.0.0-20181208033036-e8dd23f6146c h1:vcaCtK8ObawtpQRW8GdbKZ+eJGEUn41xJ8Snagd/c6I=
github.com/cjbassi/termui v0.0.0-20181208033036-e8dd23f6146c/go.mod h1:rqXckrwz+i0fH/zNwU6AdBNULHwmZsgehnSlhKP5i2Q=
-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/distatus/battery v0.0.0-20170521010419-916919eb43bd h1:rxw7AZpSwNgPPFdxDNSwQ+TfvOjmSGDK6tlpY7vw9bw=
github.com/distatus/battery v0.0.0-20170521010419-916919eb43bd/go.mod h1:gGO7GxHTi1zlRT+cAj8uGG0/8HFiqAeH0TJvoipnuPs=
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815 h1:HMAfwOa33y82IaQEKQDfUCiwNlxtM1iw7HLM9ru0RNc=
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:l7JNRynTRuqe45tpIyItHNqZWTxywYjp87MWTOnU5cg=
-github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
+github.com/gizak/termui v0.0.0-20190101100649-ffdef4cb72e9 h1:h5eo3CW6c9WMG+rN8cVxf25v8bSWFjuaDJdvtEWc61E=
+github.com/gizak/termui v0.0.0-20190101100649-ffdef4cb72e9/go.mod h1:S3xz8JHXNDPSNFsvXCdG7bHlEGrwvUG3a0joR/xYZ5M=
+github.com/gizak/termui v2.3.0+incompatible h1:S8wJoNumYfc/rR5UezUM4HsPEo3RJh0LKdiuDWQpjqw=
+github.com/gizak/termui v2.3.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
-github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc=
+github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/nexulh/batmond v0.0.0-20180213215845-395ac7fdba8c h1:Hiq+wfawWPyyX2AVpIhEiVsoKVQdc8zQwAwiKiVZbTI=
-github.com/nexulh/batmond v0.0.0-20180213215845-395ac7fdba8c/go.mod h1:Ho2yA2Aa5zD+GEgmhFdLLpHVFoGJXF/Et15iRydTFtY=
-github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 h1:+2OJrU8cmOstEoh0uQvYemRGVH1O6xtO2oANUWHFnP0=
-github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443/go.mod h1:JbxfV1Iifij2yhRjXai0oFrbpxszXHRx1E5RuM26o4Y=
-github.com/nsf/termbox-go v0.0.0-20180407224525-3e24a7b6661e h1:w2JDz0jtOlFFdvtUXISyYPFwmbZnwKL1mRDT0tKDvuk=
-github.com/nsf/termbox-go v0.0.0-20180407224525-3e24a7b6661e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
+github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb h1:YahEjAGkJtCrkqgVHhX6n8ZX+CZ3hDRL9fjLYugLfSs=
+github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/serussell/battery v0.0.0-20170521010419-b399f895029c/go.mod h1:0eSBUZHzM+0VCcUzFSkpxGxOg+jkMTT2cr4U/zupwsk=
github.com/shirou/gopsutil v2.18.11+incompatible h1:PMFTKnFTr/YTRW5rbLK4vWALV3a+IGXse5nvhSjztmg=
github.com/shirou/gopsutil v2.18.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
-github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
-github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a h1:DwI0ihryIiWlRUKL/ii7Snvn4LiL9TvMoVZq3qMbffg=
golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 h1:IcgEB62HYgAhX0Nd/QrVgZlxlcyxbGQHElLUhW2X4Fo=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
-howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
diff --git a/main.go b/main.go
index fc6df73..869cedc 100644
--- a/main.go
+++ b/main.go
@@ -18,8 +18,8 @@ import (
"github.com/cjbassi/gotop/colorschemes"
"github.com/cjbassi/gotop/src/logging"
w "github.com/cjbassi/gotop/src/widgets"
- ui "github.com/cjbassi/termui"
docopt "github.com/docopt/docopt.go"
+ ui "github.com/gizak/termui"
)
var version = "1.7.1"
@@ -34,11 +34,13 @@ var (
averageLoad = false
battery = false
percpuLoad = false
- widgetCount = 7
fahrenheit = false
configDir = appdir.New("gotop").UserConfig()
logPath = filepath.Join(configDir, "errors.log")
stderrLogger = log.New(os.Stderr, "", 0)
+ statusbar = false
+ termWidth int
+ termHeight int
cpu *w.CPU
batt *w.Batt
@@ -48,6 +50,7 @@ var (
disk *w.Disk
temp *w.Temp
help *w.HelpMenu
+ grid *ui.Grid
)
func cliArguments() error {
@@ -63,7 +66,8 @@ Options:
-p, --percpu Show each CPU in the CPU widget.
-a, --averagecpu Show average CPU in the CPU widget.
-f, --fahrenheit Show temperatures in fahrenheit.
- -b, --battery Show battery charge over time (minimal overrides & sets false)
+ -t, --battery Show battery charge over time ('minimal' disables; widget updates slowly)
+ -b, --bar Show a statusbar with the time.
Colorschemes:
default
@@ -87,9 +91,8 @@ Colorschemes:
battery, _ = args["--battery"].(bool)
minimal, _ = args["--minimal"].(bool)
- if minimal {
- widgetCount = 3
- }
+
+ statusbar, _ = args["--bar"].(bool)
rateStr, _ := args["--rate"].(string)
rate, err := strconv.ParseFloat(rateStr, 64)
@@ -141,46 +144,68 @@ func getCustomColorscheme(name string) (colorschemes.Colorscheme, error) {
}
func setupGrid() {
- ui.Body.Cols = 12
- ui.Body.Rows = 12
+ grid = ui.NewGrid()
+ grid.SetRect(0, 0, termWidth, termHeight)
+ var barRow interface{}
if minimal {
- ui.Body.Set(0, 0, 12, 6, cpu)
- ui.Body.Set(0, 6, 6, 12, mem)
- ui.Body.Set(6, 6, 12, 12, proc)
+ rowHeight := 1.0 / 2
+ if statusbar {
+ rowHeight = 50.0 / 101
+ barRow = ui.NewRow(1.0/101, w.NewStatusBar())
+ }
+ grid.Set(
+ ui.NewRow(rowHeight, cpu),
+ ui.NewRow(rowHeight,
+ ui.NewCol(1.0/2, mem),
+ ui.NewCol(1.0/2, proc),
+ ),
+ barRow,
+ )
} else {
+ rowHeight := 1.0 / 3
+ if statusbar {
+ rowHeight = 50.0 / 151
+ barRow = ui.NewRow(1.0/151, w.NewStatusBar())
+ }
+ var cpuRow ui.GridItem
if battery {
- ui.Body.Set(0, 0, 8, 4, cpu)
- ui.Body.Set(8, 0, 12, 4, batt)
+ cpuRow = ui.NewRow(rowHeight,
+ ui.NewCol(2.0/3, cpu),
+ ui.NewCol(1.0/3, batt),
+ )
} else {
- ui.Body.Set(0, 0, 12, 4, cpu)
+ cpuRow = ui.NewRow(rowHeight, cpu)
}
-
- ui.Body.Set(0, 4, 4, 6, disk)
- ui.Body.Set(0, 6, 4, 8, temp)
- ui.Body.Set(4, 4, 12, 8, mem)
-
- ui.Body.Set(0, 8, 6, 12, net)
- ui.Body.Set(6, 8, 12, 12, proc)
+ grid.Set(
+ cpuRow,
+ ui.NewRow(rowHeight,
+ ui.NewCol(1.0/3,
+ ui.NewRow(1.0/2, disk),
+ ui.NewRow(1.0/2, temp),
+ ),
+ ui.NewCol(2.0/3, mem),
+ ),
+ ui.NewRow(rowHeight,
+ ui.NewCol(1.0/2, net),
+ ui.NewCol(1.0/2, proc),
+ ),
+ barRow,
+ )
}
}
func termuiColors() {
- ui.Theme.Fg = ui.Color(colorscheme.Fg)
- ui.Theme.Bg = ui.Color(colorscheme.Bg)
- ui.Theme.LabelFg = ui.Color(colorscheme.BorderLabel)
- ui.Theme.LabelBg = ui.Color(colorscheme.Bg)
- ui.Theme.BorderFg = ui.Color(colorscheme.BorderLine)
- ui.Theme.BorderBg = ui.Color(colorscheme.Bg)
-
- ui.Theme.TableCursor = ui.Color(colorscheme.ProcCursor)
- ui.Theme.Sparkline = ui.Color(colorscheme.Sparkline)
- ui.Theme.GaugeColor = ui.Color(colorscheme.DiskBar)
+ ui.Theme.Default = ui.AttrPair{ui.Attribute(colorscheme.Fg), ui.Attribute(colorscheme.Bg)}
+ ui.Theme.Block.Title = ui.AttrPair{ui.Attribute(colorscheme.BorderLabel), ui.Attribute(colorscheme.Bg)}
+ ui.Theme.Block.Border = ui.AttrPair{ui.Attribute(colorscheme.BorderLine), ui.Attribute(colorscheme.Bg)}
}
func widgetColors() {
- mem.LineColor["Main"] = ui.Color(colorscheme.MainMem)
- mem.LineColor["Swap"] = ui.Color(colorscheme.SwapMem)
+ mem.LineColor["Main"] = ui.Attribute(colorscheme.MainMem)
+ mem.LineColor["Swap"] = ui.Attribute(colorscheme.SwapMem)
+
+ proc.CursorColor = ui.Attribute(colorscheme.ProcCursor)
var keys []string
for key := range cpu.Data {
@@ -194,60 +219,76 @@ func widgetColors() {
i = 0
}
c := colorscheme.CPULines[i]
- cpu.LineColor[v] = ui.Color(c)
+ cpu.LineColor[v] = ui.Attribute(c)
i++
}
if !minimal {
- temp.TempLow = ui.Color(colorscheme.TempLow)
- temp.TempHigh = ui.Color(colorscheme.TempHigh)
- var battKeys []string
- for key := range batt.Data {
- battKeys = append(battKeys, key)
- }
- sort.Strings(battKeys)
- bi := 0
- for _, v := range battKeys {
- if bi >= len(colorscheme.BattLines) {
- // assuming colorscheme for CPU lines is not empty
- bi = 0
+ if battery {
+ var battKeys []string
+ for key := range batt.Data {
+ battKeys = append(battKeys, key)
+ }
+ sort.Strings(battKeys)
+ i = 0 // Re-using variable from CPU
+ for _, v := range battKeys {
+ if i >= len(colorscheme.BattLines) {
+ // assuming colorscheme for battery lines is not empty
+ i = 0
+ }
+ c := colorscheme.BattLines[i]
+ batt.LineColor[v] = ui.Attribute(c)
+ i++
}
- c := colorscheme.BattLines[bi]
- batt.LineColor[v] = ui.Color(c)
- bi++
}
+
+ temp.TempLow = ui.Attribute(colorscheme.TempLow)
+ temp.TempHigh = ui.Attribute(colorscheme.TempHigh)
+
+ net.Lines[0].LineColor = ui.Attribute(colorscheme.Sparkline)
+ net.Lines[0].TitleColor = ui.Attribute(colorscheme.BorderLabel)
+ net.Lines[1].LineColor = ui.Attribute(colorscheme.Sparkline)
+ net.Lines[1].TitleColor = ui.Attribute(colorscheme.BorderLabel)
}
}
func initWidgets() {
var wg sync.WaitGroup
- wg.Add(widgetCount)
+ wg.Add(1)
go func() {
cpu = w.NewCPU(interval, zoom, averageLoad, percpuLoad)
wg.Done()
}()
+ wg.Add(1)
go func() {
mem = w.NewMem(interval, zoom)
wg.Done()
}()
+ wg.Add(1)
go func() {
proc = w.NewProc()
wg.Done()
}()
if !minimal {
- go func() {
- batt = w.NewBatt(time.Minute, zoom)
- wg.Done()
- }()
+ if battery {
+ wg.Add(1)
+ go func() {
+ batt = w.NewBatt(time.Minute, zoom)
+ wg.Done()
+ }()
+ }
+ wg.Add(1)
go func() {
net = w.NewNet()
wg.Done()
}()
+ wg.Add(1)
go func() {
disk = w.NewDisk()
wg.Done()
}()
+ wg.Add(1)
go func() {
temp = w.NewTemp(fahrenheit)
wg.Done()
@@ -274,7 +315,7 @@ func eventLoop() {
return
case <-drawTicker:
if !helpVisible {
- ui.Render(ui.Body)
+ ui.Render(grid)
}
case e := <-uiEvents:
switch e.ID {
@@ -286,7 +327,7 @@ func eventLoop() {
ui.Clear()
ui.Render(help)
} else {
- ui.Render(ui.Body)
+ ui.Render(grid)
}
case "h":
if !helpVisible {
@@ -307,27 +348,27 @@ func eventLoop() {
case "<Escape>":
if helpVisible {
helpVisible = false
- ui.Render(ui.Body)
+ ui.Render(grid)
}
case "<Resize>":
payload := e.Payload.(ui.Resize)
- ui.Body.Width, ui.Body.Height = payload.Width, payload.Height
- ui.Body.Resize()
+ grid.SetRect(0, 0, payload.Width, payload.Height)
+ help.Resize(payload.Width, payload.Height)
ui.Clear()
if helpVisible {
ui.Render(help)
} else {
- ui.Render(ui.Body)
+ ui.Render(grid)
}
case "<MouseLeft>":
payload := e.Payload.(ui.Mouse)
proc.Click(payload.X, payload.Y)
ui.Render(proc)
- case "<MouseWheelUp>", "<Up>", "k":
+ case "k", "<Up>", "<MouseWheelUp>":
proc.Up()
ui.Render(proc)
- case "<MouseWheelDown>", "<Down>", "j":
+ case "j", "<Down>", "<MouseWheelDown>":
proc.Down()
ui.Render(proc)
case "g", "<Home>":
@@ -401,11 +442,6 @@ func main() {
stderrLogger.Fatalf("failed to parse cli args: %v", err)
}
- termuiColors() // need to do this before initializing widgets so that they can inherit the colors
- initWidgets()
- widgetColors()
- help = w.NewHelpMenu()
-
if err := ui.Init(); err != nil {
stderrLogger.Fatalf("failed to initialize termui: %v", err)
}
@@ -413,8 +449,16 @@ func main() {
logging.StderrToLogfile(lf)
+ termWidth, termHeight = ui.TerminalSize()
+
+ termuiColors() // need to do this before initializing widgets so that they can inherit the colors
+ initWidgets()
+ widgetColors()
+ help = w.NewHelpMenu()
+ help.Resize(termWidth, termHeight)
+
setupGrid()
- ui.Render(ui.Body)
+ ui.Render(grid)
eventLoop()
}
diff --git a/src/termui/linegraph.go b/src/termui/linegraph.go
new file mode 100644
index 0000000..a20ddcd
--- /dev/null
+++ b/src/termui/linegraph.go
@@ -0,0 +1,131 @@
+package termui
+
+import (
+ "image"
+ "sort"
+
+ drawille "github.com/cjbassi/drawille-go"
+ . "github.com/gizak/termui"
+)
+
+// LineGraph implements a line graph of data points.
+type LineGraph struct {
+ *Block
+ Data map[string][]float64
+ LineColor map[string]Attribute
+ Zoom int
+ Labels map[string]string
+
+ DefaultLineColor Attribute
+}
+
+// NewLineGraph returns a new LineGraph with current theme.
+func NewLineGraph() *LineGraph {
+ return &LineGraph{
+ Block: NewBlock(),
+ Data: make(map[string][]float64),
+ LineColor: make(map[string]Attribute),
+ Labels: make(map[string]string),
+ Zoom: 5,
+ }
+}
+
+func (self *LineGraph) Draw(buf *Buffer) {
+ self.Block.Draw(buf)
+ // we render each data point on to the canvas then copy over the braille to the buffer at the end
+ // fyi braille characters have 2x4 dots for each character
+ c := drawille.NewCanvas()
+ // used to keep track of the braille colors until the end when we render the braille to the buffer
+ colors := make([][]Attribute, self.Inner.Dx()+2)
+ for i := range colors {
+ colors[i] = make([]Attribute, self.Inner.Dy()+2)
+ }
+
+ // sort the series so that overlapping data will overlap the same way each time
+ seriesList := make([]string, len(self.Data))
+ i := 0
+ for seriesName := range self.Data {
+ seriesList[i] = seriesName
+ i++
+ }
+ sort.Strings(seriesList)
+
+ // draw lines in reverse order so that the first color defined in the colorscheme is on top
+ for i := len(seriesList) - 1; i >= 0; i-- {
+ seriesName := seriesList[i]
+ seriesData := self.Data[seriesName]
+ seriesLineColor, ok := self.LineColor[seriesName]
+ if !ok {
+ seriesLineColor = self.DefaultLineColor
+ }
+
+ // coordinates of last point
+ lastY, lastX := -1, -1
+ // assign colors to `colors` and lines/points to the canvas
+ for i := len(seriesData) - 1; i >= 0; i-- {
+ x := ((self.Inner.Dx() + 1) * 2) - 1 - (((len(seriesData) - 1) - i) * self.Zoom)
+ y := ((self.Inner.Dy() + 1) * 4) - 1 - int((float64((self.Inner.Dy())*4)-1)*(seriesData[i]/100))
+ if x < 0 {
+ // render the line to the last point up to the wall
+ if x > 0-self.Zoom {
+ for _, p := range drawille.Line(lastX, lastY, x, y) {
+ if p.X > 0 {
+ c.Set(p.X, p.Y)
+ colors[p.X/2][p.Y/4] = seriesLineColor
+ }
+ }
+ }
+ break
+ }
+ if lastY == -1 { // if this is the first point
+ c.Set(x, y)
+ colors[x/2][y/4] = seriesLineColor
+ } else {
+ c.DrawLine(lastX, lastY, x, y)
+ for _, p := range drawille.Line(lastX, lastY, x, y) {
+ colors[p.X/2][p.Y/4] = seriesLineColor
+ }
+ }
+ lastX, lastY = x, y
+ }
+
+ // copy braille and colors to buffer
+ for y, line := range c.Rows(c.MinX(), c.MinY(), c.MaxX(), c.MaxY()) {
+ for x, char := range line {
+ x /= 3 // idk why but it works
+ if x == 0 {
+ continue
+ }
+ if char != 10240 { // empty braille character
+ buf.SetCell(
+ Cell{char, AttrPair{colors[x][y], -1}},
+ image.Pt(self.Inner.Min.X+x-1, self.Inner.Min.Y+y-1),
+ )
+ }
+ }
+ }
+ }
+
+ // renders key/label ontop
+ for i, seriesName := range seriesList {
+ if i+2 > self.Inner.Dy() {
+ continue
+ }
+ seriesLineColor, ok := self.LineColor[seriesName]
+ if !ok {
+ seriesLineColor = self.DefaultLineColor
+ }
+
+ // render key ontop, but let braille be drawn over space characters
+ str := seriesName + " " + self.Labels[seriesName]
+ for k, char := range str {
+ if char != ' ' {
+ buf.SetCell(
+ Cell{char, AttrPair{seriesLineColor, -1}},
+ image.Pt(self.Inner.Min.X+2+k, self.Inner.Min.Y+i+1),
+ )
+ }
+ }
+
+ }
+}
diff --git a/src/termui/sparkline.go b/src/termui/sparkline.go
new file mode 100644
index 0000000..4783b86
--- /dev/null
+++ b/src/termui/sparkline.go
@@ -0,0 +1,99 @@
+package termui
+
+import (
+ "image"
+
+ . "github.com/gizak/termui"
+)
+
+var SPARKS = [8]rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
+
+// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
+type Sparkline struct {
+ Data []int
+ Title1 string
+ Title2 string
+ TitleColor Attribute
+ LineColor Attribute
+}
+
+// Sparklines is a renderable widget which groups together the given sparklines.
+type Sparklines struct {
+ *Block
+ Lines []*Sparkline
+}
+
+// Add appends a given Sparkline to the *Sparklines.
+func (self *Sparklines) Add(sl Sparkline) {
+ self.Lines = append(self.Lines, &sl)
+}
+
+// NewSparkline returns an unrenderable single sparkline that intended to be added into a Sparklines.
+func NewSparkline() *Sparkline {
+ return &Sparkline{}
+}
+
+// NewSparklines return a new *Sparklines with given Sparklines, you can always add a new Sparkline later.
+func NewSparklines(ss ...*Sparkline) *Sparklines {
+ return &Sparklines{
+ Block: NewBlock(),
+ Lines: ss,
+ }
+}
+
+func (self *Sparklines) Draw(buf *Buffer) {
+ self.Block.Draw(buf)
+
+ lc := len(self.Lines) // lineCount
+
+ // renders each sparkline and its titles
+ for i, line := range self.Lines {
+
+ // prints titles
+ title1Y := self.Inner.Min.Y + 1 + (self.Inner.Dy()/lc)*i
+ title2Y := self.Inner.Min.Y + 2 + (self.Inner.Dy()/lc)*i
+ title1 := TrimString(line.Title1, self.Inner.Dx())
+ title2 := TrimString(line.Title2, self.Inner.Dx())
+ if self.Inner.Dy() > 5 {
+ buf.SetString(
+ title1,
+ image.Pt(self.Inner.Min.X, title1Y),
+ AttrPair{line.TitleColor | AttrBold, -1},
+ )
+ }
+ if self.Inner.Dy() > 6 {
+ buf.SetString(
+ title2,
+ image.Pt(self.Inner.Min.X, title2Y),
+ AttrPair{line.TitleColor | AttrBold, -1},
+ )
+ }
+
+ sparkY := (self.Inner.Dy() / lc) * (i + 1)
+ // finds max data in current view used for relative heights
+ max := 1
+ for i := len(line.Data) - 1; i >= 0 && self.Inner.Dx()-((len(line.Data)-1)-i) >= 1; i-- {
+ if line.Data[i] > max {
+ max = line.Data[i]
+ }
+ }
+ // prints sparkline
+ for x := self.Inner.Dx(); x >= 1; x-- {
+ char := SPARKS[0]
+ if (self.Inner.Dx() - x) < len(line.Data) {
+ offset := self.Inner.Dx() - x
+ cur_item := line.Data[(len(line.Data)-1)-offset]
+ percent := float64(cur_item) / float64(max)
+ index := int(percent * 7)
+ if index < 0 || index >= len(SPARKS) {
+ panic("TODO")
+ }
+ char = SPARKS[index]
+ }
+ buf.SetCell(
+ Cell{char, AttrPair{line.LineColor, -1}},
+ image.Pt(self.Inner.Min.X+x-1, self.Inner.Min.Y+sparkY-1),
+ )
+ }
+ }
+}
diff --git a/src/termui/table.go b/src/termui/table.go
new file mode 100644
index 0000000..693bb54
--- /dev/null
+++ b/src/termui/table.go
@@ -0,0 +1,206 @@
+package termui
+
+import (
+ "image"
+ "strings"
+ "sync"
+
+ . "github.com/gizak/termui"
+)
+
+// Table tracks all the attributes of a Table instance
+type Table struct {
+ *Block
+
+ sync.Mutex
+
+ Header []string
+ Rows [][]string
+
+ ColWidths []int
+ CellXPos []int // column position
+ ColResizer func() // for widgets that inherit a Table and want to overload the ColResize method
+ Gap int // gap between columns
+ PadLeft int
+
+ Cursor bool
+ CursorColor Attribute
+
+ UniqueCol int // the column used to identify the selected item
+ SelectedItem string // used to keep the cursor on the correct item if the data changes
+ SelectedRow int
+ TopRow int // used to indicate where in the table we are scrolled at
+}
+
+// NewTable returns a new Table instance
+func NewTable() *Table {
+ self := &Table{
+ Block: NewBlock(),
+ // CursorColor: Theme.TableCursor,
+ SelectedRow: 0,
+ TopRow: 0,
+ UniqueCol: 0,
+ }
+ self.ColResizer = self.ColResize
+ return self
+}
+
+// ColResize is the default column resizer, but can be overriden.
+// ColResize calculates the width of each column.
+func (self *Table) ColResize() {
+}
+
+func (self *Table) Draw(buf *Buffer) {
+ self.Lock()
+
+ self.Block.Draw(buf)
+
+ self.ColResizer()
+
+ // finds exact column starting position
+ self.CellXPos = []int{}
+ cur := 1 + self.PadLeft
+ for _, w := range self.ColWidths {
+ self.CellXPos = append(self.CellXPos, cur)
+ cur += w
+ cur += self.Gap
+ }
+
+ // prints header
+ for i, h := range self.Header {
+ width := self.ColWidths[i]
+ if width == 0 {
+ continue
+ }
+ // don't render column if it doesn't fit in widget
+ if width > (self.Inner.Dx()-self.CellXPos[i])+1 {
+ continue
+ }
+ buf.SetString(
+ h,
+ image.Pt(self.Inner.Min.X+self.CellXPos[i]-1, self.Inner.Min.Y),
+ AttrPair{Theme.Default.Fg | AttrBold, -1},
+ )
+ }
+
+ // prints each row
+ for rowNum := self.TopRow; rowNum < self.TopRow+self.Inner.Dy()-1 && rowNum < len(self.Rows); rowNum++ {
+ if rowNum < 0 || rowNum >= len(self.Rows) {
+ panic("TODO")
+ }
+ row := self.Rows[rowNum]
+ y := (rowNum + 2) - self.TopRow
+
+ // prints cursor
+ fg := Theme.Default.Fg
+ if self.Cursor {
+ if (self.SelectedItem == "" && rowNum == self.SelectedRow) || (self.SelectedItem != "" && self.SelectedItem == row[self.UniqueCol]) {
+ fg = self.CursorColor | AttrReverse
+ for _, width := range self.ColWidths {
+ if width == 0 {
+ continue
+ }
+ buf.SetString(
+ strings.Repeat(" ", self.Inner.Dx()),
+ image.Pt(self.Inner.Min.X, self.Inner.Min.Y+y-1),
+ AttrPair{fg, -1},
+ )
+ }
+ self.SelectedItem = row[self.UniqueCol]
+ self.SelectedRow = rowNum
+ }
+ }
+
+ // prints each col of the row
+ for i, width := range self.ColWidths {
+ if width == 0 {
+ continue
+ }
+ // don't render column if width is greater than distance to end of widget
+ if width > (self.Inner.Dx()-self.CellXPos[i])+1 {
+ continue
+ }
+ r := TrimString(row[i], width)
+ buf.SetString(
+ r,
+ image.Pt(self.Inner.Min.X+self.CellXPos[i]-1, self.Inner.Min.Y+y-1),
+ AttrPair{fg, -1},
+ )
+ }
+ }
+ self.Unlock()
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+// Cursor Movement //
+/////////////////////////////////////////////////////////////////////////////////
+
+// calcPos is used to calculate the cursor position and the current view.
+func (self *Table) calcPos() {
+ self.SelectedItem = ""
+
+ if self.SelectedRow < 0 {
+ self.SelectedRow = 0
+ }
+ if self.SelectedRow < self.TopRow {
+ self.TopRow = self.SelectedRow
+ }
+
+ if self.SelectedRow > len(self.Rows)-1 {
+ self.SelectedRow = len(self.Rows) - 1
+ }
+ if self.SelectedRow > self.TopRow+(self.Inner.Dy()-2) {
+ self.TopRow = self.SelectedRow - (self.Inner.Dy() - 2)
+ }
+}
+
+func (self *Table) Up() {
+ self.SelectedRow -= 1
+ self.calcPos()
+}
+
+func (self *Table) Down() {
+ self.SelectedRow += 1
+ self.calcPos()
+}
+
+func (self *Table) Top() {
+ self.SelectedRow = 0
+ self.calcPos()
+}
+
+func (self *Table) Bottom() {
+ self.SelectedRow = len(self.Rows) - 1
+ self.calcPos()
+}
+
+// The number of lines in a page is equal to the height of the widgeself.
+
+func (self *Table) HalfPageUp() {
+ self.SelectedRow = self.SelectedRow - (self.Inner.Dy()-2)/2
+ self.calcPos()
+}
+
+func (self *Table) HalfPageDown() {
+ self.SelectedRow = self.SelectedRow + (self.Inner.Dy()-2)/2
+ self.calcPos()
+}
+
+func (self *Table) PageUp() {
+ self.SelectedRow -= (self.Inner.Dy() - 2)
+ self.calcPos()
+}
+
+func (self *Table) PageDown() {
+ self.SelectedRow += (self.Inner.Dy() - 2)
+ self.calcPos()
+}
+
+func (self *Table) Click(x, y int) {
+ x = x - self.Min.X
+ y = y - self.Min.Y
+ if (x > 0 && x <= self.Inner.Dx()) && (y > 0 && y <= self.Inner.Dy()) {
+ self.SelectedRow = (self.TopRow + y) - 2
+ self.calcPos()
+ }
+}
diff --git a/src/widgets/battery.go b/src/widgets/battery.go
index c087dd2..271de65 100644
--- a/src/widgets/battery.go
+++ b/src/widgets/battery.go
@@ -7,7 +7,7 @@ import (
"strconv"
"time"
- ui "github.com/cjbassi/termui"
+ ui "github.com/cjbassi/gotop/src/termui"
"github.com/distatus/battery"
)
@@ -24,7 +24,7 @@ func NewBatt(interval time.Duration, zoom int) *Batt {
Count: len(batts),
interval: interval,
}
- self.Label = "Battery Status"
+ self.Title = "Battery Status"
self.Zoom = zoom
if err != nil {
log.Printf("failed to get battery info from system: %v", err)
diff --git a/src/widgets/cpu.go b/src/widgets/cpu.go
index 649f50d..c041e67 100644
--- a/src/widgets/cpu.go
+++ b/src/widgets/cpu.go
@@ -5,7 +5,7 @@ import (
"log"
"time"
- ui "github.com/cjbassi/termui"
+ ui "github.com/cjbassi/gotop/src/termui"
psCPU "github.com/shirou/gopsutil/cpu"
)
@@ -35,7 +35,7 @@ func NewCPU(interval time.Duration, zoom int, average bool, percpu bool) *CPU {
PerCPU: percpu,
formatString: formatString,
}
- self.Label = "CPU Usage"
+ self.Title = " CPU Usage "
self.Zoom = zoom
if !(self.Average || self.PerCPU) {
diff --git a/src/widgets/disk.go b/src/widgets/disk.go
index e8d8f68..fe2315b 100644
--- a/