termbox-util/termbox_menu.go

533 lines
14 KiB
Go
Raw Normal View History

package termboxUtil
2016-02-25 03:33:33 +00:00
import "github.com/nsf/termbox-go"
// Menu is a menu with a list of options
type Menu struct {
2016-02-09 16:21:57 +00:00
id string
title string
options []MenuOption
// If height is -1, then it is adaptive to the menu
x, y, width, height int
showHelp bool
cursor int
bg, fg termbox.Attribute
selectedBg, selectedFg termbox.Attribute
disabledBg, disabledFg termbox.Attribute
2020-02-06 16:26:52 +00:00
selectedDisabledBg termbox.Attribute
selectedDisabledFg termbox.Attribute
2019-03-25 15:10:25 +00:00
activeFg, activeBg termbox.Attribute
isDone bool
bordered bool
vimMode bool
2016-02-09 16:21:57 +00:00
tabSkip bool
2019-03-25 15:10:25 +00:00
active bool
2020-02-06 16:26:52 +00:00
canSelectDisabled bool
}
// CreateMenu Creates a menu with the specified attributes
func CreateMenu(title string, options []string, x, y, width, height int, fg, bg termbox.Attribute) *Menu {
2019-03-25 15:10:25 +00:00
c := Menu{
title: title,
x: x, y: y, width: width, height: height,
fg: fg, bg: bg, selectedFg: bg, selectedBg: fg,
disabledFg: bg, disabledBg: bg,
2019-03-25 15:10:25 +00:00
activeFg: fg, activeBg: bg,
bordered: true,
2020-02-20 16:08:19 +00:00
tabSkip: false,
}
for _, line := range options {
2019-03-25 15:10:25 +00:00
c.options = append(c.options, MenuOption{text: line})
}
2019-03-25 15:10:25 +00:00
if len(c.options) > 0 {
c.SetSelectedOption(&c.options[0])
}
2019-03-25 15:10:25 +00:00
return &c
}
2019-03-25 15:10:25 +00:00
func (c *Menu) SetActiveFgColor(fg termbox.Attribute) { c.activeFg = fg }
func (c *Menu) SetActiveBgColor(bg termbox.Attribute) { c.activeBg = bg }
func (c *Menu) SetActive(a bool) { c.active = a }
func (c *Menu) IsActive() bool { return c.active }
2016-02-09 16:21:57 +00:00
// GetID returns this control's ID
2019-03-25 15:10:25 +00:00
func (c *Menu) GetID() string { return c.id }
2016-02-09 16:21:57 +00:00
// SetID sets this control's ID
2019-03-25 15:10:25 +00:00
func (c *Menu) SetID(newID string) { c.id = newID }
2020-02-20 16:08:19 +00:00
func (c *Menu) IsTabSkipped() bool { return c.tabSkip }
func (c *Menu) SetTabSkip(b bool) { c.tabSkip = b }
2016-02-09 16:21:57 +00:00
// GetTitle returns the current title of the menu
2019-03-25 15:10:25 +00:00
func (c *Menu) GetTitle() string { return c.title }
// SetTitle sets the current title of the menu to s
2019-03-25 15:10:25 +00:00
func (c *Menu) SetTitle(s string) {
c.title = s
}
// GetOptions returns the current options of the menu
2019-03-25 15:10:25 +00:00
func (c *Menu) GetOptions() []MenuOption {
return c.options
}
// SetOptions set the menu's options to opts
2019-03-25 15:10:25 +00:00
func (c *Menu) SetOptions(opts []MenuOption) {
c.options = opts
}
// SetOptionsFromStrings sets the options of this menu from a slice of strings
2019-03-25 15:10:25 +00:00
func (c *Menu) SetOptionsFromStrings(opts []string) {
var newOpts []MenuOption
for _, v := range opts {
newOpts = append(newOpts, *CreateOptionFromText(v))
}
2019-03-25 15:10:25 +00:00
c.SetOptions(newOpts)
c.SetSelectedOption(c.GetOptionFromIndex(0))
}
// GetX returns the current x coordinate of the menu
2019-03-25 15:10:25 +00:00
func (c *Menu) GetX() int { return c.x }
// SetX sets the current x coordinate of the menu to x
2019-03-25 15:10:25 +00:00
func (c *Menu) SetX(x int) {
c.x = x
}
// GetY returns the current y coordinate of the menu
2019-03-25 15:10:25 +00:00
func (c *Menu) GetY() int { return c.y }
// SetY sets the current y coordinate of the menu to y
2019-03-25 15:10:25 +00:00
func (c *Menu) SetY(y int) {
c.y = y
}
// GetWidth returns the current width of the menu
2019-03-25 15:10:25 +00:00
func (c *Menu) GetWidth() int { return c.width }
// SetWidth sets the current menu width to width
2019-03-25 15:10:25 +00:00
func (c *Menu) SetWidth(width int) {
c.width = width
}
// GetHeight returns the current height of the menu
2019-03-25 15:10:25 +00:00
func (c *Menu) GetHeight() int { return c.height }
// SetHeight set the height of the menu to height
2019-03-25 15:10:25 +00:00
func (c *Menu) SetHeight(height int) {
c.height = height
}
// GetSelectedOption returns the current selected option
2019-03-25 15:10:25 +00:00
func (c *Menu) GetSelectedOption() *MenuOption {
idx := c.GetSelectedIndex()
if idx != -1 {
2019-03-25 15:10:25 +00:00
return &c.options[idx]
}
return nil
}
// GetOptionFromIndex Returns the
2019-03-25 15:10:25 +00:00
func (c *Menu) GetOptionFromIndex(idx int) *MenuOption {
if idx >= 0 && idx < len(c.options) {
return &c.options[idx]
}
return nil
}
// GetOptionFromText Returns the first option with the text v
2019-03-25 15:10:25 +00:00
func (c *Menu) GetOptionFromText(v string) *MenuOption {
for idx := range c.options {
testOption := &c.options[idx]
if testOption.GetText() == v {
return testOption
}
}
return nil
}
// GetSelectedIndex returns the index of the selected option
// Returns -1 if nothing is selected
2019-03-25 15:10:25 +00:00
func (c *Menu) GetSelectedIndex() int {
for idx := range c.options {
if c.options[idx].IsSelected() {
return idx
}
}
return -1
}
2016-02-22 13:32:34 +00:00
// SetSelectedIndex sets the selection to setIdx
2019-03-25 15:10:25 +00:00
func (c *Menu) SetSelectedIndex(idx int) {
if len(c.options) > 0 {
if idx < 0 {
idx = 0
2019-03-25 15:10:25 +00:00
} else if idx >= len(c.options) {
idx = len(c.options) - 1
}
2019-03-25 15:10:25 +00:00
c.SetSelectedOption(&c.options[idx])
2016-02-22 13:32:34 +00:00
}
}
// SetSelectedOption sets the current selected option to v (if it's valid)
2019-03-25 15:10:25 +00:00
func (c *Menu) SetSelectedOption(v *MenuOption) {
for idx := range c.options {
if &c.options[idx] == v {
c.options[idx].Select()
} else {
2019-03-25 15:10:25 +00:00
c.options[idx].Unselect()
}
}
}
// SelectPrevOption Decrements the selected option (if it can)
2019-03-25 15:10:25 +00:00
func (c *Menu) SelectPrevOption() {
idx := c.GetSelectedIndex()
for idx >= 0 {
idx--
2019-03-25 15:10:25 +00:00
testOption := c.GetOptionFromIndex(idx)
2020-02-06 16:26:52 +00:00
if testOption != nil {
if c.canSelectDisabled || !testOption.IsDisabled() {
c.SetSelectedOption(testOption)
return
}
}
}
}
// SelectNextOption Increments the selected option (if it can)
2019-03-25 15:10:25 +00:00
func (c *Menu) SelectNextOption() {
idx := c.GetSelectedIndex()
for idx < len(c.options) {
idx++
2019-03-25 15:10:25 +00:00
testOption := c.GetOptionFromIndex(idx)
2020-02-06 16:26:52 +00:00
if testOption != nil {
if c.canSelectDisabled || !testOption.IsDisabled() {
c.SetSelectedOption(testOption)
return
}
}
}
}
2016-02-25 03:33:33 +00:00
// SelectPageUpOption Goes up 'menu height' options
2019-03-25 15:10:25 +00:00
func (c *Menu) SelectPageUpOption() {
idx := c.GetSelectedIndex()
idx -= c.height
2016-02-25 03:33:33 +00:00
if idx < 0 {
idx = 0
}
2019-03-25 15:10:25 +00:00
c.SetSelectedIndex(idx)
2016-02-25 03:33:33 +00:00
return
}
// SelectPageDownOption Goes down 'menu height' options
2019-03-25 15:10:25 +00:00
func (c *Menu) SelectPageDownOption() {
idx := c.GetSelectedIndex()
idx += c.height
if idx >= len(c.options) {
idx = len(c.options) - 1
2016-02-25 03:33:33 +00:00
}
2019-03-25 15:10:25 +00:00
c.SetSelectedIndex(idx)
2016-02-25 03:33:33 +00:00
return
}
// SelectFirstOption Goes to the top
2019-03-25 15:10:25 +00:00
func (c *Menu) SelectFirstOption() {
c.SetSelectedIndex(0)
2016-02-25 03:33:33 +00:00
return
}
// SelectLastOption Goes to the bottom
2019-03-25 15:10:25 +00:00
func (c *Menu) SelectLastOption() {
c.SetSelectedIndex(len(c.options) - 1)
2016-02-25 03:33:33 +00:00
return
}
// SetOptionDisabled Disables the specified option
2019-03-25 15:10:25 +00:00
func (c *Menu) SetOptionDisabled(idx int) {
if len(c.options) > idx {
c.GetOptionFromIndex(idx).Disable()
}
}
// SetOptionEnabled Enables the specified option
2019-03-25 15:10:25 +00:00
func (c *Menu) SetOptionEnabled(idx int) {
if len(c.options) > idx {
c.GetOptionFromIndex(idx).Enable()
}
}
// HelpIsShown returns true or false if the help is displayed
2019-03-25 15:10:25 +00:00
func (c *Menu) HelpIsShown() bool { return c.showHelp }
// ShowHelp sets whether or not to display the help text
2019-03-25 15:10:25 +00:00
func (c *Menu) ShowHelp(b bool) {
c.showHelp = b
}
2020-02-06 16:26:52 +00:00
func (c *Menu) GetFgColor() termbox.Attribute { return c.fg }
func (c *Menu) SetFgColor(fg termbox.Attribute) { c.fg = fg }
func (c *Menu) GetBgColor() termbox.Attribute { return c.bg }
func (c *Menu) SetBgColor(bg termbox.Attribute) { c.bg = bg }
2020-02-06 16:26:52 +00:00
func (c *Menu) GetSelectedFgColor() termbox.Attribute { return c.selectedFg }
func (c *Menu) SetSelectedFgColor(fg termbox.Attribute) { c.selectedFg = fg }
func (c *Menu) GetSelectedBgColor() termbox.Attribute { return c.selectedBg }
func (c *Menu) SetSelectedBgColor(bg termbox.Attribute) { c.selectedBg = bg }
2020-02-06 16:26:52 +00:00
func (c *Menu) GetSelectedDisabledFgColor() termbox.Attribute { return c.selectedDisabledFg }
func (c *Menu) SetSelectedDisabledFgColor(fg termbox.Attribute) { c.selectedDisabledFg = fg }
func (c *Menu) GetSelectedDisabledBgColor() termbox.Attribute { return c.selectedDisabledBg }
func (c *Menu) SetSelectedDisabledBgColor(bg termbox.Attribute) { c.selectedDisabledBg = bg }
2020-02-06 16:26:52 +00:00
func (c *Menu) GetDisabledFgColor() termbox.Attribute { return c.disabledFg }
func (c *Menu) SetDisabledFgColor(fg termbox.Attribute) { c.disabledFg = fg }
func (c *Menu) GetDisabledBgColor() termbox.Attribute { return c.disabledBg }
func (c *Menu) SetDisabledBgColor(bg termbox.Attribute) { c.disabledBg = bg }
// IsDone returns whether the user has answered the modal
2019-03-25 15:10:25 +00:00
func (c *Menu) IsDone() bool { return c.isDone }
// SetDone sets whether the modal has completed it's purpose
2019-03-25 15:10:25 +00:00
func (c *Menu) SetDone(b bool) {
c.isDone = b
}
// IsBordered returns true or false if this menu has a border
2019-03-25 15:10:25 +00:00
func (c *Menu) IsBordered() bool { return c.bordered }
// SetBordered sets whether we render a border around the menu
2019-03-25 15:10:25 +00:00
func (c *Menu) SetBordered(b bool) {
c.bordered = b
}
// EnableVimMode Enables h,j,k,l navigation
2019-03-25 15:10:25 +00:00
func (c *Menu) EnableVimMode() {
c.vimMode = true
}
// DisableVimMode Disables h,j,k,l navigation
2019-03-25 15:10:25 +00:00
func (c *Menu) DisableVimMode() {
c.vimMode = false
}
2020-02-06 16:26:52 +00:00
func (c *Menu) SetCanSelectDisabled(b bool) {
c.canSelectDisabled = b
}
2016-02-09 16:21:57 +00:00
// HandleEvent handles the termbox event and returns whether it was consumed
2019-03-25 15:10:25 +00:00
func (c *Menu) HandleEvent(event termbox.Event) bool {
if event.Key == termbox.KeyEnter || event.Key == termbox.KeySpace {
2019-03-25 15:10:25 +00:00
c.isDone = true
return true
}
2019-03-25 15:10:25 +00:00
currentIdx := c.GetSelectedIndex()
switch event.Key {
case termbox.KeyArrowUp:
2019-03-25 15:10:25 +00:00
c.SelectPrevOption()
case termbox.KeyArrowDown:
2019-03-25 15:10:25 +00:00
c.SelectNextOption()
2016-02-25 03:33:33 +00:00
case termbox.KeyArrowLeft:
2019-03-25 15:10:25 +00:00
c.SelectPageUpOption()
2016-02-25 03:33:33 +00:00
case termbox.KeyArrowRight:
2019-03-25 15:10:25 +00:00
c.SelectPageDownOption()
}
2019-03-25 15:10:25 +00:00
if c.vimMode {
switch event.Ch {
case 'j':
2019-03-25 15:10:25 +00:00
c.SelectNextOption()
case 'k':
2019-03-25 15:10:25 +00:00
c.SelectPrevOption()
2016-02-09 16:21:57 +00:00
}
2016-02-25 03:33:33 +00:00
if event.Key == termbox.KeyCtrlF {
2019-03-25 15:10:25 +00:00
c.SelectPageDownOption()
2016-02-25 03:33:33 +00:00
} else if event.Key == termbox.KeyCtrlB {
2019-03-25 15:10:25 +00:00
c.SelectPageUpOption()
2016-02-25 03:33:33 +00:00
}
}
2019-03-25 15:10:25 +00:00
if c.GetSelectedIndex() != currentIdx {
return true
}
return false
}
// Draw draws the modal
2019-03-25 15:10:25 +00:00
func (c *Menu) Draw() {
useFg, useBg := c.fg, c.bg
if c.active {
useFg, useBg = c.activeFg, c.activeBg
}
// First blank out the area we'll be putting the menu
2019-03-25 15:10:25 +00:00
FillWithChar(' ', c.x, c.y, c.x+c.width, c.y+c.height, useFg, useBg)
// Now draw the border
2019-03-25 15:10:25 +00:00
optionStartX := c.x
optionStartY := c.y
optionWidth := c.width
_ = optionWidth
optionHeight := c.height
if optionHeight == -1 {
2019-03-25 15:10:25 +00:00
optionHeight = len(c.options)
}
2019-03-25 15:10:25 +00:00
if c.bordered {
pct := float64(c.GetSelectedIndex()) / float64(len(c.options))
if c.title == "" {
if len(c.options) > c.height-2 {
DrawBorderWithPct(c.x, c.y, c.x+c.width, c.y+c.height, pct, useFg, useBg)
} else {
DrawBorder(c.x, c.y, c.x+c.width, c.y+c.height, useFg, useBg)
}
} else {
2019-03-25 15:10:25 +00:00
if len(c.options) > c.height-2 {
DrawBorderWithTitleAndPct(c.x, c.y, c.x+c.width, c.y+c.height, " "+c.title+" ", pct, useFg, useBg)
} else {
DrawBorderWithTitle(c.x, c.y, c.x+c.width, c.y+c.height, " "+c.title+" ", useFg, useBg)
}
}
2019-03-25 15:10:25 +00:00
optionStartX = c.x + 1
optionStartY = c.y + 1
optionWidth = c.width - 1
optionHeight -= 2
}
2019-03-25 15:10:25 +00:00
if len(c.options) > 0 {
firstDispIdx := 0
lastDispIdx := len(c.options) - 1
if len(c.options) > c.height-2 {
lastDispIdx = c.height - 2
}
2019-03-25 15:10:25 +00:00
if c.GetSelectedIndex() > c.height-2 {
firstDispIdx = c.GetSelectedIndex() - (c.height - 2)
lastDispIdx = c.GetSelectedIndex()
}
2020-02-06 16:26:52 +00:00
for idx := firstDispIdx; idx < lastDispIdx+1; idx++ {
2019-03-25 15:10:25 +00:00
currOpt := &c.options[idx]
outTxt := currOpt.GetText()
if currOpt.IsDisabled() {
2020-02-06 16:26:52 +00:00
if c.GetSelectedOption() == currOpt {
DrawStringAtPoint(outTxt, optionStartX, optionStartY, c.selectedDisabledFg, c.selectedDisabledBg)
} else {
DrawStringAtPoint(outTxt, optionStartX, optionStartY, c.disabledFg, c.disabledBg)
}
2019-03-25 15:10:25 +00:00
} else if c.GetSelectedOption() == currOpt {
DrawStringAtPoint(outTxt, optionStartX, optionStartY, c.selectedFg, c.selectedBg)
} else {
2019-03-25 15:10:25 +00:00
DrawStringAtPoint(outTxt, optionStartX, optionStartY, useFg, useBg)
}
optionStartY++
}
}
2019-03-25 15:10:25 +00:00
/*
// Print the options
bldHeight := (optionHeight / 2)
startIdx := c.GetSelectedIndex()
endIdx := c.GetSelectedIndex()
for bldHeight > 0 && startIdx >= 1 {
startIdx--
bldHeight--
}
bldHeight += (optionHeight / 2)
for bldHeight > 0 && endIdx < len(c.options) {
endIdx++
bldHeight--
}
for idx := startIdx; idx < endIdx; idx++ {
if c.GetSelectedIndex()-idx >= optionHeight-1 {
// Skip this one
continue
}
currOpt := &c.options[idx]
outTxt := currOpt.GetText()
if len(outTxt) >= c.width {
outTxt = outTxt[:c.width]
}
if currOpt.IsDisabled() {
DrawStringAtPoint(outTxt, optionStartX, optionStartY, c.disabledFg, c.disabledBg)
} else if c.GetSelectedOption() == currOpt {
DrawStringAtPoint(outTxt, optionStartX, optionStartY, c.selectedFg, c.selectedBg)
} else {
DrawStringAtPoint(outTxt, optionStartX, optionStartY, useFg, useBg)
}
optionStartY++
if optionStartY > c.y+optionHeight-1 {
break
}
}
}
*/
}
/* MenuOption Struct & methods */
// MenuOption An option in the menu
type MenuOption struct {
2016-02-09 16:21:57 +00:00
id string
text string
selected bool
disabled bool
helpText string
2016-02-09 16:21:57 +00:00
subMenu []MenuOption
}
// CreateOptionFromText just returns a MenuOption object
// That only has it's text value set.
func CreateOptionFromText(s string) *MenuOption {
return &MenuOption{text: s}
}
// SetText Sets the text for this option
2019-03-25 15:10:25 +00:00
func (c *MenuOption) SetText(s string) {
c.text = s
}
// GetText Returns the text for this option
2019-03-25 15:10:25 +00:00
func (c *MenuOption) GetText() string { return c.text }
// Disable Sets this option to disabled
2019-03-25 15:10:25 +00:00
func (c *MenuOption) Disable() {
c.disabled = true
}
// Enable Sets this option to enabled
2019-03-25 15:10:25 +00:00
func (c *MenuOption) Enable() {
c.disabled = false
}
// IsDisabled returns whether this option is enabled
2019-03-25 15:10:25 +00:00
func (c *MenuOption) IsDisabled() bool {
return c.disabled
}
// IsSelected Returns whether this option is selected
2019-03-25 15:10:25 +00:00
func (c *MenuOption) IsSelected() bool {
return c.selected
}
// Select Sets this option to selected
2019-03-25 15:10:25 +00:00
func (c *MenuOption) Select() {
c.selected = true
}
// Unselect Sets this option to not selected
2019-03-25 15:10:25 +00:00
func (c *MenuOption) Unselect() {
c.selected = false
}
// SetHelpText Sets this option's help text to s
2019-03-25 15:10:25 +00:00
func (c *MenuOption) SetHelpText(s string) {
c.helpText = s
}
// GetHelpText Returns the help text for this option
2019-03-25 15:10:25 +00:00
func (c *MenuOption) GetHelpText() string { return c.helpText }
2016-02-09 16:21:57 +00:00
// AddToSubMenu adds a slice of MenuOptions to this option
2019-03-25 15:10:25 +00:00
func (c *MenuOption) AddToSubMenu(sub *MenuOption) {
c.subMenu = append(c.subMenu, *sub)
2016-02-09 16:21:57 +00:00
}