diff --git a/README.md b/README.md index 0f667e20..f651bed4 100644 --- a/README.md +++ b/README.md @@ -113,11 +113,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) ## Stargazers over time diff --git a/go.mod b/go.mod index e2f7ec6d..f2148813 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,11 @@ module github.com/cjbassi/gotop require ( 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815 + github.com/gizak/termui v0.0.0-20190101005313-46c77dca8480 github.com/go-ole/go-ole v1.2.1 // indirect - github.com/mattn/go-runewidth v0.0.2 // 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 diff --git a/go.sum b/go.sum index b869d58c..de5c0f72 100644 --- a/go.sum +++ b/go.sum @@ -4,20 +4,20 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIO 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 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/gizak/termui v0.0.0-20190101005313-46c77dca8480 h1:+NHS7QUnQqEwIuhrHaFT6R78LMGbja4fYTu6wny7Q4s= +github.com/gizak/termui v0.0.0-20190101005313-46c77dca8480/go.mod h1:S3xz8JHXNDPSNFsvXCdG7bHlEGrwvUG3a0joR/xYZ5M= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= 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/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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shirou/gopsutil v2.18.11+incompatible h1:PMFTKnFTr/YTRW5rbLK4vWALV3a+IGXse5nvhSjztmg= diff --git a/main.go b/main.go index 60df7d53..48adc7dc 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" @@ -38,6 +38,8 @@ var ( configDir = appdir.New("gotop").UserConfig() logPath = filepath.Join(configDir, "errors.log") stderrLogger = log.New(os.Stderr, "", 0) + termWidth int + termHeight int cpu *w.CPU mem *w.Mem @@ -46,6 +48,7 @@ var ( disk *w.Disk temp *w.Temp help *w.HelpMenu + grid *ui.Grid ) func cliArguments() error { @@ -137,41 +140,46 @@ 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) 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) + grid.Set( + ui.NewRow(1.0/2, cpu), + ui.NewRow(1.0/2, + ui.NewCol(1.0/2, mem), + ui.NewCol(1.0/2, proc), + ), + ) } else { - ui.Body.Set(0, 0, 12, 4, 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( + ui.NewRow(1.0/3, cpu), + ui.NewRow(1.0/3, + 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(1.0/3, + ui.NewCol(1.0/2, net), + ui.NewCol(1.0/2, proc), + ), + ) } } 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 { @@ -185,13 +193,18 @@ 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) + 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) } } @@ -246,7 +259,7 @@ func eventLoop() { return case <-drawTicker: if !helpVisible { - ui.Render(ui.Body) + ui.Render(grid) } case e := <-uiEvents: switch e.ID { @@ -258,7 +271,7 @@ func eventLoop() { ui.Clear() ui.Render(help) } else { - ui.Render(ui.Body) + ui.Render(grid) } case "h": if !helpVisible { @@ -279,27 +292,27 @@ func eventLoop() { case "": if helpVisible { helpVisible = false - ui.Render(ui.Body) + ui.Render(grid) } case "": 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 "": payload := e.Payload.(ui.Mouse) proc.Click(payload.X, payload.Y) ui.Render(proc) - case "", "", "k": + case "k", "", "": proc.Up() ui.Render(proc) - case "", "", "j": + case "j", "", "": proc.Down() ui.Render(proc) case "g", "": @@ -373,11 +386,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) } @@ -385,8 +393,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 00000000..72f48b69 --- /dev/null +++ b/src/termui/linegraph.go @@ -0,0 +1,132 @@ +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, + } +} + +// Buffer implements Bufferer interface. +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 00000000..52963fa4 --- /dev/null +++ b/src/termui/sparkline.go @@ -0,0 +1,96 @@ +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, + } +} + +// Buffer implements Bufferer interface. +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 := 2 + (self.Inner.Dy()/lc)*i + title2Y := (2 + (self.Inner.Dy()/lc)*i) + 1 + title1 := TrimString(line.Title1, self.Inner.Dx()) + title2 := TrimString(line.Title2, self.Inner.Dx()) + buf.SetString( + title1, + image.Pt(self.Inner.Min.X, self.Inner.Min.Y+title1Y-1), + AttrPair{line.TitleColor | AttrBold, -1}, + ) + buf.SetString( + title2, + image.Pt(self.Inner.Min.X, self.Inner.Min.Y+title2Y-1), + 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 00000000..bb62eec0 --- /dev/null +++ b/src/termui/table.go @@ -0,0 +1,201 @@ +package termui + +import ( + "image" + "strings" + + . "github.com/gizak/termui" +) + +// Table tracks all the attributes of a Table instance +type Table struct { + *Block + + 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() { +} + +// Buffer implements the Bufferer interface. +func (self *Table) Draw(buf *Buffer) { + 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}, + ) + } + } +} + +///////////////////////////////////////////////////////////////////////////////// +// 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/cpu.go b/src/widgets/cpu.go index 649f50d3..c041e67f 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 e8d8f68d..fe2315b7 100644 --- a/src/widgets/disk.go +++ b/src/widgets/disk.go @@ -7,8 +7,8 @@ import ( "strings" "time" + ui "github.com/cjbassi/gotop/src/termui" "github.com/cjbassi/gotop/src/utils" - ui "github.com/cjbassi/termui" psDisk "github.com/shirou/gopsutil/disk" ) @@ -35,7 +35,7 @@ func NewDisk() *Disk { interval: time.Second, Partitions: make(map[string]*Partition), } - self.Label = "Disk Usage" + self.Title = " Disk Usage " self.Header = []string{"Disk", "Mount", "Used", "Free", "R/s", "W/s"} self.Gap = 2 self.ColResizer = self.ColResize @@ -154,8 +154,8 @@ func (self *Disk) update() { // ColResize overrides the default ColResize in the termui table. func (self *Disk) ColResize() { self.ColWidths = []int{ - utils.Max(4, (self.X-29)/2), - utils.Max(5, (self.X-29)/2), + utils.Max(4, (self.Inner.Dx()-29)/2), + utils.Max(5, (self.Inner.Dx()-29)/2), 4, 5, 5, 5, } diff --git a/src/widgets/help.go b/src/widgets/help.go index 19fa302c..ff2fcc4f 100644 --- a/src/widgets/help.go +++ b/src/widgets/help.go @@ -1,9 +1,10 @@ package widgets import ( + "image" "strings" - ui "github.com/cjbassi/termui" + ui "github.com/gizak/termui" ) const KEYBINDS = ` @@ -34,27 +35,33 @@ CPU and Mem graph scaling: ` type HelpMenu struct { - *ui.Block + ui.Block } func NewHelpMenu() *HelpMenu { - block := ui.NewBlock() - block.X = 51 // width - 1 - block.Y = 24 // height - 1 - return &HelpMenu{block} + return &HelpMenu{ + Block: *ui.NewBlock(), + } } -func (self *HelpMenu) Buffer() *ui.Buffer { - buf := self.Block.Buffer() +func (self *HelpMenu) Resize(termWidth, termHeight int) { + textWidth := 53 + textHeight := 22 + x := (termWidth - textWidth) / 2 + y := (termHeight - textHeight) / 2 + + self.Block.SetRect(x, y, textWidth+x, textHeight+y) +} - self.Block.XOffset = (ui.Body.Width - self.Block.X) / 2 // X coordinate - self.Block.YOffset = (ui.Body.Height - self.Block.Y) / 2 // Y coordinate +func (self *HelpMenu) Draw(buf *ui.Buffer) { + self.Block.Draw(buf) for y, line := range strings.Split(KEYBINDS, "\n") { for x, char := range line { - buf.SetCell(x+1, y, ui.NewCell(char, ui.Color(7), self.Bg)) + buf.SetCell( + ui.Cell{char, ui.AttrPair{ui.Attribute(7), -1}}, + image.Pt(self.Inner.Min.X+x, self.Inner.Min.Y+y-1), + ) } } - - return buf } diff --git a/src/widgets/mem.go b/src/widgets/mem.go index 86f3a45c..1e5718c2 100644 --- a/src/widgets/mem.go +++ b/src/widgets/mem.go @@ -5,8 +5,8 @@ import ( "log" "time" + ui "github.com/cjbassi/gotop/src/termui" "github.com/cjbassi/gotop/src/utils" - ui "github.com/cjbassi/termui" psMem "github.com/shirou/gopsutil/mem" ) @@ -20,7 +20,7 @@ func NewMem(interval time.Duration, zoom int) *Mem { LineGraph: ui.NewLineGraph(), interval: interval, } - self.Label = "Memory Usage" + self.Title = " Memory Usage " self.Zoom = zoom self.Data["Main"] = []float64{0} self.Data["Swap"] = []float64{0} diff --git a/src/widgets/net.go b/src/widgets/net.go index c47ae258..1a3c2632 100644 --- a/src/widgets/net.go +++ b/src/widgets/net.go @@ -5,8 +5,8 @@ import ( "log" "time" + ui "github.com/cjbassi/gotop/src/termui" "github.com/cjbassi/gotop/src/utils" - ui "github.com/cjbassi/termui" psNet "github.com/shirou/gopsutil/net" ) @@ -30,7 +30,7 @@ func NewNet() *Net { Sparklines: spark, interval: time.Second, } - self.Label = "Network Usage" + self.Title = " Network Usage " self.update() diff --git a/src/widgets/proc.go b/src/widgets/proc.go index dc04216f..d4ba7392 100644 --- a/src/widgets/proc.go +++ b/src/widgets/proc.go @@ -8,8 +8,9 @@ import ( "strconv" "time" + ui "github.com/cjbassi/gotop/src/termui" "github.com/cjbassi/gotop/src/utils" - ui "github.com/cjbassi/termui" + "github.com/gizak/termui" psCPU "github.com/shirou/gopsutil/cpu" ) @@ -49,7 +50,7 @@ func NewProc() *Proc { sortMethod: "c", group: true, } - self.Label = "Processes" + self.Title = " Processes " self.ColResizer = self.ColResize self.Cursor = true self.Gap = 3 @@ -108,11 +109,11 @@ func (self *Proc) Sort() { // ColResize overrides the default ColResize in the termui table. func (self *Proc) ColResize() { self.ColWidths = []int{ - 5, utils.Max(self.X-26, 10), 4, 4, + 5, utils.Max(self.Inner.Dx()-26, 10), 4, 4, } } -func (self *Proc) ChangeSort(e ui.Event) { +func (self *Proc) ChangeSort(e termui.Event) { if self.sortMethod != e.ID { self.sortMethod = e.ID self.Top() diff --git a/src/widgets/temp.go b/src/widgets/temp.go index ab1ac5ae..f065ad4b 100644 --- a/src/widgets/temp.go +++ b/src/widgets/temp.go @@ -5,10 +5,11 @@ package widgets import ( "fmt" + "image" "sort" "time" - ui "github.com/cjbassi/termui" + ui "github.com/gizak/termui" ) type Temp struct { @@ -16,8 +17,8 @@ type Temp struct { interval time.Duration Data map[string]int Threshold int - TempLow ui.Color - TempHigh ui.Color + TempLow ui.Attribute + TempHigh ui.Attribute Fahrenheit bool } @@ -28,7 +29,7 @@ func NewTemp(fahrenheit bool) *Temp { Data: make(map[string]int), Threshold: 80, // temp at which color should change } - self.Label = "Temperatures" + self.Title = " Temperatures " if fahrenheit { self.Fahrenheit = true @@ -48,8 +49,8 @@ func NewTemp(fahrenheit bool) *Temp { } // Buffer implements ui.Bufferer interface and renders the widget. -func (self *Temp) Buffer() *ui.Buffer { - buf := self.Block.Buffer() +func (self *Temp) Draw(buf *ui.Buffer) { + self.Block.Draw(buf) var keys []string for key := range self.Data { @@ -58,7 +59,7 @@ func (self *Temp) Buffer() *ui.Buffer { sort.Strings(keys) for y, key := range keys { - if y+1 > self.Y { + if y+1 > self.Inner.Dy() { break } @@ -67,14 +68,23 @@ func (self *Temp) Buffer() *ui.Buffer { fg = self.TempHigh } - s := ui.MaxString(key, (self.X - 4)) - buf.SetString(1, y+1, s, self.Fg, self.Bg) + s := ui.TrimString(key, (self.Inner.Dx() - 4)) + buf.SetString(s, + image.Pt(self.Inner.Min.X, self.Inner.Min.Y+y), + ui.Theme.Default, + ) if self.Fahrenheit { - buf.SetString(self.X-3, y+1, fmt.Sprintf("%3dF", self.Data[key]), fg, self.Bg) + buf.SetString( + fmt.Sprintf("%3dF", self.Data[key]), + image.Pt(self.Inner.Dx()-3, y+1), + ui.AttrPair{fg, -1}, + ) } else { - buf.SetString(self.X-3, y+1, fmt.Sprintf("%3dC", self.Data[key]), fg, self.Bg) + buf.SetString( + fmt.Sprintf("%3dC", self.Data[key]), + image.Pt(self.Inner.Max.X-4, self.Inner.Min.Y+y), + ui.AttrPair{fg, -1}, + ) } } - - return buf }