diff --git a/termbox-test/.gitignore b/termbox-test/.gitignore new file mode 100644 index 0000000..4724e93 --- /dev/null +++ b/termbox-test/.gitignore @@ -0,0 +1 @@ +termbox-test \ No newline at end of file diff --git a/termbox-test/main.go b/termbox-test/main.go index 855f210..9207095 100755 --- a/termbox-test/main.go +++ b/termbox-test/main.go @@ -1,17 +1,21 @@ package main import ( - "fmt" - "github.com/nsf/termbox-go" - "gogs.bullercodeworks.com/brian/termbox-util" "os" + "strconv" "syscall" + + "gogs.bullercodeworks.com/brian/termbox-util" + + "github.com/nsf/termbox-go" ) -var keep_running bool +var keepRunning bool +var initialized bool +var frame *termboxUtil.Frame func main() { - keep_running = true + keepRunning = true err := termbox.Init() if err != nil { panic(err) @@ -21,82 +25,7 @@ func main() { mainLoop() } -//var input *termbox_util.InputField -var input *termbox_util.InputModal -var new_key, new_val string -var mode string -var drawY int -var added_stuff []string - -func layoutAndDrawScreen() { - termbox.Clear(0, termbox.ColorBlack) - drawScreen() - termbox.Flush() -} - -func drawScreen() { - w, h := termbox.Size() - termbox_util.DrawStringAtPoint(termbox_util.AlignText("Termbox Utility Test", w, termbox_util.ALIGN_CENTER), 0, 0, termbox.ColorWhite, termbox.ColorRed) - if input == nil { - mw, mh := w/4, h/4 - mx, my := (w/2)-(mw/2), (h/2)-(mh/2) - input = termbox_util.CreateInputModal("", mx, my, mw, mh, termbox.ColorWhite, termbox.ColorBlack) - input.Clear() - } - if mode == "bucket" { - if input.IsDone() { - added_stuff = append(added_stuff, fmt.Sprintf("New Bucket %s", input.GetValue())) - input.Clear() - mode = "" - } else { - input.Draw() - } - } else if mode == "pair" { - if input.IsDone() { - if new_key == "" { - new_key = input.GetValue() - input.Clear() - input.SetTitle("Pair Value") - } else { - added_stuff = append(added_stuff, fmt.Sprintf("New Pair %s => %s", new_key, input.GetValue())) - mode = "" - input.Clear() - } - } - if mode == "pair" && !input.IsDone() { - input.Draw() - } - } - if mode == "" { - for i := range added_stuff { - termbox_util.DrawStringAtPoint(added_stuff[i], 1, 3+i, termbox.ColorWhite, termbox.ColorRed) - } - } -} - -func handleKeyEvent(event termbox.Event) bool { - if event.Key == termbox.KeyEsc { - return false - } else if event.Key == termbox.KeyCtrlB { - mode = "bucket" - new_key = "" - new_val = "" - input.Clear() - input.SetTitle("Bucket Name") - } else if event.Key == termbox.KeyCtrlP { - mode = "pair" - new_key = "" - new_val = "" - input.Clear() - input.SetTitle("Pair Key") - } else { - input.HandleKeyPress(event) - } - return true -} - func mainLoop() { - added_stuff = append(added_stuff, "Ctrl+B = Add Bucket; Ctrl+P = Add Pair") layoutAndDrawScreen() for { event := termbox.PollEvent() @@ -106,9 +35,12 @@ func mainLoop() { termbox.Close() process.Signal(syscall.SIGSTOP) termbox.Init() + } else if event.Key == termbox.KeyCtrlC { + termbox.Close() + os.Exit(0) } - keep_running = handleKeyEvent(event) - if !keep_running { + keepRunning = handleEvent(event) + if !keepRunning { break } layoutAndDrawScreen() @@ -117,4 +49,70 @@ func mainLoop() { layoutAndDrawScreen() } } +} + +func layoutAndDrawScreen() { + w, h := termbox.Size() + if !initialized { + fg, bg := termbox.ColorWhite, termbox.ColorBlack + frame = termboxUtil.CreateFrame(1, 1, w-3, h-3, termbox.ColorWhite, termbox.ColorBlack) + /* + frame.AddControl(termboxUtil.CreateASCIIArt([]string{ + "/" + strings.Repeat("=====", 5) + "\\", + "|" + strings.Repeat(".oOo.", 5) + "|", + "\\" + strings.Repeat("=====", 5) + "/", + }, 1, frame.GetBottomY()+1, fg, bg)) + frame.AddControl(termboxUtil.CreateAlertModal("AlertModal", 1, 1, w-5, 6, termbox.ColorGreen, bg)) + frame.AddControl(termboxUtil.CreateConfirmModal("ConfirmModal", 1, frame.GetBottomY()+1, w-5, 7, fg, bg)) + frame.AddControl(termboxUtil.CreateInputModal("InputModal", 1, frame.GetBottomY()+1, w-5, 7, fg, bg)) + */ + frame.AddControl(termboxUtil.CreateDropMenu("Add Control", []string{ + "AlertModal", + "ASCIIArt", + "ConfirmModal", + "DropMenu", + "Frame", + "InputField", + "InputModal", + "Label", + "Menu", + "ProgressBar", + "ScrollFrame", + }, + 1, frame.GetBottomY()+1, w-5, 7, fg, bg, termbox.ColorBlack, termbox.ColorGreen)) + frame.GetLastControl().SetBordered(true) + frame.SetActiveFlag(true) + + initialized = true + } + termbox.Clear(0, termbox.ColorBlack) + drawScreen() + termboxUtil.DrawStringAtPoint(strconv.Itoa(frame.GetBottomY()), 0, h-1, termbox.ColorWhite, termbox.ColorBlack) + termbox.Flush() +} + +func drawScreen() { + frame.Draw() +} + +func handleEvent(event termbox.Event) bool { + frame.HandleEvent(event) + for _, k := range frame.GetControls() { + switch v := k.(type) { + case *termboxUtil.DropMenu: + if v.IsDone() { + } + case *termboxUtil.AlertModal: + if v.IsDone() { + v.SetText("Finished") + } + case *termboxUtil.ConfirmModal: + if v.IsDone() { + v.SetText("Finished") + } + } + k.SetFgColor(termbox.ColorWhite) + } + frame.GetActiveControl().SetFgColor(termbox.ColorGreen) + return true } \ No newline at end of file diff --git a/termbox_alertmodal.go b/termbox_alertmodal.go index 8efccb7..569ea36 100644 --- a/termbox_alertmodal.go +++ b/termbox_alertmodal.go @@ -6,6 +6,7 @@ import ( // AlertModal is a modal with yes/no (or similar) buttons type AlertModal struct { + id string title string text string x, y, width, height int @@ -16,18 +17,29 @@ type AlertModal struct { accepted bool value string isVisible bool + bordered bool + tabSkip bool + active bool } // CreateAlertModal Creates a confirmation modal with the specified attributes func CreateAlertModal(title string, x, y, width, height int, fg, bg termbox.Attribute) *AlertModal { - i := AlertModal{title: title, x: x, y: y, width: width, height: height, fg: fg, bg: bg} - if i.title == "" && i.text == "" { + i := AlertModal{title: title, x: x, y: y, width: width, height: height, fg: fg, bg: bg, bordered: true} + if i.title == "" { i.title = "Alert!" } i.showHelp = true return &i } +// GetID returns this control's ID +func (i *AlertModal) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *AlertModal) SetID(newID string) { + i.id = newID +} + // GetTitle returns the current title of the modal func (i *AlertModal) GetTitle() string { return i.title } @@ -44,38 +56,74 @@ func (i *AlertModal) SetText(s string) { i.text = s } -// GetX returns the current x coordinate of the modal +// GetX returns the current x coordinate of the control func (i *AlertModal) GetX() int { return i.x } -// SetX sets the current x coordinate of the modal to x +// SetX sets the current x coordinate of the control to x func (i *AlertModal) SetX(x int) { i.x = x } -// GetY returns the current y coordinate of the modal +// GetY returns the current y coordinate of the control func (i *AlertModal) GetY() int { return i.y } -// SetY sets the current y coordinate of the modal to y +// SetY sets the current y coordinate of the control to y func (i *AlertModal) SetY(y int) { i.y = y } -// GetWidth returns the current width of the modal +// GetWidth returns the current width of the control func (i *AlertModal) GetWidth() int { return i.width } -// SetWidth sets the current modal width to width +// SetWidth sets the current control width to width func (i *AlertModal) SetWidth(width int) { i.width = width } -// GetHeight returns the current height of the modal +// GetHeight returns the current height of the control func (i *AlertModal) GetHeight() int { return i.height } -// SetHeight set the height of the modal to height +// SetHeight set the height of the control to height func (i *AlertModal) SetHeight(height int) { i.height = height } +// GetFgColor returns the foreground color +func (i *AlertModal) GetFgColor() termbox.Attribute { return i.fg } + +// SetFgColor sets the foreground color +func (i *AlertModal) SetFgColor(fg termbox.Attribute) { + i.fg = fg +} + +// GetBgColor returns the background color +func (i *AlertModal) GetBgColor() termbox.Attribute { return i.bg } + +// SetBgColor sets the current background color +func (i *AlertModal) SetBgColor(bg termbox.Attribute) { + i.bg = bg +} + +// IsBordered returns whether this control is bordered or not +func (i *AlertModal) IsBordered() bool { + return i.bordered +} + +// SetBordered sets whether we render a border around the frame +func (i *AlertModal) SetBordered(b bool) { + i.bordered = b +} + +// IsTabSkipped returns whether this control has it's tabskip flag set +func (i *AlertModal) IsTabSkipped() bool { + return i.tabSkip +} + +// SetTabSkip sets the tabskip flag for this control +func (i *AlertModal) SetTabSkip(b bool) { + i.tabSkip = b +} + // HelpIsShown returns true or false if the help is displayed func (i *AlertModal) HelpIsShown() bool { return i.showHelp } @@ -129,11 +177,21 @@ func (i *AlertModal) Clear() { i.isDone = false } -// HandleKeyPress handles the termbox event and returns whether it was consumed -func (i *AlertModal) HandleKeyPress(event termbox.Event) bool { - if event.Key == termbox.KeyEnter { - i.isDone = true - return true +// SetActiveFlag sets this control's active flag +func (i *AlertModal) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *AlertModal) IsActive() bool { return i.active } + +// HandleEvent handles the termbox event and returns whether it was consumed +func (i *AlertModal) HandleEvent(event termbox.Event) bool { + if !i.active { + if event.Key == termbox.KeyEnter { + i.isDone = true + return true + } } return false } @@ -155,7 +213,6 @@ func (i *AlertModal) Draw() { } if i.text != "" { DrawStringAtPoint(i.text, i.x+1, nextY, i.fg, i.bg) - nextY++ } nextY += 2 if i.showHelp { diff --git a/termbox_asciiart.go b/termbox_asciiart.go index 4f91a40..970c453 100644 --- a/termbox_asciiart.go +++ b/termbox_asciiart.go @@ -8,17 +8,37 @@ import ( // ASCIIArt is a []string with more functions type ASCIIArt struct { + id string contents []string x, y int bg, fg termbox.Attribute + bordered bool + tabSkip bool + active bool } // CreateASCIIArt Create an ASCII art object from a string slice func CreateASCIIArt(c []string, x, y int, fg, bg termbox.Attribute) *ASCIIArt { - i := ASCIIArt{contents: c, x: x, y: y, fg: fg, bg: bg} + i := ASCIIArt{contents: c, x: x, y: y, fg: fg, bg: bg, bordered: false, tabSkip: true} return &i } +// SetActiveFlag sets this control's active flag +func (i *ASCIIArt) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *ASCIIArt) IsActive() bool { return i.active } + +// GetID returns this control's ID +func (i *ASCIIArt) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *ASCIIArt) SetID(newID string) { + i.id = newID +} + // GetX Return the x position of the modal func (i *ASCIIArt) GetX() int { return i.x } @@ -93,20 +113,20 @@ func (i *ASCIIArt) SetContentLine(s string, idx int) { } } -// GetBackground Return the current background color of the modal -func (i *ASCIIArt) GetBackground() termbox.Attribute { return i.bg } +// GetFgColor returns the foreground color +func (i *ASCIIArt) GetFgColor() termbox.Attribute { return i.fg } -// SetBackground Set the current background color to bg -func (i *ASCIIArt) SetBackground(bg termbox.Attribute) { - i.bg = bg +// SetFgColor sets the foreground color +func (i *ASCIIArt) SetFgColor(fg termbox.Attribute) { + i.fg = fg } -// GetForeground Return the current foreground color -func (i *ASCIIArt) GetForeground() termbox.Attribute { return i.fg } +// GetBgColor returns the background color +func (i *ASCIIArt) GetBgColor() termbox.Attribute { return i.bg } -// SetForeground Set the foreground color to fg -func (i *ASCIIArt) SetForeground(fg termbox.Attribute) { - i.fg = fg +// SetBgColor sets the current background color +func (i *ASCIIArt) SetBgColor(bg termbox.Attribute) { + i.bg = bg } // Align Align the Ascii art over width width with alignment a @@ -125,8 +145,28 @@ func (i *ASCIIArt) Align(a TextAlignment, width int) { i.contents = newContents } -// HandleKeyPress accepts the termbox event and returns whether it was consumed -func (i *ASCIIArt) HandleKeyPress(event termbox.Event) bool { +// IsBordered returns whether this modal is bordered or not +func (i *ASCIIArt) IsBordered() bool { + return i.bordered +} + +// SetBordered sets whether we render a border around the frame +func (i *ASCIIArt) SetBordered(b bool) { + i.bordered = b +} + +// IsTabSkipped returns whether this modal has it's tabskip flag set +func (i *ASCIIArt) IsTabSkipped() bool { + return i.tabSkip +} + +// SetTabSkip sets the tabskip flag for this control +func (i *ASCIIArt) SetTabSkip(b bool) { + i.tabSkip = b +} + +// HandleEvent accepts the termbox event and returns whether it was consumed +func (i *ASCIIArt) HandleEvent(event termbox.Event) bool { return false } diff --git a/termbox_confirmmodal.go b/termbox_confirmmodal.go index 59d419c..585230d 100644 --- a/termbox_confirmmodal.go +++ b/termbox_confirmmodal.go @@ -6,6 +6,7 @@ import ( // ConfirmModal is a modal with yes/no (or similar) buttons type ConfirmModal struct { + id string title string text string x, y, width, height int @@ -16,6 +17,9 @@ type ConfirmModal struct { accepted bool value string isVisible bool + bordered bool + tabSkip bool + active bool } // CreateConfirmModal Creates a confirmation modal with the specified attributes @@ -28,6 +32,22 @@ func CreateConfirmModal(title string, x, y, width, height int, fg, bg termbox.At return &i } +// SetActiveFlag sets this control's active flag +func (i *ConfirmModal) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *ConfirmModal) IsActive() bool { return i.active } + +// GetID returns this control's ID +func (i *ConfirmModal) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *ConfirmModal) SetID(newID string) { + i.id = newID +} + // GetTitle returns the current title of the modal func (i *ConfirmModal) GetTitle() string { return i.title } @@ -84,20 +104,20 @@ func (i *ConfirmModal) ShowHelp(b bool) { i.showHelp = b } -// GetBackground returns the current background color -func (i *ConfirmModal) GetBackground() termbox.Attribute { return i.bg } +// GetFgColor returns the foreground color +func (i *ConfirmModal) GetFgColor() termbox.Attribute { return i.fg } -// SetBackground sets the background color to bg -func (i *ConfirmModal) SetBackground(bg termbox.Attribute) { - i.bg = bg +// SetFgColor sets the foreground color +func (i *ConfirmModal) SetFgColor(fg termbox.Attribute) { + i.fg = fg } -// GetForeground returns the current foreground color -func (i *ConfirmModal) GetForeground() termbox.Attribute { return i.fg } +// GetBgColor returns the background color +func (i *ConfirmModal) GetBgColor() termbox.Attribute { return i.bg } -// SetForeground sets the current foreground color to fg -func (i *ConfirmModal) SetForeground(fg termbox.Attribute) { - i.fg = fg +// SetBgColor sets the current background color +func (i *ConfirmModal) SetBgColor(bg termbox.Attribute) { + i.bg = bg } // IsDone returns whether the user has answered the modal @@ -129,16 +149,38 @@ func (i *ConfirmModal) Clear() { i.isDone = false } -// HandleKeyPress handles the termbox event and returns whether it was consumed -func (i *ConfirmModal) HandleKeyPress(event termbox.Event) bool { - if event.Ch == 'Y' || event.Ch == 'y' { - i.accepted = true - i.isDone = true - return true - } else if event.Ch == 'N' || event.Ch == 'n' { - i.accepted = false - i.isDone = true - return true +// IsBordered returns whether this modal is bordered or not +func (i *ConfirmModal) IsBordered() bool { + return i.bordered +} + +// SetBordered sets whether we render a border around the frame +func (i *ConfirmModal) SetBordered(b bool) { + i.bordered = b +} + +// IsTabSkipped returns whether this modal has it's tabskip flag set +func (i *ConfirmModal) IsTabSkipped() bool { + return i.tabSkip +} + +// SetTabSkip sets the tabskip flag for this control +func (i *ConfirmModal) SetTabSkip(b bool) { + i.tabSkip = b +} + +// HandleEvent handles the termbox event and returns whether it was consumed +func (i *ConfirmModal) HandleEvent(event termbox.Event) bool { + if i.active { + if event.Ch == 'Y' || event.Ch == 'y' { + i.accepted = true + i.isDone = true + return true + } else if event.Ch == 'N' || event.Ch == 'n' { + i.accepted = false + i.isDone = true + return true + } } return false } @@ -160,7 +202,6 @@ func (i *ConfirmModal) Draw() { } if i.text != "" { DrawStringAtPoint(i.text, i.x+1, nextY, i.fg, i.bg) - nextY++ } nextY += 2 if i.showHelp { diff --git a/termbox_dropmenu.go b/termbox_dropmenu.go new file mode 100644 index 0000000..ce8d509 --- /dev/null +++ b/termbox_dropmenu.go @@ -0,0 +1,190 @@ +package termboxUtil + +import "github.com/nsf/termbox-go" + +// DropMenu is a title that, when active drops a menu down +type DropMenu struct { + id string + title string + x, y, width, height int + bg, fg termbox.Attribute + selectedBg, selectedFg termbox.Attribute + menu *Menu + menuSelected bool + showMenu bool + bordered bool + tabSkip bool + active bool +} + +// CreateDropMenu Creates a menu with the specified attributes +func CreateDropMenu(title string, options []string, x, y, width, height int, fg, bg, selectedFg, selectedBg termbox.Attribute) *DropMenu { + i := DropMenu{ + title: title, + x: x, y: y, width: width, height: height, + fg: fg, bg: bg, + selectedFg: fg, selectedBg: bg, + } + i.menu = CreateMenu("", options, x, y+2, width, height, fg, bg) + return &i +} + +// GetID returns this control's ID +func (i *DropMenu) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *DropMenu) SetID(newID string) { + i.id = newID +} + +// GetTitle returns the current title of the menu +func (i *DropMenu) GetTitle() string { return i.title } + +// SetTitle sets the current title of the menu to s +func (i *DropMenu) SetTitle(s string) { + i.title = s +} + +// GetMenu returns the menu for this dropmenu +func (i *DropMenu) GetMenu() *Menu { + return i.menu +} + +// GetX returns the current x coordinate of the menu +func (i *DropMenu) GetX() int { return i.x } + +// SetX sets the current x coordinate of the menu to x +func (i *DropMenu) SetX(x int) { + i.x = x +} + +// GetY returns the current y coordinate of the menu +func (i *DropMenu) GetY() int { return i.y } + +// SetY sets the current y coordinate of the menu to y +func (i *DropMenu) SetY(y int) { + i.y = y +} + +// GetWidth returns the current width of the menu +func (i *DropMenu) GetWidth() int { return i.width } + +// SetWidth sets the current menu width to width +func (i *DropMenu) SetWidth(width int) { + i.width = width +} + +// GetHeight returns the current height of the menu +func (i *DropMenu) GetHeight() int { return i.height } + +// SetHeight set the height of the menu to height +func (i *DropMenu) SetHeight(height int) { + i.height = height +} + +// GetFgColor returns the foreground color +func (i *DropMenu) GetFgColor() termbox.Attribute { return i.fg } + +// SetFgColor sets the foreground color +func (i *DropMenu) SetFgColor(fg termbox.Attribute) { + i.fg = fg +} + +// GetBgColor returns the background color +func (i *DropMenu) GetBgColor() termbox.Attribute { return i.bg } + +// SetBgColor sets the current background color +func (i *DropMenu) SetBgColor(bg termbox.Attribute) { + i.bg = bg +} + +// IsBordered returns the bordered flag +func (i *DropMenu) IsBordered() bool { return i.bordered } + +// SetBordered sets the bordered flag +func (i *DropMenu) SetBordered(b bool) { + i.bordered = b + i.menu.SetBordered(b) +} + +// IsDone returns whether the user has answered the modal +func (i *DropMenu) IsDone() bool { return i.menu.isDone } + +// SetDone sets whether the modal has completed it's purpose +func (i *DropMenu) SetDone(b bool) { + i.menu.isDone = b +} + +// IsTabSkipped returns whether this modal has it's tabskip flag set +func (i *DropMenu) IsTabSkipped() bool { + return i.tabSkip +} + +// SetTabSkip sets the tabskip flag for this control +func (i *DropMenu) SetTabSkip(b bool) { + i.tabSkip = b +} + +// SetActiveFlag sets the dropmenu active flag +func (i *DropMenu) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether the DropMenu is active +func (i *DropMenu) IsActive() bool { + return i.active +} + +// ShowMenu tells the menu to draw the options +func (i *DropMenu) ShowMenu() { + i.showMenu = true + i.menuSelected = true +} + +// HideMenu tells the menu to hide the options +func (i *DropMenu) HideMenu() { + i.showMenu = false + i.menuSelected = false +} + +// HandleEvent handles the termbox event and returns whether it was consumed +func (i *DropMenu) HandleEvent(event termbox.Event) bool { + if i.active { + moveUp := (event.Key == termbox.KeyArrowUp || (i.menu.vimMode && event.Ch == 'k')) + moveDown := (event.Key == termbox.KeyArrowDown || (i.menu.vimMode && event.Ch == 'j')) + if i.menuSelected { + selIdx := i.menu.GetSelectedIndex() + if (moveUp && selIdx == 0) || (moveDown && selIdx == (len(i.menu.options)-1)) { + i.menuSelected = false + } else { + if i.menu.HandleEvent(event) { + if i.menu.IsDone() { + i.HideMenu() + } + return true + } + } + } else { + i.ShowMenu() + return true + } + } + return false +} + +// Draw draws the menu +func (i *DropMenu) Draw() { + // The title + ttlFg, ttlBg := i.fg, i.bg + if i.active && !i.menuSelected { + ttlFg, ttlBg = i.selectedFg, i.selectedBg + } + ttlTxt := i.title + if i.showMenu { + ttlTxt = ttlTxt + "-Showing Menu" + } + DrawStringAtPoint(AlignText(i.title, i.width, AlignLeft), i.x, i.y, ttlFg, ttlBg) + if i.showMenu { + i.menu.Draw() + } +} diff --git a/termbox_frame.go b/termbox_frame.go new file mode 100644 index 0000000..554856c --- /dev/null +++ b/termbox_frame.go @@ -0,0 +1,213 @@ +package termboxUtil + +import "github.com/nsf/termbox-go" + +// Frame is a frame for holding other elements +// It manages it's own x/y, tab index +type Frame struct { + id string + x, y, width, height int + tabIdx int + fg, bg termbox.Attribute + bordered bool + controls []termboxControl + tabSkip bool + active bool +} + +// CreateFrame creates a Frame at x, y that is w by h +func CreateFrame(x, y, w, h int, fg, bg termbox.Attribute) *Frame { + s := Frame{x: x, y: y, width: w, height: h, fg: fg, bg: bg, bordered: true} + return &s +} + +// SetActiveFlag sets this control's active flag +func (i *Frame) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *Frame) IsActive() bool { return i.active } + +// GetID returns this control's ID +func (i *Frame) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *Frame) SetID(newID string) { + i.id = newID +} + +// GetX returns the x position of the frame +func (i *Frame) GetX() int { return i.x } + +// SetX sets the x position of the frame +func (i *Frame) SetX(x int) { + i.x = x +} + +// GetY returns the y position of the frame +func (i *Frame) GetY() int { return i.y } + +// SetY sets the y position of the frame +func (i *Frame) SetY(y int) { + i.y = y +} + +// GetWidth returns the current width of the frame +func (i *Frame) GetWidth() int { return i.width } + +// SetWidth sets the current width of the frame +func (i *Frame) SetWidth(w int) { + i.width = w +} + +// GetHeight returns the current height of the frame +func (i *Frame) GetHeight() int { return i.height } + +// SetHeight sets the current height of the frame +func (i *Frame) SetHeight(h int) { + i.height = h +} + +// GetFgColor returns the foreground color +func (i *Frame) GetFgColor() termbox.Attribute { return i.fg } + +// SetFgColor sets the foreground color +func (i *Frame) SetFgColor(fg termbox.Attribute) { + i.fg = fg +} + +// GetBgColor returns the background color +func (i *Frame) GetBgColor() termbox.Attribute { return i.bg } + +// SetBgColor sets the current background color +func (i *Frame) SetBgColor(bg termbox.Attribute) { + i.bg = bg +} + +// IsBordered returns true or false if this frame has a border +func (i *Frame) IsBordered() bool { return i.bordered } + +// SetBordered sets whether we render a border around the frame +func (i *Frame) SetBordered(b bool) { + i.bordered = b +} + +// IsTabSkipped returns whether this modal has it's tabskip flag set +func (i *Frame) IsTabSkipped() bool { + return i.tabSkip +} + +// SetTabSkip sets the tabskip flag for this control +func (i *Frame) SetTabSkip(b bool) { + i.tabSkip = b +} + +// AddControl adds a control to the frame +func (i *Frame) AddControl(t termboxControl) { + i.controls = append(i.controls, t) +} + +// GetActiveControl returns the control at tabIdx +func (i *Frame) GetActiveControl() termboxControl { + if len(i.controls) >= i.tabIdx { + return i.controls[i.tabIdx] + } + return nil +} + +// GetControls returns a slice of all controls +func (i *Frame) GetControls() []termboxControl { + return i.controls +} + +// GetControl returns the control at index i +func (i *Frame) GetControl(idx int) termboxControl { + if len(i.controls) >= idx { + return i.controls[idx] + } + return nil +} + +// GetControlCount returns the number of controls contained +func (i *Frame) GetControlCount() int { + return len(i.controls) +} + +// GetLastControl returns the last control contained +func (i *Frame) GetLastControl() termboxControl { + return i.controls[len(i.controls)-1] +} + +// DrawControl figures out the relative position of the control, +// sets it, draws it, then resets it. +func (i *Frame) DrawControl(t termboxControl) { + ctlX, ctlY := t.GetX(), t.GetY() + t.SetX((i.GetX() + ctlX)) + t.SetY((i.GetY() + ctlY)) + t.Draw() + t.SetX(ctlX) + t.SetY(ctlY) +} + +// GetBottomY returns the y of the lowest control in the frame +func (i *Frame) GetBottomY() int { + var ret int + for idx := range i.controls { + if i.controls[idx].GetY()+i.controls[idx].GetHeight() > ret { + ret = i.controls[idx].GetY() + i.controls[idx].GetHeight() + } + } + return ret +} + +// HandleEvent accepts the termbox event and returns whether it was consumed +func (i *Frame) HandleEvent(event termbox.Event) bool { + if i.active { + if event.Key == termbox.KeyTab { + i.FindNextTabStop() + return true + } + return i.controls[i.tabIdx].HandleEvent(event) + } + return false +} + +// FindNextTabStop finds the next control that can be tabbed to +// A return of true means it found a different one than we started on. +func (i *Frame) FindNextTabStop() bool { + startTab := i.tabIdx + i.tabIdx = (i.tabIdx + 1) % len(i.controls) + for i.controls[i.tabIdx].IsTabSkipped() { + i.tabIdx = (i.tabIdx + 1) % len(i.controls) + if i.tabIdx == startTab { + break + } + } + for idx := range i.controls { + i.controls[idx].SetActiveFlag(idx == i.tabIdx) + } + return i.tabIdx != startTab +} + +// Draw outputs the Scoll Frame on the screen +func (i *Frame) Draw() { + maxWidth := i.width + maxHeight := i.height + x, y := i.x, i.y + startX := i.x + startY := i.y + if i.bordered { + FillWithChar(' ', i.x, i.y, i.x+i.width, i.y+i.height, i.fg, i.bg) + DrawBorder(i.x, i.y, i.x+i.width, i.y+i.height, i.fg, i.bg) + maxWidth-- + maxHeight-- + x++ + y++ + startX++ + startY++ + } + for idx := range i.controls { + i.DrawControl(i.controls[idx]) + } +} diff --git a/termbox_inputfield.go b/termbox_inputfield.go index 0f8d817..ce1790e 100644 --- a/termbox_inputfield.go +++ b/termbox_inputfield.go @@ -4,6 +4,7 @@ import "github.com/nsf/termbox-go" // InputField is a field for inputting text type InputField struct { + id string value string x, y, width, height int cursor int @@ -11,14 +12,32 @@ type InputField struct { bordered bool wrap bool multiline bool + tabSkip bool + active bool } // CreateInputField creates an input field at x, y that is w by h func CreateInputField(x, y, w, h int, fg, bg termbox.Attribute) *InputField { - i := InputField{x: x, y: y, width: w, height: h, fg: fg, bg: bg} + i := InputField{x: x, y: y, width: w, height: h, fg: fg, bg: bg, active: true} return &i } +// SetActiveFlag sets this control's active flag +func (i *InputField) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *InputField) IsActive() bool { return i.active } + +// GetID returns this control's ID +func (i *InputField) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *InputField) SetID(newID string) { + i.id = newID +} + // GetValue gets the current text that is in the InputField func (i *InputField) GetValue() string { return i.value } @@ -59,6 +78,22 @@ func (i *InputField) SetHeight(h int) { i.height = h } +// GetFgColor returns the foreground color +func (i *InputField) GetFgColor() termbox.Attribute { return i.fg } + +// SetFgColor sets the foreground color +func (i *InputField) SetFgColor(fg termbox.Attribute) { + i.fg = fg +} + +// GetBgColor returns the background color +func (i *InputField) GetBgColor() termbox.Attribute { return i.bg } + +// SetBgColor sets the current background color +func (i *InputField) SetBgColor(bg termbox.Attribute) { + i.bg = bg +} + // IsBordered returns true or false if this input field has a border func (i *InputField) IsBordered() bool { return i.bordered } @@ -67,6 +102,16 @@ func (i *InputField) SetBordered(b bool) { i.bordered = b } +// IsTabSkipped returns whether this modal has it's tabskip flag set +func (i *InputField) IsTabSkipped() bool { + return i.tabSkip +} + +// SetTabSkip sets the tabskip flag for this control +func (i *InputField) SetTabSkip(b bool) { + i.tabSkip = b +} + // DoesWrap returns true or false if this input field wraps text func (i *InputField) DoesWrap() bool { return i.wrap } @@ -83,55 +128,58 @@ 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.KeyBackspace || event.Key == termbox.KeyBackspace2 { - if len(i.value) > 0 { - i.value = i.value[:len(i.value)-1] - } - } else if event.Key == termbox.KeyArrowLeft { - if i.cursor+len(i.value) > 0 { - i.cursor-- - } - } else if event.Key == termbox.KeyArrowRight { - if i.cursor < 0 { - i.cursor++ - } - } else if event.Key == termbox.KeyCtrlU { - // 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 - var ch string - switch event.Key { - case termbox.KeySpace: - ch = " " - case termbox.KeyTab: - ch = "\t" - /* Multiline is disabled right now - case termbox.KeyEnter: - if i.multiline { - ch = "\n" +// HandleEvent accepts the termbox event and returns whether it was consumed +func (i *InputField) HandleEvent(event termbox.Event) bool { + if i.active { + if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 { + if len(i.value) > 0 { + i.value = i.value[:len(i.value)-1] + } + } else if event.Key == termbox.KeyArrowLeft { + if i.cursor+len(i.value) > 0 { + i.cursor-- + } + } else if event.Key == termbox.KeyArrowRight { + if i.cursor < 0 { + i.cursor++ + } + } else if event.Key == termbox.KeyCtrlU { + // 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 + var ch string + switch event.Key { + case termbox.KeySpace: + ch = " " + case termbox.KeyTab: + ch = "\t" + /* Multiline is disabled right now + case termbox.KeyEnter: + if i.multiline { + ch = "\n" + } + */ + default: + if KeyIsAlphaNumeric(event) || KeyIsSymbol(event) { + ch = string(event.Ch) } - */ - default: - if KeyIsAlphaNumeric(event) || KeyIsSymbol(event) { - ch = string(event.Ch) + } + + if i.cursor+len(i.value) == 0 { + i.value = string(ch) + i.value + } else if i.cursor == 0 { + 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 = strPt1 + string(ch) + strPt2 } } - - if i.cursor+len(i.value) == 0 { - i.value = string(ch) + i.value - } else if i.cursor == 0 { - 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 = strPt1 + string(ch) + strPt2 - } + return true } - return true + return false } // Draw outputs the input field on the screen @@ -190,7 +238,11 @@ func (i *InputField) Draw() { y++ x = startX } - termbox.SetCell(x, y, cursorRune, i.bg, i.fg) + if i.active { + termbox.SetCell(x, y, cursorRune, i.bg, i.fg) + } else { + termbox.SetCell(x, y, cursorRune, i.fg, i.bg) + } x++ if len(strPt2) > 0 { lenLeft := maxWidth - len(strPt1) - 1 @@ -219,7 +271,11 @@ func (i *InputField) Draw() { } } x, y = DrawStringAtPoint(strPt1, i.x+1, i.y+1, i.fg, i.bg) - termbox.SetCell(x, y, cursorRune, i.bg, i.fg) + if i.active { + termbox.SetCell(x, y, cursorRune, i.bg, i.fg) + } else { + termbox.SetCell(x, y, cursorRune, i.fg, i.bg) + } DrawStringAtPoint(strPt2, x+1, y, i.fg, i.bg) } } diff --git a/termbox_inputmodal.go b/termbox_inputmodal.go index 115449e..00acb2b 100644 --- a/termbox_inputmodal.go +++ b/termbox_inputmodal.go @@ -6,6 +6,7 @@ import ( // InputModal A modal for text input type InputModal struct { + id string title string text string input *InputField @@ -15,18 +16,37 @@ type InputModal struct { bg, fg termbox.Attribute isDone bool isVisible bool + bordered bool + tabSkip bool + active bool } // CreateInputModal Create an input modal with the given attributes func CreateInputModal(title string, x, y, width, height int, fg, bg termbox.Attribute) *InputModal { - i := InputModal{title: title, x: x, y: y, width: width, height: height, fg: fg, bg: bg} - i.input = CreateInputField(i.x+1, i.y+3, i.width-2, 2, i.fg, i.bg) + i := InputModal{title: title, x: x, y: y, width: width, height: height, fg: fg, bg: bg, bordered: true} + i.input = CreateInputField(i.x+2, i.y+3, i.width-2, 2, i.fg, i.bg) i.showHelp = true i.input.bordered = true i.isVisible = true return &i } +// SetActiveFlag sets this control's active flag +func (i *InputModal) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *InputModal) IsActive() bool { return i.active } + +// GetID returns this control's ID +func (i *InputModal) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *InputModal) SetID(newID string) { + i.id = newID +} + // GetTitle Return the title of the modal func (i *InputModal) GetTitle() string { return i.title } @@ -75,6 +95,26 @@ func (i *InputModal) SetHeight(height int) { i.height = height } +// IsBordered returns whether this control is bordered or not +func (i *InputModal) IsBordered() bool { + return i.bordered +} + +// SetBordered sets whether we render a border around the frame +func (i *InputModal) SetBordered(b bool) { + i.bordered = b +} + +// IsTabSkipped returns whether this control has it's tabskip flag set +func (i *InputModal) IsTabSkipped() bool { + return i.tabSkip +} + +// SetTabSkip sets the tabskip flag for this control +func (i *InputModal) SetTabSkip(b bool) { + i.tabSkip = b +} + // HelpIsShown Returns whether the modal is showing it's help text or not func (i *InputModal) HelpIsShown() bool { return i.showHelp } @@ -83,20 +123,20 @@ func (i *InputModal) ShowHelp(b bool) { i.showHelp = b } -// GetBackground Return the current background color of the modal -func (i *InputModal) GetBackground() termbox.Attribute { return i.bg } +// GetFgColor returns the foreground color +func (i *InputModal) GetFgColor() termbox.Attribute { return i.fg } -// SetBackground Set the current background color to bg -func (i *InputModal) SetBackground(bg termbox.Attribute) { - i.bg = bg +// SetFgColor sets the foreground color +func (i *InputModal) SetFgColor(fg termbox.Attribute) { + i.fg = fg } -// GetForeground Return the current foreground color -func (i *InputModal) GetForeground() termbox.Attribute { return i.fg } +// GetBgColor returns the background color +func (i *InputModal) GetBgColor() termbox.Attribute { return i.bg } -// SetForeground Set the foreground color to fg -func (i *InputModal) SetForeground(fg termbox.Attribute) { - i.fg = fg +// SetBgColor sets the current background color +func (i *InputModal) SetBgColor(bg termbox.Attribute) { + i.bg = bg } // Show Sets the visibility flag to true @@ -141,14 +181,17 @@ func (i *InputModal) Clear() { i.isVisible = false } -// HandleKeyPress Handle the termbox event, return true if it was consumed -func (i *InputModal) HandleKeyPress(event termbox.Event) bool { - if event.Key == termbox.KeyEnter { - // Done editing - i.isDone = true - return true +// HandleEvent Handle the termbox event, return true if it was consumed +func (i *InputModal) HandleEvent(event termbox.Event) bool { + if i.active { + if event.Key == termbox.KeyEnter { + // Done editing + i.isDone = true + return true + } + return i.input.HandleEvent(event) } - return i.input.HandleKeyPress(event) + return false } // Draw Draw the modal @@ -181,7 +224,9 @@ func (i *InputModal) Draw() { helpX := (i.x + i.width - len(helpString)) - 1 DrawStringAtPoint(helpString, helpX, nextY, i.fg, i.bg) } - // Now draw the border - DrawBorder(i.x, i.y, i.x+i.width, i.y+i.height, i.fg, i.bg) + if i.bordered { + // Now draw the border + DrawBorder(i.x, i.y, i.x+i.width, i.y+i.height, i.fg, i.bg) + } } } diff --git a/termbox_label.go b/termbox_label.go new file mode 100644 index 0000000..e2e3a97 --- /dev/null +++ b/termbox_label.go @@ -0,0 +1,149 @@ +package termboxUtil + +import "github.com/nsf/termbox-go" + +// Label is a field for inputting text +type Label struct { + id string + value string + x, y, width, height int + cursor int + fg, bg termbox.Attribute + bordered bool + wrap bool + multiline bool + active bool +} + +// CreateLabel creates an input field at x, y that is w by h +func CreateLabel(lbl string, x, y, w, h int, fg, bg termbox.Attribute) *Label { + i := Label{value: lbl, x: x, y: y, width: w, height: h, fg: fg, bg: bg} + return &i +} + +// SetActiveFlag sets this control's active flag +func (i *Label) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *Label) IsActive() bool { return i.active } + +// GetID returns this control's ID +func (i *Label) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *Label) SetID(newID string) { + i.id = newID +} + +// GetValue gets the current text that is in the Label +func (i *Label) GetValue() string { return i.value } + +// SetValue sets the current text in the Label to s +func (i *Label) SetValue(s string) { + i.value = s +} + +// GetX returns the x position of the input field +func (i *Label) GetX() int { return i.x } + +// SetX sets the x position of the input field +func (i *Label) SetX(x int) { + i.x = x +} + +// GetY returns the y position of the input field +func (i *Label) GetY() int { return i.y } + +// SetY sets the y position of the input field +func (i *Label) SetY(y int) { + i.y = y +} + +// GetWidth returns the current width of the input field +func (i *Label) GetWidth() int { + if i.width == -1 { + if i.bordered { + return len(i.value) + 2 + } + return len(i.value) + } + return i.width +} + +// SetWidth sets the current width of the input field +func (i *Label) SetWidth(w int) { + i.width = w +} + +// GetHeight returns the current height of the input field +func (i *Label) GetHeight() int { return i.height } + +// SetHeight sets the current height of the input field +func (i *Label) SetHeight(h int) { + i.height = h +} + +// GetFgColor returns the foreground color +func (i *Label) GetFgColor() termbox.Attribute { return i.fg } + +// SetFgColor sets the foreground color +func (i *Label) SetFgColor(fg termbox.Attribute) { + i.fg = fg +} + +// GetBgColor returns the background color +func (i *Label) GetBgColor() termbox.Attribute { return i.bg } + +// SetBgColor sets the current background color +func (i *Label) SetBgColor(bg termbox.Attribute) { + i.bg = bg +} + +// IsBordered returns true or false if this input field has a border +func (i *Label) IsBordered() bool { return i.bordered } + +// SetBordered sets whether we render a border around the input field +func (i *Label) SetBordered(b bool) { + i.bordered = b +} + +// DoesWrap returns true or false if this input field wraps text +func (i *Label) DoesWrap() bool { return i.wrap } + +// SetWrap sets whether we wrap the text at width. +func (i *Label) SetWrap(b bool) { + i.wrap = b +} + +// IsMultiline returns true or false if this field can have multiple lines +func (i *Label) IsMultiline() bool { return i.multiline } + +// SetMultiline sets whether the field can have multiple lines +func (i *Label) SetMultiline(b bool) { + i.multiline = b +} + +// HandleEvent accepts the termbox event and returns whether it was consumed +func (i *Label) HandleEvent(event termbox.Event) bool { return false } + +// Draw outputs the input field on the screen +func (i *Label) 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.GetWidth(), i.y+i.height, i.fg, i.bg) + maxWidth-- + maxHeight-- + x++ + y++ + startX++ + startY++ + } + + DrawStringAtPoint(i.value, x, y, i.fg, i.bg) +} diff --git a/termbox_menu.go b/termbox_menu.go index cf96e10..87d63ed 100644 --- a/termbox_menu.go +++ b/termbox_menu.go @@ -1,9 +1,14 @@ package termboxUtil -import "github.com/nsf/termbox-go" +import ( + "strings" + + "github.com/nsf/termbox-go" +) // Menu is a menu with a list of options type Menu struct { + id string title string options []MenuOption // If height is -1, then it is adaptive to the menu @@ -16,6 +21,8 @@ type Menu struct { isDone bool bordered bool vimMode bool + tabSkip bool + active bool } // CreateMenu Creates a menu with the specified attributes @@ -35,6 +42,14 @@ func CreateMenu(title string, options []string, x, y, width, height int, fg, bg return &i } +// GetID returns this control's ID +func (i *Menu) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *Menu) SetID(newID string) { + i.id = newID +} + // GetTitle returns the current title of the menu func (i *Menu) GetTitle() string { return i.title } @@ -193,20 +208,20 @@ func (i *Menu) ShowHelp(b bool) { i.showHelp = b } -// GetBackground returns the current background color -func (i *Menu) GetBackground() termbox.Attribute { return i.bg } +// GetFgColor returns the foreground color +func (i *Menu) GetFgColor() termbox.Attribute { return i.fg } -// SetBackground sets the background color to bg -func (i *Menu) SetBackground(bg termbox.Attribute) { - i.bg = bg +// SetFgColor sets the foreground color +func (i *Menu) SetFgColor(fg termbox.Attribute) { + i.fg = fg } -// GetForeground returns the current foreground color -func (i *Menu) GetForeground() termbox.Attribute { return i.fg } +// GetBgColor returns the background color +func (i *Menu) GetBgColor() termbox.Attribute { return i.bg } -// SetForeground sets the current foreground color to fg -func (i *Menu) SetForeground(fg termbox.Attribute) { - i.fg = fg +// SetBgColor sets the current background color +func (i *Menu) SetBgColor(bg termbox.Attribute) { + i.bg = bg } // IsDone returns whether the user has answered the modal @@ -235,29 +250,39 @@ func (i *Menu) DisableVimMode() { i.vimMode = false } -// HandleKeyPress handles the termbox event and returns whether it was consumed -func (i *Menu) HandleKeyPress(event termbox.Event) bool { - if event.Key == termbox.KeyEnter || event.Key == termbox.KeySpace { - i.isDone = true - return true - } - currentIdx := i.GetSelectedIndex() - switch event.Key { - case termbox.KeyArrowUp: - i.SelectPrevOption() - case termbox.KeyArrowDown: - i.SelectNextOption() - } - if i.vimMode { - switch event.Ch { - case 'j': - i.SelectNextOption() - case 'k': - i.SelectPrevOption() +// SetActiveFlag sets this control's active flag +func (i *Menu) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *Menu) IsActive() bool { return i.active } + +// HandleEvent handles the termbox event and returns whether it was consumed +func (i *Menu) HandleEvent(event termbox.Event) bool { + if i.active { + if event.Key == termbox.KeyEnter || event.Key == termbox.KeySpace { + i.isDone = true + return true + } + currentIdx := i.GetSelectedIndex() + switch event.Key { + case termbox.KeyArrowUp: + i.SelectPrevOption() + case termbox.KeyArrowDown: + i.SelectNextOption() + } + if i.vimMode { + switch event.Ch { + case 'j': + i.SelectNextOption() + case 'k': + i.SelectPrevOption() + } + } + if i.GetSelectedIndex() != currentIdx { + return true } - } - if i.GetSelectedIndex() != currentIdx { - return true } return false } @@ -325,16 +350,55 @@ func (i *Menu) Draw() { } } } + i.DrawOptions(optionStartX, optionStartY, optionHeight, optionWidth) +} + +// DrawOptions draws the menu options at x, y +func (i *Menu) DrawOptions(x, y, h, w int) { + DrawStringAtPoint(strings.Repeat("-", w), x, y, i.disabledFg, i.disabledBg) + y++ + if len(i.options) > 0 { + // If the currently selected option is disabled, move to the next + if i.GetSelectedOption().IsDisabled() { + i.SelectNextOption() + } + + // Print the options + for idx := range i.options { + if i.GetSelectedIndex()-idx >= h-1 { + // Skip this one + continue + } + currOpt := &i.options[idx] + outTxt := currOpt.GetText() + if len(outTxt) >= i.width { + outTxt = outTxt[:i.width] + } + if currOpt.IsDisabled() { + DrawStringAtPoint(outTxt, x, y, i.disabledFg, i.disabledBg) + } else if i.GetSelectedOption() == currOpt { + DrawStringAtPoint(AlignText(outTxt, w, AlignLeft), x, y, i.selectedFg, i.selectedBg) + } else { + DrawStringAtPoint(outTxt, x, y, i.fg, i.bg) + } + y++ + if y > i.y+h-1 { + break + } + } + } } /* MenuOption Struct & methods */ // MenuOption An option in the menu type MenuOption struct { + id string text string selected bool disabled bool helpText string + subMenu []MenuOption } // CreateOptionFromText just returns a MenuOption object @@ -388,3 +452,8 @@ func (i *MenuOption) SetHelpText(s string) { // GetHelpText Returns the help text for this option func (i *MenuOption) GetHelpText() string { return i.helpText } + +// AddToSubMenu adds a slice of MenuOptions to this option +func (i *MenuOption) AddToSubMenu(sub *MenuOption) { + i.subMenu = append(i.subMenu, *sub) +} diff --git a/termbox_progressbar.go b/termbox_progressbar.go index 16aaef0..4858994 100644 --- a/termbox_progressbar.go +++ b/termbox_progressbar.go @@ -4,6 +4,7 @@ import "github.com/nsf/termbox-go" // ProgressBar Just contains the data needed to display a progress bar type ProgressBar struct { + id string total int progress int allowOverflow bool @@ -17,6 +18,7 @@ type ProgressBar struct { x, y int width, height int bg, fg termbox.Attribute + active bool } // CreateProgressBar Create a progress bar object @@ -30,6 +32,22 @@ func CreateProgressBar(tot, x, y int, fg, bg termbox.Attribute) *ProgressBar { return &i } +// SetActiveFlag sets this control's active flag +func (i *ProgressBar) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *ProgressBar) IsActive() bool { return i.active } + +// GetID returns this control's ID +func (i *ProgressBar) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *ProgressBar) SetID(newID string) { + i.id = newID +} + // GetProgress returns the curret progress value func (i *ProgressBar) GetProgress() int { return i.progress @@ -138,20 +156,20 @@ func (i *ProgressBar) SetWidth(w int) { i.width = w } -// GetBackground Return the current background color of the modal -func (i *ProgressBar) GetBackground() termbox.Attribute { return i.bg } +// GetFgColor returns the foreground color +func (i *ProgressBar) GetFgColor() termbox.Attribute { return i.fg } -// SetBackground Set the current background color to bg -func (i *ProgressBar) SetBackground(bg termbox.Attribute) { - i.bg = bg +// SetFgColor sets the foreground color +func (i *ProgressBar) SetFgColor(fg termbox.Attribute) { + i.fg = fg } -// GetForeground Return the current foreground color -func (i *ProgressBar) GetForeground() termbox.Attribute { return i.fg } +// GetBgColor returns the background color +func (i *ProgressBar) GetBgColor() termbox.Attribute { return i.bg } -// SetForeground Set the foreground color to fg -func (i *ProgressBar) SetForeground(fg termbox.Attribute) { - i.fg = fg +// SetBgColor sets the current background color +func (i *ProgressBar) SetBgColor(bg termbox.Attribute) { + i.bg = bg } // Align Tells which direction the progress bar empties @@ -168,8 +186,8 @@ func (i *ProgressBar) SetColorized(c bool) { i.colorized = c } -// HandleKeyPress accepts the termbox event and returns whether it was consumed -func (i *ProgressBar) HandleKeyPress(event termbox.Event) bool { +// HandleEvent accepts the termbox event and returns whether it was consumed +func (i *ProgressBar) HandleEvent(event termbox.Event) bool { return false } diff --git a/termbox_scrollframe.go b/termbox_scrollframe.go index 5d31f13..abfb2ff 100644 --- a/termbox_scrollframe.go +++ b/termbox_scrollframe.go @@ -5,12 +5,14 @@ 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 { + id string x, y, width, height int scrollX, scrollY int tabIdx int fg, bg termbox.Attribute bordered bool controls []termboxControl + active bool } // CreateScrollFrame creates Scrolling Frame at x, y that is w by h @@ -19,92 +21,124 @@ func CreateScrollFrame(x, y, w, h int, fg, bg termbox.Attribute) *ScrollFrame { return &s } +// SetActiveFlag sets this control's active flag +func (i *ScrollFrame) SetActiveFlag(b bool) { + i.active = b +} + +// IsActive returns whether this control is active +func (i *ScrollFrame) IsActive() bool { return i.active } + +// GetID returns this control's ID +func (i *ScrollFrame) GetID() string { return i.id } + +// SetID sets this control's ID +func (i *ScrollFrame) SetID(newID string) { + i.id = newID +} + // GetX returns the x position of the scroll frame -func (s *ScrollFrame) GetX() int { return s.x } +func (i *ScrollFrame) GetX() int { return i.x } // SetX sets the x position of the scroll frame -func (s *ScrollFrame) SetX(x int) { - s.x = x +func (i *ScrollFrame) SetX(x int) { + i.x = x } // GetY returns the y position of the scroll frame -func (s *ScrollFrame) GetY() int { return s.y } +func (i *ScrollFrame) GetY() int { return i.y } // SetY sets the y position of the scroll frame -func (s *ScrollFrame) SetY(y int) { - s.y = y +func (i *ScrollFrame) SetY(y int) { + i.y = y } // GetWidth returns the current width of the scroll frame -func (s *ScrollFrame) GetWidth() int { return s.width } +func (i *ScrollFrame) GetWidth() int { return i.width } // SetWidth sets the current width of the scroll frame -func (s *ScrollFrame) SetWidth(w int) { - s.width = w +func (i *ScrollFrame) SetWidth(w int) { + i.width = w } // GetHeight returns the current height of the scroll frame -func (s *ScrollFrame) GetHeight() int { return s.height } +func (i *ScrollFrame) GetHeight() int { return i.height } // SetHeight sets the current height of the scroll frame -func (s *ScrollFrame) SetHeight(h int) { - s.height = h +func (i *ScrollFrame) SetHeight(h int) { + i.height = h +} + +// GetFgColor returns the foreground color +func (i *ScrollFrame) GetFgColor() termbox.Attribute { return i.fg } + +// SetFgColor sets the foreground color +func (i *ScrollFrame) SetFgColor(fg termbox.Attribute) { + i.fg = fg +} + +// GetBgColor returns the background color +func (i *ScrollFrame) GetBgColor() termbox.Attribute { return i.bg } + +// SetBgColor sets the current background color +func (i *ScrollFrame) SetBgColor(bg termbox.Attribute) { + i.bg = bg } // IsBordered returns true or false if this scroll frame has a border -func (s *ScrollFrame) IsBordered() bool { return s.bordered } +func (i *ScrollFrame) IsBordered() bool { return i.bordered } // SetBordered sets whether we render a border around the scroll frame -func (s *ScrollFrame) SetBordered(b bool) { - s.bordered = b +func (i *ScrollFrame) SetBordered(b bool) { + i.bordered = b } // GetScrollX returns the x distance scrolled -func (s *ScrollFrame) GetScrollX() int { - return s.scrollX +func (i *ScrollFrame) GetScrollX() int { + return i.scrollX } // GetScrollY returns the y distance scrolled -func (s *ScrollFrame) GetScrollY() int { - return s.scrollY +func (i *ScrollFrame) GetScrollY() int { + return i.scrollY } // ScrollDown scrolls the frame down -func (s *ScrollFrame) ScrollDown() { - s.scrollY++ +func (i *ScrollFrame) ScrollDown() { + i.scrollY++ } // ScrollUp scrolls the frame up -func (s *ScrollFrame) ScrollUp() { - if s.scrollY > 0 { - s.scrollY-- +func (i *ScrollFrame) ScrollUp() { + if i.scrollY > 0 { + i.scrollY-- } } // ScrollLeft scrolls the frame left -func (s *ScrollFrame) ScrollLeft() { - if s.scrollX > 0 { - s.scrollX-- +func (i *ScrollFrame) ScrollLeft() { + if i.scrollX > 0 { + i.scrollX-- } } // ScrollRight scrolls the frame right -func (s *ScrollFrame) ScrollRight() { - s.scrollX++ +func (i *ScrollFrame) ScrollRight() { + i.scrollX++ } // AddControl adds a control to the frame -func (s *ScrollFrame) AddControl(t termboxControl) { - s.controls = append(s.controls, t) +func (i *ScrollFrame) AddControl(t termboxControl) { + i.controls = append(i.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) { +func (i *ScrollFrame) DrawControl(t termboxControl) { + if i.IsVisible(t) { ctlX, ctlY := t.GetX(), t.GetY() - t.SetX((s.GetX() + ctlX)) - t.SetY((s.GetY() + ctlY)) + t.SetX((i.GetX() + ctlX)) + t.SetY((i.GetY() + ctlY)) t.Draw() t.SetX(ctlX) t.SetY(ctlY) @@ -113,35 +147,35 @@ func (s *ScrollFrame) DrawControl(t termboxControl) { // IsVisible takes a Termbox Control and returns whether // that control would be visible in the frame -func (s *ScrollFrame) IsVisible(t termboxControl) bool { +func (i *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 + if cX+t.GetWidth() >= i.scrollX && cX <= i.scrollX+i.width { + return cY+t.GetHeight() >= i.scrollY && cY <= i.scrollY+i.height } return false } -// HandleKeyPress accepts the termbox event and returns whether it was consumed -func (s *ScrollFrame) HandleKeyPress(event termbox.Event) bool { +// HandleEvent accepts the termbox event and returns whether it was consumed +func (i *ScrollFrame) HandleEvent(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 { +func (i *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) +func (i *ScrollFrame) 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++ @@ -149,7 +183,7 @@ func (s *ScrollFrame) Draw() { startX++ startY++ } - for i := range s.controls { - s.DrawControl(s.controls[i]) + for idx := range i.controls { + i.DrawControl(i.controls[idx]) } } diff --git a/termbox_util.go b/termbox_util.go index 25d12a9..fc4cb3e 100644 --- a/termbox_util.go +++ b/termbox_util.go @@ -8,6 +8,7 @@ import ( ) type termboxControl interface { + GetID() string GetX() int SetX(int) GetY() int @@ -16,7 +17,17 @@ type termboxControl interface { SetWidth(int) GetHeight() int SetHeight(int) - HandleKeyPress(termbox.Event) bool + GetFgColor() termbox.Attribute + SetFgColor(termbox.Attribute) + GetBgColor() termbox.Attribute + SetBgColor(termbox.Attribute) + HandleEvent(termbox.Event) bool + IsBordered() bool + SetBordered(bool) + SetTabSkip(bool) + IsTabSkipped() bool + IsActive() bool + SetActiveFlag(bool) Draw() } @@ -112,21 +123,28 @@ func DrawBorder(x1, y1, x2, y2 int, fg termbox.Attribute, bg termbox.Attribute) // AlignText Aligns the text txt within width characters using the specified alignment func AlignText(txt string, width int, align TextAlignment) string { + return AlignTextWithFill(txt, width, align, ' ') +} + +// AlignTextWithFill Aligns the text txt within width characters using the specified alignment +// filling any spaces with the 'fill' character +func AlignTextWithFill(txt string, width int, align TextAlignment, fill rune) string { + fillChar := string(fill) numSpaces := width - len(txt) switch align { case AlignCenter: if numSpaces/2 > 0 { return fmt.Sprintf("%s%s%s", - strings.Repeat(" ", numSpaces/2), - txt, strings.Repeat(" ", numSpaces/2), + strings.Repeat(fillChar, numSpaces/2), + txt, strings.Repeat(fillChar, numSpaces/2), ) } return txt case AlignRight: - return fmt.Sprintf("%s%s", strings.Repeat(" ", numSpaces), txt) + return fmt.Sprintf("%s%s", strings.Repeat(fillChar, numSpaces), txt) default: if numSpaces >= 0 { - return fmt.Sprintf("%s%s", txt, strings.Repeat(" ", numSpaces)) + return fmt.Sprintf("%s%s", txt, strings.Repeat(fillChar, numSpaces)) } return txt }