package main import ( "fmt" "github.com/bcicen/ctop/config" "github.com/bcicen/ctop/widgets" ui "github.com/gizak/termui" ) type Grid struct { cursorID string // id of currently selected container cmap *ContainerMap containers []*Container // sorted slice of containers header *widgets.CTopHeader } func NewGrid() *Grid { cmap := NewContainerMap() g := &Grid{ cmap: cmap, containers: cmap.All(), header: widgets.NewCTopHeader(), } // set initial cursor position if len(g.containers) > 0 { g.cursorID = g.containers[0].id } return g } // Return current cursor index func (g *Grid) cursorIdx() int { for n, c := range g.containers { if c.id == g.cursorID { return n } } return 0 } func (g *Grid) cursorUp() { idx := g.cursorIdx() // decrement if possible if idx > 0 { g.cursorID = g.containers[idx-1].id g.redrawCursor() } } func (g *Grid) cursorDown() { idx := g.cursorIdx() // increment if possible if idx < (len(g.containers) - 1) { g.cursorID = g.containers[idx+1].id g.redrawCursor() } } // Redraw the cursor with the currently selected row func (g *Grid) redrawCursor() { for _, c := range g.containers { if c.id == g.cursorID { c.widgets.Highlight() } else { c.widgets.UnHighlight() } ui.Render(ui.Body) } } func (g *Grid) redrawRows() { // reinit body rows ui.Body.Rows = []*ui.Row{} ui.Clear() // build layout if config.GetSwitchVal("enableHeader") { ui.Body.Y = g.header.Height() g.header.SetCount(len(g.containers)) g.header.SetFilter(config.GetVal("filterStr")) g.header.Render() } else { ui.Body.Y = 0 } ui.Body.AddRows(fieldHeader()) for _, c := range g.containers { ui.Body.AddRows(c.widgets.Row()) } ui.Body.Align() resizeIndicator() ui.Render(ui.Body) // dump aligned widget positions and sizes //for i, w := range ui.Body.Rows[1].Cols { //log.Infof("w%v: x=%v y=%v w=%v h=%v", i, w.X, w.Y, w.Width, w.Height) //} } // override Align()'d size for indicator column func resizeIndicator() { xShift := 1 toWidth := 3 for _, r := range ui.Body.Rows { wDiff := r.Cols[0].Width - (toWidth + xShift) // set indicator x, width r.Cols[0].SetX(xShift) r.Cols[0].SetWidth(toWidth) // shift remainder of columns left by wDiff for _, c := range r.Cols[1:] { c.SetX(c.X - wDiff) c.SetWidth(c.Width - wDiff) } } } func fieldHeader() *ui.Row { return ui.NewRow( ui.NewCol(1, 0, headerPar("")), ui.NewCol(2, 0, headerPar("NAME")), ui.NewCol(2, 0, headerPar("CID")), ui.NewCol(2, 0, headerPar("CPU")), ui.NewCol(2, 0, headerPar("MEM")), ui.NewCol(2, 0, headerPar("NET RX/TX")), ) } func headerPar(s string) *ui.Par { p := ui.NewPar(fmt.Sprintf(" %s", s)) p.Border = false p.Height = 2 p.Width = 20 p.TextFgColor = ui.ColorWhite return p } func (g *Grid) ExpandView() { ui.Clear() ui.DefaultEvtStream.ResetHandlers() defer ui.DefaultEvtStream.ResetHandlers() container := g.cmap.Get(g.cursorID) container.Expand() container.widgets.Render() container.Collapse() } func logEvent(e ui.Event) { var s string s += fmt.Sprintf("Type: %s\n", e.Type) s += fmt.Sprintf("Path: %s\n", e.Path) s += fmt.Sprintf("From: %s\n", e.From) s += fmt.Sprintf("To: %s", e.To) log.Debugf("new event:\n%s", s) } func Display(g *Grid) bool { var menu func() var expand bool var loopIter int ui.DefaultEvtStream.Hook(logEvent) // initial draw g.redrawCursor() g.redrawRows() ui.Handle("/sys/kbd/", func(ui.Event) { g.cursorUp() }) ui.Handle("/sys/kbd/", func(ui.Event) { g.cursorDown() }) ui.Handle("/sys/kbd/", func(ui.Event) { expand = true ui.StopLoop() }) ui.Handle("/sys/kbd/a", func(ui.Event) { config.Toggle("allContainers") g.redrawRows() }) ui.Handle("/sys/kbd/f", func(ui.Event) { menu = FilterMenu ui.StopLoop() }) ui.Handle("/sys/kbd/h", func(ui.Event) { menu = HelpMenu ui.StopLoop() }) ui.Handle("/sys/kbd/H", func(ui.Event) { config.Toggle("enableHeader") g.redrawRows() }) ui.Handle("/sys/kbd/q", func(ui.Event) { ui.StopLoop() }) ui.Handle("/sys/kbd/r", func(e ui.Event) { config.Toggle("sortReversed") }) ui.Handle("/sys/kbd/s", func(ui.Event) { menu = SortMenu ui.StopLoop() }) ui.Handle("/timer/1s", func(e ui.Event) { loopIter++ if loopIter%5 == 0 { g.cmap.Refresh() } g.containers = g.cmap.All() // refresh containers for current sort order g.redrawRows() }) ui.Handle("/sys/wnd/resize", func(e ui.Event) { ui.Body.Width = ui.TermWidth() log.Infof("resize: width=%v", ui.Body.Width) g.redrawRows() }) ui.Loop() if menu != nil { menu() return false } if expand { g.ExpandView() return false } return true }