sampler-fork/component/menu.go

249 lines
5.7 KiB
Go

package component
import (
"github.com/sqshq/sampler/config"
"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"
MenuOptionResume MenuOption = "RESUME"
)
const (
minimalMenuHeight = 8
)
func NewMenu() *Menu {
block := *ui.NewBlock()
block.Border = true
return &Menu{
Block: block,
options: []MenuOption{MenuOptionMove, MenuOptionResize, MenuOptionPinpoint, MenuOptionResume},
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
}
}
if m.option == MenuOptionPinpoint && m.component.Type != config.TypeRunChart {
m.Up()
}
}
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
}
}
if m.option == MenuOptionPinpoint && m.component.Type != config.TypeRunChart {
m.Down()
}
}
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.ColorBlack)), m.GetRect())
if m.Dy() > minimalMenuHeight {
m.drawInnerBorder(buffer)
}
m.Block.Draw(buffer)
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) {
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),
getMiddlePoint(m.Block, optionsText, -1),
)
return
}
m.printAllDirectionsArrowSign(buffer, -2)
arrowsTextPoint := getMiddlePoint(m.Block, arrowsText, 2)
if arrowsTextPoint.Y+1 < m.Inner.Max.Y {
buffer.SetString(
arrowsText,
ui.NewStyle(console.ColorDarkGrey),
arrowsTextPoint,
)
}
optionsTextPoint := getMiddlePoint(m.Block, optionsText, 3)
if optionsTextPoint.Y+1 < m.Inner.Max.Y {
buffer.SetString(
optionsText,
ui.NewStyle(console.ColorDarkGrey),
getMiddlePoint(m.Block, optionsText, 3),
)
}
resumeTextPoint := getMiddlePoint(m.Block, resumeText, 4)
if resumeTextPoint.Y+1 < m.Inner.Max.Y {
buffer.SetString(
resumeText,
ui.NewStyle(console.ColorDarkGrey),
resumeTextPoint,
)
}
}
func (m *Menu) renderMoveAndResize(buffer *ui.Buffer) {
saveText := "<ENTER> to save changes"
if m.Dy() <= minimalMenuHeight {
buffer.SetString(saveText, ui.NewStyle(console.ColorDarkGrey), getMiddlePoint(m.Block, saveText, -1))
return
}
m.printAllDirectionsArrowSign(buffer, -1)
buffer.SetString(saveText, ui.NewStyle(console.ColorDarkGrey), getMiddlePoint(m.Block, saveText, 3))
}
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.ColorOlive, console.ColorBlack, ui.ModifierReverse)
regularStyle := ui.NewStyle(console.ColorWhite, console.ColorBlack)
offset := 1
for _, option := range m.options {
style := regularStyle
if m.option == option {
style = highlightedStyle
}
if option != MenuOptionPinpoint || m.component.Type == config.TypeRunChart {
offset += 2
point := getMiddlePoint(m.Block, string(option), offset-5)
buffer.SetString(string(option), style, point)
//if point.In(m.GetRect()) {
// buffer.SetString(string(option), style, point)
//}
}
}
}
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)
}