diff --git a/termbox_asciiart.go b/termbox_asciiart.go index d86855b..4f91a40 100644 --- a/termbox_asciiart.go +++ b/termbox_asciiart.go @@ -1,6 +1,8 @@ package termboxUtil import ( + "strings" + "github.com/nsf/termbox-go" ) @@ -21,18 +23,16 @@ func CreateASCIIArt(c []string, x, y int, fg, bg termbox.Attribute) *ASCIIArt { func (i *ASCIIArt) GetX() int { return i.x } // SetX set the x position of the modal to x -func (i *ASCIIArt) SetX(x int) *ASCIIArt { +func (i *ASCIIArt) SetX(x int) { i.x = x - return i } // GetY Return the y position of the modal func (i *ASCIIArt) GetY() int { return i.y } // SetY Set the y position of the modal to y -func (i *ASCIIArt) SetY(y int) *ASCIIArt { +func (i *ASCIIArt) SetY(y int) { i.y = y - return i } // GetHeight Returns the number of strings in the contents slice @@ -40,10 +40,45 @@ func (i *ASCIIArt) GetHeight() int { return len(i.contents) } +// SetHeight truncates lines from the bottom of the ascii art +func (i *ASCIIArt) SetHeight(h int) { + if len(i.contents) > h { + i.contents = i.contents[:h] + } else { + for j := len(i.contents); j < h; j++ { + i.contents = append(i.contents, "") + } + } +} + +// GetWidth Returns the number of strings in the contents slice +func (i *ASCIIArt) GetWidth() int { + // Find the longest line + var ret int + for j := range i.contents { + if len(i.contents[j]) > ret { + ret = len(i.contents[j]) + } + } + return ret +} + +// SetWidth Sets all lines in the contents to width w +func (i *ASCIIArt) SetWidth(w int) { + // Find the longest line + for j := range i.contents { + mkUp := w - len(i.contents[j]) + if mkUp > 0 { + i.contents[j] = i.contents[j] + strings.Repeat(" ", mkUp) + } else { + i.contents[j] = i.contents[j][:w] + } + } +} + // SetContents Sets the contents of i to c -func (i *ASCIIArt) SetContents(c []string) *ASCIIArt { +func (i *ASCIIArt) SetContents(c []string) { i.contents = c - return i } // GetContents returns the ascii art @@ -52,33 +87,30 @@ func (i *ASCIIArt) GetContents() []string { } // SetContentLine Sets a specific line of the contents to s -func (i *ASCIIArt) SetContentLine(s string, idx int) *ASCIIArt { +func (i *ASCIIArt) SetContentLine(s string, idx int) { if idx >= 0 && idx < len(i.contents) { i.contents[idx] = s } - return i } // GetBackground Return the current background color of the modal func (i *ASCIIArt) GetBackground() termbox.Attribute { return i.bg } // SetBackground Set the current background color to bg -func (i *ASCIIArt) SetBackground(bg termbox.Attribute) *ASCIIArt { +func (i *ASCIIArt) SetBackground(bg termbox.Attribute) { i.bg = bg - return i } // GetForeground Return the current foreground color func (i *ASCIIArt) GetForeground() termbox.Attribute { return i.fg } // SetForeground Set the foreground color to fg -func (i *ASCIIArt) SetForeground(fg termbox.Attribute) *ASCIIArt { +func (i *ASCIIArt) SetForeground(fg termbox.Attribute) { i.fg = fg - return i } // Align Align the Ascii art over width width with alignment a -func (i *ASCIIArt) Align(a TextAlignment, width int) *ASCIIArt { +func (i *ASCIIArt) Align(a TextAlignment, width int) { // First get the width of the longest string in the slice var newContents []string incomingLength := 0 @@ -91,7 +123,6 @@ func (i *ASCIIArt) Align(a TextAlignment, width int) *ASCIIArt { newContents = append(newContents, AlignText(AlignText(line, incomingLength, AlignLeft), width, a)) } i.contents = newContents - return i } // HandleKeyPress accepts the termbox event and returns whether it was consumed diff --git a/termbox_confirmmodal.go b/termbox_confirmmodal.go index 2b40187..59d419c 100644 --- a/termbox_confirmmodal.go +++ b/termbox_confirmmodal.go @@ -32,114 +32,101 @@ func CreateConfirmModal(title string, x, y, width, height int, fg, bg termbox.At func (i *ConfirmModal) GetTitle() string { return i.title } // SetTitle sets the current title of the modal to s -func (i *ConfirmModal) SetTitle(s string) *ConfirmModal { +func (i *ConfirmModal) SetTitle(s string) { i.title = s - return i } // GetText returns the current text of the modal func (i *ConfirmModal) GetText() string { return i.text } // SetText sets the text of the modal to s -func (i *ConfirmModal) SetText(s string) *ConfirmModal { +func (i *ConfirmModal) SetText(s string) { i.text = s - return i } // GetX returns the current x coordinate of the modal func (i *ConfirmModal) GetX() int { return i.x } // SetX sets the current x coordinate of the modal to x -func (i *ConfirmModal) SetX(x int) *ConfirmModal { +func (i *ConfirmModal) SetX(x int) { i.x = x - return i } // GetY returns the current y coordinate of the modal func (i *ConfirmModal) GetY() int { return i.y } // SetY sets the current y coordinate of the modal to y -func (i *ConfirmModal) SetY(y int) *ConfirmModal { +func (i *ConfirmModal) SetY(y int) { i.y = y - return i } // GetWidth returns the current width of the modal func (i *ConfirmModal) GetWidth() int { return i.width } // SetWidth sets the current modal width to width -func (i *ConfirmModal) SetWidth(width int) *ConfirmModal { +func (i *ConfirmModal) SetWidth(width int) { i.width = width - return i } // GetHeight returns the current height of the modal func (i *ConfirmModal) GetHeight() int { return i.height } // SetHeight set the height of the modal to height -func (i *ConfirmModal) SetHeight(height int) *ConfirmModal { +func (i *ConfirmModal) SetHeight(height int) { i.height = height - return i } // HelpIsShown returns true or false if the help is displayed func (i *ConfirmModal) HelpIsShown() bool { return i.showHelp } // ShowHelp sets whether or not to display the help text -func (i *ConfirmModal) ShowHelp(b bool) *ConfirmModal { +func (i *ConfirmModal) ShowHelp(b bool) { i.showHelp = b - return i } // GetBackground returns the current background color func (i *ConfirmModal) GetBackground() termbox.Attribute { return i.bg } // SetBackground sets the background color to bg -func (i *ConfirmModal) SetBackground(bg termbox.Attribute) *ConfirmModal { +func (i *ConfirmModal) SetBackground(bg termbox.Attribute) { i.bg = bg - return i } // GetForeground returns the current foreground color func (i *ConfirmModal) GetForeground() termbox.Attribute { return i.fg } // SetForeground sets the current foreground color to fg -func (i *ConfirmModal) SetForeground(fg termbox.Attribute) *ConfirmModal { +func (i *ConfirmModal) SetForeground(fg termbox.Attribute) { i.fg = fg - return i } // IsDone returns whether the user has answered the modal func (i *ConfirmModal) IsDone() bool { return i.isDone } // SetDone sets whether the modal has completed it's purpose -func (i *ConfirmModal) SetDone(b bool) *ConfirmModal { +func (i *ConfirmModal) SetDone(b bool) { i.isDone = b - return i } // Show sets the visibility flag of the modal to true -func (i *ConfirmModal) Show() *ConfirmModal { +func (i *ConfirmModal) Show() { i.isVisible = true - return i } // Hide sets the visibility flag of the modal to false -func (i *ConfirmModal) Hide() *ConfirmModal { +func (i *ConfirmModal) Hide() { i.isVisible = false - return i } // IsAccepted returns whether the user accepted the modal func (i *ConfirmModal) IsAccepted() bool { return i.accepted } // Clear clears all of the non-positional parameters of the modal -func (i *ConfirmModal) Clear() *ConfirmModal { +func (i *ConfirmModal) Clear() { i.title = "" i.text = "" i.accepted = false i.isDone = false - return i } // HandleKeyPress handles the termbox event and returns whether it was consumed diff --git a/termbox_inputfield.go b/termbox_inputfield.go index f597ef8..0f8d817 100644 --- a/termbox_inputfield.go +++ b/termbox_inputfield.go @@ -1,10 +1,6 @@ package termboxUtil -import ( - "fmt" - - "github.com/nsf/termbox-go" -) +import "github.com/nsf/termbox-go" // InputField is a field for inputting text type InputField struct { @@ -13,6 +9,8 @@ type InputField struct { cursor int fg, bg termbox.Attribute bordered bool + wrap bool + multiline bool } // CreateInputField creates an input field at x, y that is w by h @@ -25,61 +23,69 @@ func CreateInputField(x, y, w, h int, fg, bg termbox.Attribute) *InputField { func (i *InputField) GetValue() string { return i.value } // SetValue sets the current text in the InputField to s -func (i *InputField) SetValue(s string) *InputField { +func (i *InputField) SetValue(s string) { i.value = s - return i } // GetX returns the x position of the input field func (i *InputField) GetX() int { return i.x } // SetX sets the x position of the input field -func (i *InputField) SetX(x int) *InputField { +func (i *InputField) SetX(x int) { i.x = x - return i } // GetY returns the y position of the input field func (i *InputField) GetY() int { return i.y } // SetY sets the y position of the input field -func (i *InputField) SetY(y int) *InputField { +func (i *InputField) SetY(y int) { i.y = y - return i } // GetWidth returns the current width of the input field func (i *InputField) GetWidth() int { return i.width } // SetWidth sets the current width of the input field -func (i *InputField) SetWidth(w int) *InputField { +func (i *InputField) SetWidth(w int) { i.width = w - return i } // GetHeight returns the current height of the input field func (i *InputField) GetHeight() int { return i.height } // SetHeight sets the current height of the input field -func (i *InputField) SetHeight(h int) *InputField { +func (i *InputField) SetHeight(h int) { i.height = h - return i } // IsBordered returns true or false if this input field has a border func (i *InputField) IsBordered() bool { return i.bordered } // SetBordered sets whether we render a border around the input field -func (i *InputField) SetBordered(b bool) *InputField { +func (i *InputField) SetBordered(b bool) { i.bordered = b - return i +} + +// DoesWrap returns true or false if this input field wraps text +func (i *InputField) DoesWrap() bool { return i.wrap } + +// SetWrap sets whether we wrap the text at width. +func (i *InputField) SetWrap(b bool) { + i.wrap = b +} + +// IsMultiline returns true or false if this field can have multiple lines +func (i *InputField) IsMultiline() bool { return i.multiline } + +// SetMultiline sets whether the field can have multiple lines +func (i *InputField) SetMultiline(b bool) { + i.multiline = b } // HandleKeyPress accepts the termbox event and returns whether it was consumed func (i *InputField) HandleKeyPress(event termbox.Event) bool { - if event.Key == termbox.KeyEnter { - // Done editing - } else if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 { + if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 { if len(i.value) > 0 { i.value = i.value[:len(i.value)-1] } @@ -92,8 +98,8 @@ func (i *InputField) HandleKeyPress(event termbox.Event) bool { i.cursor++ } } else if event.Key == termbox.KeyCtrlU { - // Ctrl+U Clears the Input - i.value = "" + // Ctrl+U Clears the Input (before the cursor) + i.value = i.value[i.cursor:] } else { // Get the rune to add to our value. Space and Tab are special cases where // we can't use the event's rune directly @@ -103,18 +109,26 @@ func (i *InputField) HandleKeyPress(event termbox.Event) bool { ch = " " case termbox.KeyTab: ch = "\t" + /* Multiline is disabled right now + case termbox.KeyEnter: + if i.multiline { + ch = "\n" + } + */ default: - ch = string(event.Ch) + if KeyIsAlphaNumeric(event) || KeyIsSymbol(event) { + ch = string(event.Ch) + } } if i.cursor+len(i.value) == 0 { - i.value = fmt.Sprintf("%s%s", ch, i.value) + i.value = string(ch) + i.value } else if i.cursor == 0 { - i.value = fmt.Sprintf("%s%s", i.value, ch) + i.value = i.value + string(ch) } else { strPt1 := i.value[:(len(i.value) + i.cursor)] strPt2 := i.value[(len(i.value) + i.cursor):] - i.value = fmt.Sprintf("%s%s%s", strPt1, ch, strPt2) + i.value = strPt1 + string(ch) + strPt2 } } return true @@ -122,8 +136,19 @@ func (i *InputField) HandleKeyPress(event termbox.Event) bool { // Draw outputs the input field on the screen func (i *InputField) Draw() { + maxWidth := i.width + maxHeight := i.height + x, y := i.x, i.y + startX := i.x + startY := i.y if i.bordered { DrawBorder(i.x, i.y, i.x+i.width, i.y+i.height, i.fg, i.bg) + maxWidth-- + maxHeight-- + x++ + y++ + startX++ + startY++ } var strPt1, strPt2 string @@ -145,7 +170,56 @@ func (i *InputField) Draw() { } else { strPt1, strPt2, cursorRune = "", "", ' ' } - x, y := DrawStringAtPoint(strPt1, i.x+1, i.y+1, i.fg, i.bg) - termbox.SetCell(x, y, cursorRune, i.bg, i.fg) - DrawStringAtPoint(strPt2, x+1, y, i.fg, i.bg) + // Original: + /* + x, y = DrawStringAtPoint(strPt1, i.x+1, i.y+1, i.fg, i.bg) + termbox.SetCell(x, y, cursorRune, i.bg, i.fg) + DrawStringAtPoint(strPt2, x+1, y, i.fg, i.bg) + */ + if i.wrap { + // Split the text into maxWidth chunks + for len(strPt1) > maxWidth { + breakAt := maxWidth + DrawStringAtPoint(strPt1[:breakAt], x, y, i.fg, i.bg) + x = startX + y++ + strPt1 = strPt1[breakAt:] + } + x, y = DrawStringAtPoint(strPt1, x, y, i.fg, i.bg) + if x >= maxWidth { + y++ + x = startX + } + termbox.SetCell(x, y, cursorRune, i.bg, i.fg) + x++ + if len(strPt2) > 0 { + lenLeft := maxWidth - len(strPt1) - 1 + if lenLeft > 0 && len(strPt2) > lenLeft { + DrawStringAtPoint(strPt2[:lenLeft], x+1, y, i.fg, i.bg) + strPt2 = strPt2[lenLeft:] + } + for len(strPt2) > maxWidth { + breakAt := maxWidth + DrawStringAtPoint(strPt2[:breakAt], x, y, i.fg, i.bg) + x = startX + y++ + strPt2 = strPt2[breakAt:] + } + x, y = DrawStringAtPoint(strPt2, x, y, i.fg, i.bg) + } + } else { + for len(strPt1)+len(strPt2)+1 > maxWidth { + if len(strPt1) >= len(strPt2) { + if len(strPt1) == 0 { + break + } + strPt1 = strPt1[1:] + } else { + strPt2 = strPt2[:len(strPt2)-1] + } + } + x, y = DrawStringAtPoint(strPt1, i.x+1, i.y+1, i.fg, i.bg) + termbox.SetCell(x, y, cursorRune, i.bg, i.fg) + DrawStringAtPoint(strPt2, x+1, y, i.fg, i.bg) + } } diff --git a/termbox_inputmodal.go b/termbox_inputmodal.go index 2a0ac23..50ca646 100644 --- a/termbox_inputmodal.go +++ b/termbox_inputmodal.go @@ -30,99 +30,87 @@ func CreateInputModal(title string, x, y, width, height int, fg, bg termbox.Attr func (i *InputModal) GetTitle() string { return i.title } // SetTitle Sets the title of the modal to s -func (i *InputModal) SetTitle(s string) *InputModal { +func (i *InputModal) SetTitle(s string) { i.title = s - return i } // GetText Return the text of the modal func (i *InputModal) GetText() string { return i.text } // SetText Set the text of the modal to s -func (i *InputModal) SetText(s string) *InputModal { +func (i *InputModal) SetText(s string) { i.text = s - return i } // GetX Return the x position of the modal func (i *InputModal) GetX() int { return i.x } // SetX set the x position of the modal to x -func (i *InputModal) SetX(x int) *InputModal { +func (i *InputModal) SetX(x int) { i.x = x - return i } // GetY Return the y position of the modal func (i *InputModal) GetY() int { return i.y } // SetY Set the y position of the modal to y -func (i *InputModal) SetY(y int) *InputModal { +func (i *InputModal) SetY(y int) { i.y = y - return i } // GetWidth Return the width of the modal func (i *InputModal) GetWidth() int { return i.width } // SetWidth Set the width of the modal to width -func (i *InputModal) SetWidth(width int) *InputModal { +func (i *InputModal) SetWidth(width int) { i.width = width - return i } // GetHeight Return the height of the modal func (i *InputModal) GetHeight() int { return i.height } // SetHeight Set the height of the modal to height -func (i *InputModal) SetHeight(height int) *InputModal { +func (i *InputModal) SetHeight(height int) { i.height = height - return i } // HelpIsShown Returns whether the modal is showing it's help text or not func (i *InputModal) HelpIsShown() bool { return i.showHelp } // ShowHelp Set the "Show Help" flag -func (i *InputModal) ShowHelp(b bool) *InputModal { +func (i *InputModal) ShowHelp(b bool) { i.showHelp = b - return i } // GetBackground Return the current background color of the modal func (i *InputModal) GetBackground() termbox.Attribute { return i.bg } // SetBackground Set the current background color to bg -func (i *InputModal) SetBackground(bg termbox.Attribute) *InputModal { +func (i *InputModal) SetBackground(bg termbox.Attribute) { i.bg = bg - return i } // GetForeground Return the current foreground color func (i *InputModal) GetForeground() termbox.Attribute { return i.fg } // SetForeground Set the foreground color to fg -func (i *InputModal) SetForeground(fg termbox.Attribute) *InputModal { +func (i *InputModal) SetForeground(fg termbox.Attribute) { i.fg = fg - return i } // Show Sets the visibility flag to true -func (i *InputModal) Show() *InputModal { +func (i *InputModal) Show() { i.isVisible = true - return i } // Hide Sets the visibility flag to false -func (i *InputModal) Hide() *InputModal { +func (i *InputModal) Hide() { i.isVisible = false - return i } // SetDone Sets the flag that tells whether this modal has completed it's purpose -func (i *InputModal) SetDone(b bool) *InputModal { +func (i *InputModal) SetDone(b bool) { i.isDone = b - return i } // IsDone Returns the "isDone" flag @@ -134,19 +122,22 @@ func (i *InputModal) IsDone() bool { func (i *InputModal) GetValue() string { return i.input.GetValue() } // SetValue Sets the value of the input to s -func (i *InputModal) SetValue(s string) *InputModal { +func (i *InputModal) SetValue(s string) { i.input.SetValue(s) - return i +} + +// SetInputWrap sets whether the input field will wrap long text or not +func (i *InputModal) SetInputWrap(b bool) { + i.input.SetWrap(b) } // Clear Resets all non-positional parameters of the modal -func (i *InputModal) Clear() *InputModal { +func (i *InputModal) Clear() { i.title = "" i.text = "" i.input.SetValue("") i.isDone = false i.isVisible = false - return i } // HandleKeyPress Handle the termbox event, return true if it was consumed diff --git a/termbox_menu.go b/termbox_menu.go index 44e7485..c9a5ad2 100644 --- a/termbox_menu.go +++ b/termbox_menu.go @@ -15,7 +15,7 @@ type Menu struct { disabledBg, disabledFg termbox.Attribute isDone bool bordered bool - vimMode bool + vimMode bool } // CreateMenu Creates a menu with the specified attributes @@ -39,9 +39,8 @@ func CreateMenu(title string, options []string, x, y, width, height int, fg, bg func (i *Menu) GetTitle() string { return i.title } // SetTitle sets the current title of the menu to s -func (i *Menu) SetTitle(s string) *Menu { +func (i *Menu) SetTitle(s string) { i.title = s - return i } // GetOptions returns the current options of the menu @@ -50,54 +49,49 @@ func (i *Menu) GetOptions() []MenuOption { } // SetOptions set the menu's options to opts -func (i *Menu) SetOptions(opts []MenuOption) *Menu { +func (i *Menu) SetOptions(opts []MenuOption) { i.options = opts - return i } // SetOptionsFromStrings sets the options of this menu from a slice of strings -func (i *Menu) SetOptionsFromStrings(opts []string) *Menu { +func (i *Menu) SetOptionsFromStrings(opts []string) { var newOpts []MenuOption for _, v := range opts { newOpts = append(newOpts, *CreateOptionFromText(v)) } - return i.SetOptions(newOpts) + i.SetOptions(newOpts) } // GetX returns the current x coordinate of the menu func (i *Menu) GetX() int { return i.x } // SetX sets the current x coordinate of the menu to x -func (i *Menu) SetX(x int) *Menu { +func (i *Menu) SetX(x int) { i.x = x - return i } // GetY returns the current y coordinate of the menu func (i *Menu) GetY() int { return i.y } // SetY sets the current y coordinate of the menu to y -func (i *Menu) SetY(y int) *Menu { +func (i *Menu) SetY(y int) { i.y = y - return i } // GetWidth returns the current width of the menu func (i *Menu) GetWidth() int { return i.width } // SetWidth sets the current menu width to width -func (i *Menu) SetWidth(width int) *Menu { +func (i *Menu) SetWidth(width int) { i.width = width - return i } // GetHeight returns the current height of the menu func (i *Menu) GetHeight() int { return i.height } // SetHeight set the height of the menu to height -func (i *Menu) SetHeight(height int) *Menu { +func (i *Menu) SetHeight(height int) { i.height = height - return i } // GetSelectedOption returns the current selected option @@ -140,7 +134,7 @@ func (i *Menu) GetSelectedIndex() int { } // SetSelectedOption sets the current selected option to v (if it's valid) -func (i *Menu) SetSelectedOption(v *MenuOption) *Menu { +func (i *Menu) SetSelectedOption(v *MenuOption) { for idx := range i.options { if &i.options[idx] == v { i.options[idx].Select() @@ -148,35 +142,30 @@ func (i *Menu) SetSelectedOption(v *MenuOption) *Menu { i.options[idx].Unselect() } } - return i } // SelectPrevOption Decrements the selected option (if it can) -func (i *Menu) SelectPrevOption() *Menu { +func (i *Menu) SelectPrevOption() { idx := i.GetSelectedIndex() for idx >= 0 { idx-- testOption := i.GetOptionFromIndex(idx) if testOption != nil && !testOption.IsDisabled() { i.SetSelectedOption(testOption) - return i } } - return i } // SelectNextOption Increments the selected option (if it can) -func (i *Menu) SelectNextOption() *Menu { +func (i *Menu) SelectNextOption() { idx := i.GetSelectedIndex() for idx < len(i.options) { idx++ testOption := i.GetOptionFromIndex(idx) if testOption != nil && !testOption.IsDisabled() { i.SetSelectedOption(testOption) - return i } } - return i } // SetOptionDisabled Disables the specified option @@ -197,62 +186,55 @@ func (i *Menu) SetOptionEnabled(idx int) { func (i *Menu) HelpIsShown() bool { return i.showHelp } // ShowHelp sets whether or not to display the help text -func (i *Menu) ShowHelp(b bool) *Menu { +func (i *Menu) ShowHelp(b bool) { i.showHelp = b - return i } // GetBackground returns the current background color func (i *Menu) GetBackground() termbox.Attribute { return i.bg } // SetBackground sets the background color to bg -func (i *Menu) SetBackground(bg termbox.Attribute) *Menu { +func (i *Menu) SetBackground(bg termbox.Attribute) { i.bg = bg - return i } // GetForeground returns the current foreground color func (i *Menu) GetForeground() termbox.Attribute { return i.fg } // SetForeground sets the current foreground color to fg -func (i *Menu) SetForeground(fg termbox.Attribute) *Menu { +func (i *Menu) SetForeground(fg termbox.Attribute) { i.fg = fg - return i } // IsDone returns whether the user has answered the modal func (i *Menu) IsDone() bool { return i.isDone } // SetDone sets whether the modal has completed it's purpose -func (i *Menu) SetDone(b bool) *Menu { +func (i *Menu) SetDone(b bool) { i.isDone = b - return i } // IsBordered returns true or false if this menu has a border func (i *Menu) IsBordered() bool { return i.bordered } // SetBordered sets whether we render a border around the menu -func (i *Menu) SetBordered(b bool) *Menu { +func (i *Menu) SetBordered(b bool) { i.bordered = b - return i } // EnableVimMode Enables h,j,k,l navigation -func (i *Menu) EnableVimMode() *Menu { +func (i *Menu) EnableVimMode() { i.vimMode = true - return i } // DisableVimMode Disables h,j,k,l navigation -func (i *Menu) DisableVimMode() *Menu { +func (i *Menu) DisableVimMode() { i.vimMode = false - return i } // HandleKeyPress handles the termbox event and returns whether it was consumed func (i *Menu) HandleKeyPress(event termbox.Event) bool { - if event.Key == termbox.KeyEnter { + if event.Key == termbox.KeyEnter || event.Key == termbox.KeySpace { i.isDone = true return true } @@ -310,6 +292,11 @@ func (i *Menu) Draw() { } } + // If the currently selected option is disabled, move to the next + if i.GetSelectedOption().IsDisabled() { + i.SelectNextOption() + } + // Print the options if len(i.options) > 0 { for idx := range i.options { @@ -343,24 +330,21 @@ func CreateOptionFromText(s string) *MenuOption { } // SetText Sets the text for this option -func (i *MenuOption) SetText(s string) *MenuOption { +func (i *MenuOption) SetText(s string) { i.text = s - return i } // GetText Returns the text for this option func (i *MenuOption) GetText() string { return i.text } // Disable Sets this option to disabled -func (i *MenuOption) Disable() *MenuOption { +func (i *MenuOption) Disable() { i.disabled = true - return i } // Enable Sets this option to enabled -func (i *MenuOption) Enable() *MenuOption { +func (i *MenuOption) Enable() { i.disabled = false - return i } // IsDisabled returns whether this option is enabled @@ -374,21 +358,18 @@ func (i *MenuOption) IsSelected() bool { } // Select Sets this option to selected -func (i *MenuOption) Select() *MenuOption { +func (i *MenuOption) Select() { i.selected = true - return i } // Unselect Sets this option to not selected -func (i *MenuOption) Unselect() *MenuOption { +func (i *MenuOption) Unselect() { i.selected = false - return i } // SetHelpText Sets this option's help text to s -func (i *MenuOption) SetHelpText(s string) *MenuOption { +func (i *MenuOption) SetHelpText(s string) { i.helpText = s - return i } // GetHelpText Returns the help text for this option diff --git a/termbox_progressbar.go b/termbox_progressbar.go index afb0ed8..16aaef0 100644 --- a/termbox_progressbar.go +++ b/termbox_progressbar.go @@ -12,6 +12,7 @@ type ProgressBar struct { emptyChar rune bordered bool alignment TextAlignment + colorized bool x, y int width, height int @@ -35,27 +36,24 @@ func (i *ProgressBar) GetProgress() int { } // SetProgress sets the current progress of the bar -func (i *ProgressBar) SetProgress(p int) *ProgressBar { +func (i *ProgressBar) SetProgress(p int) { if (p <= i.total || i.allowOverflow) || (p >= 0 || i.allowUnderflow) { i.progress = p } - return i } // IncrProgress increments the current progress of the bar -func (i *ProgressBar) IncrProgress() *ProgressBar { +func (i *ProgressBar) IncrProgress() { if i.progress < i.total || i.allowOverflow { i.progress++ } - return i } // DecrProgress decrements the current progress of the bar -func (i *ProgressBar) DecrProgress() *ProgressBar { +func (i *ProgressBar) DecrProgress() { if i.progress > 0 || i.allowUnderflow { i.progress-- } - return i } // GetPercent returns the percent full of the bar @@ -64,27 +62,23 @@ func (i *ProgressBar) GetPercent() int { } // EnableOverflow Tells the progress bar that it can go over the total -func (i *ProgressBar) EnableOverflow() *ProgressBar { +func (i *ProgressBar) EnableOverflow() { i.allowOverflow = true - return i } // DisableOverflow Tells the progress bar that it can NOT go over the total -func (i *ProgressBar) DisableOverflow() *ProgressBar { +func (i *ProgressBar) DisableOverflow() { i.allowOverflow = false - return i } // EnableUnderflow Tells the progress bar that it can go below zero -func (i *ProgressBar) EnableUnderflow() *ProgressBar { +func (i *ProgressBar) EnableUnderflow() { i.allowUnderflow = true - return i } // DisableUnderflow Tells the progress bar that it can NOT go below zero -func (i *ProgressBar) DisableUnderflow() *ProgressBar { +func (i *ProgressBar) DisableUnderflow() { i.allowUnderflow = false - return i } // GetFullChar returns the rune used for 'full' @@ -93,9 +87,8 @@ func (i *ProgressBar) GetFullChar() rune { } // SetFullChar sets the rune used for 'full' -func (i *ProgressBar) SetFullChar(f rune) *ProgressBar { +func (i *ProgressBar) SetFullChar(f rune) { i.fullChar = f - return i } // GetEmptyChar gets the rune used for 'empty' @@ -104,27 +97,24 @@ func (i *ProgressBar) GetEmptyChar() rune { } // SetEmptyChar sets the rune used for 'empty' -func (i *ProgressBar) SetEmptyChar(f rune) *ProgressBar { +func (i *ProgressBar) SetEmptyChar(f rune) { i.emptyChar = f - return i } // GetX Return the x position of the Progress Bar func (i *ProgressBar) GetX() int { return i.x } // SetX set the x position of the ProgressBar to x -func (i *ProgressBar) SetX(x int) *ProgressBar { +func (i *ProgressBar) SetX(x int) { i.x = x - return i } // GetY Return the y position of the ProgressBar func (i *ProgressBar) GetY() int { return i.y } // SetY Set the y position of the ProgressBar to y -func (i *ProgressBar) SetY(y int) *ProgressBar { +func (i *ProgressBar) SetY(y int) { i.y = y - return i } // GetHeight returns the height of the progress bar @@ -134,9 +124,8 @@ func (i *ProgressBar) GetHeight() int { } // SetHeight Sets the height of the progress bar -func (i *ProgressBar) SetHeight(h int) *ProgressBar { +func (i *ProgressBar) SetHeight(h int) { i.height = h - return i } // GetWidth returns the width of the progress bar @@ -145,33 +134,38 @@ func (i *ProgressBar) GetWidth() int { } // SetWidth Sets the width of the progress bar -func (i *ProgressBar) SetWidth(w int) *ProgressBar { +func (i *ProgressBar) SetWidth(w int) { i.width = w - return i } // GetBackground Return the current background color of the modal func (i *ProgressBar) GetBackground() termbox.Attribute { return i.bg } // SetBackground Set the current background color to bg -func (i *ProgressBar) SetBackground(bg termbox.Attribute) *ProgressBar { +func (i *ProgressBar) SetBackground(bg termbox.Attribute) { i.bg = bg - return i } // GetForeground Return the current foreground color func (i *ProgressBar) GetForeground() termbox.Attribute { return i.fg } // SetForeground Set the foreground color to fg -func (i *ProgressBar) SetForeground(fg termbox.Attribute) *ProgressBar { +func (i *ProgressBar) SetForeground(fg termbox.Attribute) { i.fg = fg - return i } // Align Tells which direction the progress bar empties -func (i *ProgressBar) Align(a TextAlignment) *ProgressBar { +func (i *ProgressBar) Align(a TextAlignment) { i.alignment = a - return i +} + +// SetColorized sets whether the progress bar should be colored +// depending on how full it is: +// 10% - Red +// 50% - Yellow +// 80% - Green +func (i *ProgressBar) SetColorized(c bool) { + i.colorized = c } // HandleKeyPress accepts the termbox event and returns whether it was consumed @@ -183,11 +177,21 @@ func (i *ProgressBar) HandleKeyPress(event termbox.Event) bool { func (i *ProgressBar) Draw() { // For now, just draw a [#### ] bar // TODO: make this more advanced + useFg := i.fg + if i.colorized { + if i.GetPercent() < 10 { + useFg = termbox.ColorRed + } else if i.GetPercent() < 50 { + useFg = termbox.ColorYellow + } else { + useFg = termbox.ColorGreen + } + } drawX, drawY := i.x, i.y fillWidth, fillHeight := i.width-2, i.height DrawStringAtPoint("[", drawX, drawY, i.fg, i.bg) numFull := int(float64(fillWidth) * float64(i.progress) / float64(i.total)) - FillWithChar(i.fullChar, drawX+1, drawY, drawX+1+numFull, drawY+(fillHeight-1), i.fg, i.bg) + FillWithChar(i.fullChar, drawX+1, drawY, drawX+1+numFull, drawY+(fillHeight-1), useFg, i.bg) DrawStringAtPoint("]", drawX+i.width-1, drawY, i.fg, i.bg) /* diff --git a/termbox_scrollframe.go b/termbox_scrollframe.go new file mode 100644 index 0000000..5d31f13 --- /dev/null +++ b/termbox_scrollframe.go @@ -0,0 +1,155 @@ +package termboxUtil + +import "github.com/nsf/termbox-go" + +// ScrollFrame is a frame for holding other elements +// It manages it's own x/y, tab index +type ScrollFrame struct { + x, y, width, height int + scrollX, scrollY int + tabIdx int + fg, bg termbox.Attribute + bordered bool + controls []termboxControl +} + +// CreateScrollFrame creates Scrolling Frame at x, y that is w by h +func CreateScrollFrame(x, y, w, h int, fg, bg termbox.Attribute) *ScrollFrame { + s := ScrollFrame{x: x, y: y, width: w, height: h, fg: fg, bg: bg} + return &s +} + +// GetX returns the x position of the scroll frame +func (s *ScrollFrame) GetX() int { return s.x } + +// SetX sets the x position of the scroll frame +func (s *ScrollFrame) SetX(x int) { + s.x = x +} + +// GetY returns the y position of the scroll frame +func (s *ScrollFrame) GetY() int { return s.y } + +// SetY sets the y position of the scroll frame +func (s *ScrollFrame) SetY(y int) { + s.y = y +} + +// GetWidth returns the current width of the scroll frame +func (s *ScrollFrame) GetWidth() int { return s.width } + +// SetWidth sets the current width of the scroll frame +func (s *ScrollFrame) SetWidth(w int) { + s.width = w +} + +// GetHeight returns the current height of the scroll frame +func (s *ScrollFrame) GetHeight() int { return s.height } + +// SetHeight sets the current height of the scroll frame +func (s *ScrollFrame) SetHeight(h int) { + s.height = h +} + +// IsBordered returns true or false if this scroll frame has a border +func (s *ScrollFrame) IsBordered() bool { return s.bordered } + +// SetBordered sets whether we render a border around the scroll frame +func (s *ScrollFrame) SetBordered(b bool) { + s.bordered = b +} + +// GetScrollX returns the x distance scrolled +func (s *ScrollFrame) GetScrollX() int { + return s.scrollX +} + +// GetScrollY returns the y distance scrolled +func (s *ScrollFrame) GetScrollY() int { + return s.scrollY +} + +// ScrollDown scrolls the frame down +func (s *ScrollFrame) ScrollDown() { + s.scrollY++ +} + +// ScrollUp scrolls the frame up +func (s *ScrollFrame) ScrollUp() { + if s.scrollY > 0 { + s.scrollY-- + } +} + +// ScrollLeft scrolls the frame left +func (s *ScrollFrame) ScrollLeft() { + if s.scrollX > 0 { + s.scrollX-- + } +} + +// ScrollRight scrolls the frame right +func (s *ScrollFrame) ScrollRight() { + s.scrollX++ +} + +// AddControl adds a control to the frame +func (s *ScrollFrame) AddControl(t termboxControl) { + s.controls = append(s.controls, t) +} + +// DrawControl figures out the relative position of the control, +// sets it, draws it, then resets it. +func (s *ScrollFrame) DrawControl(t termboxControl) { + if s.IsVisible(t) { + ctlX, ctlY := t.GetX(), t.GetY() + t.SetX((s.GetX() + ctlX)) + t.SetY((s.GetY() + ctlY)) + t.Draw() + t.SetX(ctlX) + t.SetY(ctlY) + } +} + +// IsVisible takes a Termbox Control and returns whether +// that control would be visible in the frame +func (s *ScrollFrame) IsVisible(t termboxControl) bool { + // Check if any part of t should be visible + cX, cY := t.GetX(), t.GetY() + if cX+t.GetWidth() >= s.scrollX && cX <= s.scrollX+s.width { + return cY+t.GetHeight() >= s.scrollY && cY <= s.scrollY+s.height + } + return false +} + +// HandleKeyPress accepts the termbox event and returns whether it was consumed +func (s *ScrollFrame) HandleKeyPress(event termbox.Event) bool { + return false +} + +// DrawToStrings generates a slice of strings with what should +// be drawn to the screen +func (s *ScrollFrame) DrawToStrings() []string { + return []string{} +} + +// Draw outputs the Scoll Frame on the screen +func (s *ScrollFrame) Draw() { + maxWidth := s.width + maxHeight := s.height + x, y := s.x, s.y + startX := s.x + startY := s.y + if s.bordered { + DrawBorder(s.x, s.y, s.x+s.width, s.y+s.height, s.fg, s.bg) + maxWidth-- + maxHeight-- + x++ + y++ + startX++ + startY++ + } + for i := range s.controls { + s.DrawControl(s.controls[i]) + } +} diff --git a/termbox_util.go b/termbox_util.go index db05a84..25d12a9 100644 --- a/termbox_util.go +++ b/termbox_util.go @@ -7,6 +7,19 @@ import ( "github.com/nsf/termbox-go" ) +type termboxControl interface { + GetX() int + SetX(int) + GetY() int + SetY(int) + GetWidth() int + SetWidth(int) + GetHeight() int + SetHeight(int) + HandleKeyPress(termbox.Event) bool + Draw() +} + // TextAlignment is an int value for how we're aligning text type TextAlignment int @@ -19,6 +32,49 @@ const ( AlignRight ) +/* Basic Input Helpers */ + +// KeyIsAlphaNumeric Returns whether the termbox event is an +// Alpha-Numeric Key Press +func KeyIsAlphaNumeric(event termbox.Event) bool { + return KeyIsAlpha(event) || KeyIsNumeric(event) +} + +// KeyIsAlpha Returns whether the termbox event is a +// alphabetic Key press +func KeyIsAlpha(event termbox.Event) bool { + k := event.Ch + if (k >= 'a' && k <= 'z') || (k >= 'A' && k <= 'Z') { + return true + } + return false +} + +// KeyIsNumeric Returns whether the termbox event is a +// numeric Key press +func KeyIsNumeric(event termbox.Event) bool { + k := event.Ch + if k >= '0' && k <= '9' { + return true + } + return false +} + +// KeyIsSymbol Returns whether the termbox event is a +// symbol Key press +func KeyIsSymbol(event termbox.Event) bool { + symbols := []rune{'!', '@', '#', '$', '%', '^', '&', '*', + '(', ')', '-', '_', '=', '+', '[', ']', '{', '}', '|', + ';', ':', '"', '\'', ',', '<', '.', '>', '/', '?', '`', '~'} + k := event.Ch + for i := range symbols { + if k == symbols[i] { + return true + } + } + return false +} + /* Basic Output Helpers */ // DrawStringAtPoint Draw a string of text at x, y with foreground color fg, background color bg