diff --git a/config.yml b/config.yml index e0f20a9..865bdb3 100644 --- a/config.yml +++ b/config.yml @@ -8,7 +8,7 @@ runcharts: script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/ - label: BING script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ - refresh-rate-ms: 500 + refresh-rate-ms: 200 decimal-places: 3 alert: value: @@ -52,8 +52,8 @@ runcharts: x: 0 y: 15 size: - x: 15 - y: 15 + x: 4 + y: 4 - title: MONGO COLLECTIONS COUNT items: - label: POSTS diff --git a/console/key.go b/console/key.go new file mode 100644 index 0000000..95ac467 --- /dev/null +++ b/console/key.go @@ -0,0 +1,14 @@ +package console + +const ( + KeyPause = "p" + KeyQuit = "q" + KeyResize = "" + KeyExit = "" + KeyLeft = "" + KeyRight = "" + KeyUp = "" + KeyDown = "" + KeyEnter = "" + KeyEsc = "" +) diff --git a/console/palette.go b/console/palette.go index b1c4be3..954b2da 100644 --- a/console/palette.go +++ b/console/palette.go @@ -17,6 +17,9 @@ const ( ColorDeepSkyBlue ui.Color = 39 ColorDeepPink ui.Color = 162 ColorDarkGrey ui.Color = 240 + ColorWhite ui.Color = 7 + ColorBlack ui.Color = 0 + ColorClear ui.Color = -1 ) type Palette struct { diff --git a/event/event.go b/event/event.go deleted file mode 100644 index 21b31ca..0000000 --- a/event/event.go +++ /dev/null @@ -1,15 +0,0 @@ -package event - -type Event string - -const ( - EventPause = "p" - EventQuit = "q" - EventResize = "" - EventExit = "" - EventMouseClick = "" - EventKeyboardLeft = "" - EventKeyboardRight = "" - EventKeyboardUp = "" - EventKeyboardDown = "" -) diff --git a/event/handler.go b/event/handler.go index 0926a1d..ce6ca83 100644 --- a/event/handler.go +++ b/event/handler.go @@ -1,6 +1,7 @@ package event import ( + "github.com/sqshq/sampler/console" "github.com/sqshq/sampler/widgets" ui "github.com/sqshq/termui" "time" @@ -24,23 +25,17 @@ func (self *Handler) HandleEvents() { } case e := <-self.ConsoleEvents: switch e.ID { - case EventQuit, EventExit: + case console.KeyQuit, console.KeyExit: return - case EventPause: + case console.KeyPause: pause = !pause - case EventResize: + case console.KeyResize: payload := e.Payload.(ui.Resize) self.Layout.ChangeDimensions(payload.Width, payload.Height) - case "a": - self.Layout.GetComponent(0).DisableSelection() - case EventKeyboardLeft: - self.Layout.GetComponent(0).MoveSelection(-1) - case EventKeyboardRight: - self.Layout.GetComponent(0).MoveSelection(+1) - case EventKeyboardDown: - //layout.GetItem(0).Move(0, 1) - case EventKeyboardUp: - //layout.GetItem(0).Move(0, -1) + //case "a": + // self.Layout.GetComponent(0).DisableSelection() + default: + self.Layout.HandleConsoleEvent(e.ID) } } } diff --git a/main.go b/main.go index 1396c37..4242660 100644 --- a/main.go +++ b/main.go @@ -17,12 +17,13 @@ func main() { csl.Init() defer csl.Close() - layout := widgets.NewLayout(ui.TerminalDimensions()) + width, height := ui.TerminalDimensions() + layout := widgets.NewLayout(width, height, widgets.NewMenu()) for _, chartConfig := range cfg.RunCharts { chart := widgets.NewRunChart(chartConfig.Title, chartConfig.Precision, chartConfig.RefreshRateMs) - layout.AddComponent(chart, chartConfig.Position, chartConfig.Size, widgets.TypeRunChart) + layout.AddComponent(chart, chartConfig.Title, chartConfig.Position, chartConfig.Size, widgets.TypeRunChart) for _, item := range chartConfig.Items { data.NewSampler(chart, item, chartConfig.RefreshRateMs) diff --git a/widgets/component.go b/widgets/component.go index 9b64a68..2ddcfa5 100644 --- a/widgets/component.go +++ b/widgets/component.go @@ -6,15 +6,17 @@ import ( type Component struct { Drawable Drawable + Title string Position Position Size Size Type ComponentType } -type ComponentType string +type ComponentType rune const ( - TypeRunChart ComponentType = "runchart" + TypeRunChart ComponentType = 0 + TypeBarChart ComponentType = 1 ) type Position struct { diff --git a/widgets/layout.go b/widgets/layout.go index 9f5075e..e22d7f0 100644 --- a/widgets/layout.go +++ b/widgets/layout.go @@ -1,39 +1,57 @@ package widgets import ( - . "github.com/sqshq/termui" + "github.com/sqshq/sampler/console" + ui "github.com/sqshq/termui" ) type Layout struct { - Block + ui.Block components []Component + menu *Menu + mode Mode + selection int } +type Mode rune + +const ( + ModeDefault Mode = 0 + ModeComponentSelect Mode = 1 + ModeMenuOptionSelect Mode = 2 + ModeComponentMove Mode = 3 + ModeComponentResize Mode = 4 + ModeChartPinpoint Mode = 5 +) + const ( columnsCount = 30 rowsCount = 30 ) -func NewLayout(width, height int) *Layout { +func NewLayout(width, height int, menu *Menu) *Layout { - block := *NewBlock() + block := *ui.NewBlock() block.SetRect(0, 0, width, height) return &Layout{ Block: block, components: make([]Component, 0), + menu: menu, + mode: ModeDefault, + selection: 0, } } -func (self *Layout) AddComponent(drawable Drawable, position Position, size Size, Type ComponentType) { - self.components = append(self.components, Component{drawable, position, size, Type}) +func (l *Layout) AddComponent(drawable ui.Drawable, title string, position Position, size Size, Type ComponentType) { + l.components = append(l.components, Component{drawable, title, position, size, Type}) } -func (self *Layout) GetComponents(Type ComponentType) []Drawable { +func (l *Layout) GetComponents(Type ComponentType) []ui.Drawable { - var components []Drawable + var components []ui.Drawable - for _, component := range self.components { + for _, component := range l.components { if component.Type == Type { components = append(components, component.Drawable) } @@ -42,21 +60,127 @@ func (self *Layout) GetComponents(Type ComponentType) []Drawable { return components } -// temp function until ui item selection is implemented -func (self *Layout) GetComponent(i int) *RunChart { - return self.components[i].Drawable.(*RunChart) +// TODO func to get prev/next component navigating left/right/top/bottom +func (l *Layout) getComponent(i int) Component { + return l.components[i] } -func (self *Layout) ChangeDimensions(width, height int) { - self.SetRect(0, 0, width, height) +func (l *Layout) getSelectedComponent() *Component { + return &l.components[l.selection] } -func (self *Layout) Draw(buffer *Buffer) { +func (l *Layout) HandleConsoleEvent(e string) { + switch e { + case console.KeyEnter: + switch l.mode { + case ModeComponentSelect: + l.menu.choose() + l.mode = ModeMenuOptionSelect + case ModeMenuOptionSelect: + option := l.menu.getSelectedOption() + switch option { + case MenuOptionMove: + l.mode = ModeComponentMove + l.menu.moveOrResize() + case MenuOptionResize: + l.mode = ModeComponentResize + l.menu.moveOrResize() + case MenuOptionPinpoint: + l.mode = ModeChartPinpoint + l.menu.idle() + chart := l.getSelectedComponent().Drawable.(*RunChart) + chart.MoveSelection(0) + case MenuOptionExit: + l.mode = ModeDefault + l.menu.idle() + } + case ModeComponentMove: + fallthrough + case ModeComponentResize: + l.menu.idle() + l.mode = ModeDefault + } + case console.KeyEsc: + switch l.mode { + case ModeChartPinpoint: + chart := l.getSelectedComponent().Drawable.(*RunChart) + chart.DisableSelection() + fallthrough + case ModeComponentSelect: + fallthrough + case ModeMenuOptionSelect: + l.menu.idle() + l.mode = ModeDefault + } + case console.KeyLeft: + switch l.mode { + case ModeDefault: + l.mode = ModeComponentSelect + l.selection = 0 + l.menu.highlight(l.getComponent(l.selection)) + case ModeChartPinpoint: + chart := l.getSelectedComponent().Drawable.(*RunChart) + chart.MoveSelection(-1) + case ModeComponentSelect: + if l.selection > 0 { + l.selection-- + } + l.menu.highlight(l.getComponent(l.selection)) + case ModeComponentMove: + l.getSelectedComponent().Move(-1, 0) + case ModeComponentResize: + l.getSelectedComponent().Resize(-1, 0) + } + case console.KeyRight: + switch l.mode { + case ModeDefault: + l.mode = ModeComponentSelect + l.selection = 0 + l.menu.highlight(l.getComponent(l.selection)) + case ModeChartPinpoint: + chart := l.getSelectedComponent().Drawable.(*RunChart) + chart.MoveSelection(1) + case ModeComponentSelect: + if l.selection < len(l.components)-1 { + l.selection++ + } + l.menu.highlight(l.getComponent(l.selection)) + case ModeComponentMove: + l.getSelectedComponent().Move(1, 0) + case ModeComponentResize: + l.getSelectedComponent().Resize(1, 0) + } + case console.KeyUp: + switch l.mode { + case ModeMenuOptionSelect: + l.menu.up() + case ModeComponentMove: + l.getSelectedComponent().Move(0, -1) + case ModeComponentResize: + l.getSelectedComponent().Resize(0, -1) + } + case console.KeyDown: + switch l.mode { + case ModeMenuOptionSelect: + l.menu.down() + case ModeComponentMove: + l.getSelectedComponent().Move(0, 1) + case ModeComponentResize: + l.getSelectedComponent().Resize(0, 1) + } + } +} - columnWidth := float64(self.GetRect().Dx()) / columnsCount - rowHeight := float64(self.GetRect().Dy()) / rowsCount +func (l *Layout) ChangeDimensions(width, height int) { + l.SetRect(0, 0, width, height) +} - for _, component := range self.components { +func (l *Layout) Draw(buffer *ui.Buffer) { + + columnWidth := float64(l.GetRect().Dx()) / columnsCount + rowHeight := float64(l.GetRect().Dy()) / rowsCount + + for _, component := range l.components { x1 := float64(component.Position.X) * columnWidth y1 := float64(component.Position.Y) * rowHeight @@ -66,4 +190,6 @@ func (self *Layout) Draw(buffer *Buffer) { component.Drawable.SetRect(int(x1), int(y1), int(x2), int(y2)) component.Drawable.Draw(buffer) } + + l.menu.Draw(buffer) } diff --git a/widgets/menu.go b/widgets/menu.go new file mode 100644 index 0000000..1361186 --- /dev/null +++ b/widgets/menu.go @@ -0,0 +1,222 @@ +package widgets + +import ( + "github.com/sqshq/sampler/console" + ui "github.com/sqshq/termui" + "image" +) + +type Menu struct { + ui.Block + options []MenuOption + component Component + mode MenuMode + option MenuOption +} + +type MenuMode rune + +const ( + MenuModeIdle MenuMode = 0 + MenuModeHighlight MenuMode = 1 + MenuModeOptionSelect MenuMode = 2 + MenuModeMoveAndResize MenuMode = 3 +) + +type MenuOption string + +const ( + MenuOptionMove MenuOption = "MOVE" + MenuOptionResize MenuOption = "RESIZE" + MenuOptionPinpoint MenuOption = "PINPOINT" + MenuOptionExit MenuOption = "EXIT" +) + +func NewMenu() *Menu { + block := *ui.NewBlock() + block.Border = true + block.BorderStyle = ui.NewStyle(console.ColorDarkGrey) + return &Menu{ + Block: block, + options: []MenuOption{MenuOptionMove, MenuOptionResize, MenuOptionPinpoint, MenuOptionExit}, + mode: MenuModeIdle, + option: MenuOptionMove, + } +} + +func (m *Menu) getSelectedOption() MenuOption { + return m.option +} + +func (m *Menu) highlight(component Component) { + m.component = component + m.updateDimensions() + m.mode = MenuModeHighlight + m.Title = component.Title +} + +func (m *Menu) choose() { + m.mode = MenuModeOptionSelect +} + +func (m *Menu) idle() { + m.mode = MenuModeIdle +} + +func (m *Menu) up() { + for i := 1; i < len(m.options); i++ { + if m.options[i] == m.option { + m.option = m.options[i-1] + break + } + } +} + +func (m *Menu) down() { + for i := 0; i < len(m.options)-1; i++ { + if m.options[i] == m.option { + m.option = m.options[i+1] + break + } + } +} + +func (m *Menu) moveOrResize() { + m.mode = MenuModeMoveAndResize +} + +func (m *Menu) Draw(buffer *ui.Buffer) { + + if m.mode == MenuModeIdle { + return + } + + m.updateDimensions() + + buffer.Fill( + ui.NewCell(' ', ui.NewStyle(ui.ColorClear, ui.ColorBlack)), + m.GetRect(), + ) + + switch m.mode { + case MenuModeHighlight: + m.renderHighlight(buffer) + case MenuModeMoveAndResize: + m.renderMoveAndResize(buffer) + case MenuModeOptionSelect: + m.renderOptions(buffer) + } + + m.drawInnerBorder(buffer) + m.Block.Draw(buffer) +} + +func (m *Menu) renderHighlight(buffer *ui.Buffer) { + + m.printAllDirectionsArrowSign(buffer, -2) + + arrowsText := "Use arrows for selection" + buffer.SetString( + arrowsText, + ui.NewStyle(console.ColorDarkGrey), + getMiddlePoint(m.Block, arrowsText, 2), + ) + + optionsText := " to view options" + buffer.SetString( + optionsText, + ui.NewStyle(console.ColorDarkGrey), + getMiddlePoint(m.Block, optionsText, 3), + ) + + resumeText := " to resume" + buffer.SetString( + resumeText, + ui.NewStyle(console.ColorDarkGrey), + getMiddlePoint(m.Block, resumeText, 4), + ) +} + +func (m *Menu) renderMoveAndResize(buffer *ui.Buffer) { + + m.printAllDirectionsArrowSign(buffer, -2) + + saveText := " to save changes" + textPoint := getMiddlePoint(m.Block, saveText, 4) + if textPoint.In(m.Rectangle) { + buffer.SetString( + saveText, + ui.NewStyle(console.ColorDarkGrey), + textPoint, + ) + } +} + +func (m *Menu) printAllDirectionsArrowSign(buffer *ui.Buffer, y int) { + + arrows := []string{ + " ↑ ", + "←· →", + " ↓ ", + } + + for i, a := range arrows { + buffer.SetString( + a, + ui.NewStyle(console.ColorOlive), + getMiddlePoint(m.Block, a, i+y), + ) + } +} + +func (m *Menu) renderOptions(buffer *ui.Buffer) { + + // TODO extract styles to console.Palette + highlightedStyle := ui.NewStyle(console.ColorWhite, console.ColorClear, ui.ModifierReverse) + regularStyle := ui.NewStyle(console.ColorWhite) + + offset := 1 + for _, option := range m.options { + + style := regularStyle + if m.option == option { + style = highlightedStyle + } + + if option != MenuOptionPinpoint || m.component.Type == TypeRunChart { + offset += 2 + buffer.SetString( + string(option), + style, + getMiddlePoint(m.Block, string(option), offset-5), + ) + } + } +} + +func (m *Menu) updateDimensions() { + r := m.component.Drawable.GetRect() + m.SetRect(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y) +} + +func (m *Menu) drawInnerBorder(buffer *ui.Buffer) { + + verticalCell := ui.Cell{ui.VERTICAL_LINE, m.BorderStyle} + horizontalCell := ui.Cell{ui.HORIZONTAL_LINE, m.BorderStyle} + + // draw lines + buffer.Fill(horizontalCell, image.Rect(m.Min.X+2, m.Min.Y+2, m.Max.X-2, m.Min.Y)) + buffer.Fill(horizontalCell, image.Rect(m.Min.X+2, m.Max.Y-2, m.Max.X-2, m.Max.Y)) + buffer.Fill(verticalCell, image.Rect(m.Min.X+2, m.Min.Y+1, m.Min.X+3, m.Max.Y-1)) + buffer.Fill(verticalCell, image.Rect(m.Max.X-2, m.Min.Y, m.Max.X-3, m.Max.Y)) + + // draw corners + buffer.SetCell(ui.Cell{ui.TOP_LEFT, m.BorderStyle}, image.Pt(m.Min.X+2, m.Min.Y+1)) + buffer.SetCell(ui.Cell{ui.TOP_RIGHT, m.BorderStyle}, image.Pt(m.Max.X-3, m.Min.Y+1)) + buffer.SetCell(ui.Cell{ui.BOTTOM_LEFT, m.BorderStyle}, image.Pt(m.Min.X+2, m.Max.Y-2)) + buffer.SetCell(ui.Cell{ui.BOTTOM_RIGHT, m.BorderStyle}, image.Pt(m.Max.X-3, m.Max.Y-2)) +} + +func getMiddlePoint(block ui.Block, text string, offset int) image.Point { + return image.Pt(block.Min.X+block.Dx()/2-len(text)/2, block.Max.Y-block.Dy()/2+offset) +}