2019-02-24 04:49:09 +00:00
|
|
|
package component
|
2019-02-11 02:51:55 +00:00
|
|
|
|
|
|
|
import (
|
2019-03-14 03:01:44 +00:00
|
|
|
ui "github.com/gizak/termui/v3"
|
2019-02-17 01:54:48 +00:00
|
|
|
"github.com/sqshq/sampler/config"
|
2019-02-11 02:51:55 +00:00
|
|
|
"github.com/sqshq/sampler/console"
|
|
|
|
"image"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Menu struct {
|
2019-03-24 00:16:59 +00:00
|
|
|
*ui.Block
|
2019-02-11 02:51:55 +00:00
|
|
|
options []MenuOption
|
|
|
|
component Component
|
|
|
|
mode MenuMode
|
|
|
|
option MenuOption
|
2019-03-24 00:16:59 +00:00
|
|
|
palette console.Palette
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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"
|
2019-02-11 04:06:48 +00:00
|
|
|
MenuOptionResume MenuOption = "RESUME"
|
2019-02-11 02:51:55 +00:00
|
|
|
)
|
|
|
|
|
2019-02-26 04:36:23 +00:00
|
|
|
const (
|
|
|
|
minimalMenuHeight = 8
|
|
|
|
)
|
|
|
|
|
2019-03-24 00:16:59 +00:00
|
|
|
func NewMenu(palette console.Palette) *Menu {
|
2019-02-11 02:51:55 +00:00
|
|
|
return &Menu{
|
2019-03-24 00:16:59 +00:00
|
|
|
Block: NewBlock("", true, palette),
|
2019-02-11 04:06:48 +00:00
|
|
|
options: []MenuOption{MenuOptionMove, MenuOptionResize, MenuOptionPinpoint, MenuOptionResume},
|
2019-02-11 02:51:55 +00:00
|
|
|
mode: MenuModeIdle,
|
|
|
|
option: MenuOptionMove,
|
2019-03-24 00:16:59 +00:00
|
|
|
palette: palette,
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-08 04:04:46 +00:00
|
|
|
func (m *Menu) GetSelectedOption() MenuOption {
|
2019-02-11 02:51:55 +00:00
|
|
|
return m.option
|
|
|
|
}
|
|
|
|
|
2019-03-08 04:04:46 +00:00
|
|
|
func (m *Menu) Highlight(component *Component) {
|
|
|
|
m.component = *component
|
2019-02-11 02:51:55 +00:00
|
|
|
m.updateDimensions()
|
|
|
|
m.mode = MenuModeHighlight
|
|
|
|
m.Title = component.Title
|
|
|
|
}
|
|
|
|
|
2019-03-08 04:04:46 +00:00
|
|
|
func (m *Menu) Choose() {
|
2019-02-11 02:51:55 +00:00
|
|
|
m.mode = MenuModeOptionSelect
|
|
|
|
}
|
|
|
|
|
2019-03-08 04:04:46 +00:00
|
|
|
func (m *Menu) Idle() {
|
2019-02-11 02:51:55 +00:00
|
|
|
m.mode = MenuModeIdle
|
|
|
|
}
|
|
|
|
|
2019-03-08 04:04:46 +00:00
|
|
|
func (m *Menu) Up() {
|
2019-02-11 02:51:55 +00:00
|
|
|
for i := 1; i < len(m.options); i++ {
|
|
|
|
if m.options[i] == m.option {
|
|
|
|
m.option = m.options[i-1]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-02-17 23:00:00 +00:00
|
|
|
if m.option == MenuOptionPinpoint && m.component.Type != config.TypeRunChart {
|
2019-03-08 04:04:46 +00:00
|
|
|
m.Up()
|
2019-02-17 23:00:00 +00:00
|
|
|
}
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
|
|
|
|
2019-03-08 04:04:46 +00:00
|
|
|
func (m *Menu) Down() {
|
2019-02-11 02:51:55 +00:00
|
|
|
for i := 0; i < len(m.options)-1; i++ {
|
|
|
|
if m.options[i] == m.option {
|
|
|
|
m.option = m.options[i+1]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-02-17 23:00:00 +00:00
|
|
|
if m.option == MenuOptionPinpoint && m.component.Type != config.TypeRunChart {
|
2019-03-08 04:04:46 +00:00
|
|
|
m.Down()
|
2019-02-17 23:00:00 +00:00
|
|
|
}
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
|
|
|
|
2019-03-08 04:04:46 +00:00
|
|
|
func (m *Menu) MoveOrResize() {
|
2019-02-11 02:51:55 +00:00
|
|
|
m.mode = MenuModeMoveAndResize
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Menu) Draw(buffer *ui.Buffer) {
|
|
|
|
|
|
|
|
if m.mode == MenuModeIdle {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m.updateDimensions()
|
2019-03-24 00:16:59 +00:00
|
|
|
buffer.Fill(ui.NewCell(' ', ui.NewStyle(m.palette.ReverseColor)), m.GetRect())
|
2019-02-26 04:36:23 +00:00
|
|
|
|
|
|
|
if m.Dy() > minimalMenuHeight {
|
|
|
|
m.drawInnerBorder(buffer)
|
|
|
|
}
|
|
|
|
|
|
|
|
m.Block.Draw(buffer)
|
2019-02-11 02:51:55 +00:00
|
|
|
|
|
|
|
switch m.mode {
|
|
|
|
case MenuModeHighlight:
|
|
|
|
m.renderHighlight(buffer)
|
|
|
|
case MenuModeMoveAndResize:
|
|
|
|
m.renderMoveAndResize(buffer)
|
|
|
|
case MenuModeOptionSelect:
|
|
|
|
m.renderOptions(buffer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Menu) renderHighlight(buffer *ui.Buffer) {
|
|
|
|
|
2019-02-26 04:36:23 +00:00
|
|
|
arrowsText := "Use arrows for selection"
|
|
|
|
optionsText := "<ENTER> to view options"
|
|
|
|
resumeText := "<ESC> to resume"
|
|
|
|
|
|
|
|
if m.Dy() <= minimalMenuHeight {
|
|
|
|
buffer.SetString(
|
|
|
|
optionsText,
|
|
|
|
ui.NewStyle(console.ColorDarkGrey),
|
2019-03-11 03:43:47 +00:00
|
|
|
getMiddlePoint(m.Block.Rectangle, optionsText, -1),
|
2019-02-26 04:36:23 +00:00
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-02-11 02:51:55 +00:00
|
|
|
m.printAllDirectionsArrowSign(buffer, -2)
|
|
|
|
|
2019-03-11 03:43:47 +00:00
|
|
|
arrowsTextPoint := getMiddlePoint(m.Block.Rectangle, arrowsText, 2)
|
2019-02-25 00:08:36 +00:00
|
|
|
if arrowsTextPoint.Y+1 < m.Inner.Max.Y {
|
2019-02-11 03:26:51 +00:00
|
|
|
buffer.SetString(
|
|
|
|
arrowsText,
|
|
|
|
ui.NewStyle(console.ColorDarkGrey),
|
|
|
|
arrowsTextPoint,
|
|
|
|
)
|
|
|
|
}
|
2019-02-11 02:51:55 +00:00
|
|
|
|
2019-03-11 03:43:47 +00:00
|
|
|
optionsTextPoint := getMiddlePoint(m.Block.Rectangle, optionsText, 3)
|
2019-02-25 00:08:36 +00:00
|
|
|
if optionsTextPoint.Y+1 < m.Inner.Max.Y {
|
2019-02-11 03:26:51 +00:00
|
|
|
buffer.SetString(
|
|
|
|
optionsText,
|
|
|
|
ui.NewStyle(console.ColorDarkGrey),
|
2019-03-11 03:43:47 +00:00
|
|
|
getMiddlePoint(m.Block.Rectangle, optionsText, 3),
|
2019-02-11 03:26:51 +00:00
|
|
|
)
|
|
|
|
}
|
2019-02-11 02:51:55 +00:00
|
|
|
|
2019-03-11 03:43:47 +00:00
|
|
|
resumeTextPoint := getMiddlePoint(m.Block.Rectangle, resumeText, 4)
|
2019-02-25 00:08:36 +00:00
|
|
|
if resumeTextPoint.Y+1 < m.Inner.Max.Y {
|
2019-02-11 03:26:51 +00:00
|
|
|
buffer.SetString(
|
|
|
|
resumeText,
|
|
|
|
ui.NewStyle(console.ColorDarkGrey),
|
|
|
|
resumeTextPoint,
|
|
|
|
)
|
|
|
|
}
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Menu) renderMoveAndResize(buffer *ui.Buffer) {
|
|
|
|
|
|
|
|
saveText := "<ENTER> to save changes"
|
2019-02-26 04:36:23 +00:00
|
|
|
|
|
|
|
if m.Dy() <= minimalMenuHeight {
|
2019-03-11 03:43:47 +00:00
|
|
|
buffer.SetString(saveText, ui.NewStyle(console.ColorDarkGrey), getMiddlePoint(m.Block.Rectangle, saveText, -1))
|
2019-02-26 04:36:23 +00:00
|
|
|
return
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
2019-02-26 04:36:23 +00:00
|
|
|
|
|
|
|
m.printAllDirectionsArrowSign(buffer, -1)
|
2019-03-11 03:43:47 +00:00
|
|
|
buffer.SetString(saveText, ui.NewStyle(console.ColorDarkGrey), getMiddlePoint(m.Block.Rectangle, saveText, 3))
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Menu) printAllDirectionsArrowSign(buffer *ui.Buffer, y int) {
|
|
|
|
|
|
|
|
arrows := []string{
|
|
|
|
" ↑ ",
|
2019-04-13 16:39:09 +00:00
|
|
|
"← →",
|
2019-02-11 02:51:55 +00:00
|
|
|
" ↓ ",
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, a := range arrows {
|
2019-03-14 04:28:26 +00:00
|
|
|
printString(
|
2019-02-11 02:51:55 +00:00
|
|
|
a,
|
|
|
|
ui.NewStyle(console.ColorOlive),
|
2019-03-11 03:43:47 +00:00
|
|
|
getMiddlePoint(m.Block.Rectangle, a, i+y),
|
2019-03-14 04:28:26 +00:00
|
|
|
buffer,
|
2019-02-11 02:51:55 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Menu) renderOptions(buffer *ui.Buffer) {
|
|
|
|
|
2019-03-26 03:29:23 +00:00
|
|
|
highlightedStyle := ui.NewStyle(m.palette.ReverseColor, console.ColorOlive)
|
2019-03-24 00:16:59 +00:00
|
|
|
regularStyle := ui.NewStyle(m.palette.BaseColor, m.palette.ReverseColor)
|
2019-02-11 02:51:55 +00:00
|
|
|
|
|
|
|
offset := 1
|
|
|
|
for _, option := range m.options {
|
|
|
|
|
|
|
|
style := regularStyle
|
|
|
|
if m.option == option {
|
|
|
|
style = highlightedStyle
|
|
|
|
}
|
|
|
|
|
2019-02-17 01:54:48 +00:00
|
|
|
if option != MenuOptionPinpoint || m.component.Type == config.TypeRunChart {
|
2019-02-11 02:51:55 +00:00
|
|
|
offset += 2
|
2019-03-24 00:16:59 +00:00
|
|
|
point := getMiddlePoint(m.Block.Rectangle, string(option), offset-6)
|
2019-02-26 04:36:23 +00:00
|
|
|
buffer.SetString(string(option), style, point)
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Menu) updateDimensions() {
|
2019-03-16 23:59:28 +00:00
|
|
|
r := m.component.GetRect()
|
2019-02-11 02:51:55 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2019-03-11 03:43:47 +00:00
|
|
|
// TODO move to utils
|
|
|
|
func getMiddlePoint(rectangle image.Rectangle, text string, offset int) image.Point {
|
|
|
|
return image.Pt(rectangle.Min.X+rectangle.Dx()/2-len(text)/2, rectangle.Max.Y-rectangle.Dy()/2+offset)
|
2019-02-11 02:51:55 +00:00
|
|
|
}
|
2019-03-14 04:28:26 +00:00
|
|
|
|
|
|
|
// TODO move to utils
|
|
|
|
func printString(s string, style ui.Style, p image.Point, buffer *ui.Buffer) {
|
|
|
|
for i, char := range s {
|
|
|
|
buffer.SetCell(ui.Cell{Rune: char, Style: style}, image.Pt(p.X+i, p.Y))
|
|
|
|
}
|
|
|
|
}
|