diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/TUI.iml b/.idea/TUI.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/TUI.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..104c42f
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..c400fe6
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ansi.go b/ansi.go
new file mode 100644
index 0000000..84e41fe
--- /dev/null
+++ b/ansi.go
@@ -0,0 +1,64 @@
+package tui
+
+import (
+ "fmt"
+ "golang.org/x/term"
+ "os"
+ "strings"
+)
+
+type Colors = string
+type Fmt = string
+
+// Formatting
+const (
+ FmtReset Colors = "\033[0m"
+ FmtBold = "\033[1m"
+ FmtBoldReset = "\033[22m"
+ FmtDim = "\033[2m"
+ FmtDimReset = FmtBoldReset
+ FmtItalic = "\033[3m"
+ FmtItalicReset = "\033[23m"
+ FmtUnderline = "\033[4m"
+ FmtUnderlineReset = "\033[24m"
+ FmtBlink = "\033[5m"
+ FmtBlinkReset = "\033[25m"
+ FmtReverse = "\033[7m"
+ FmtReverseReset = "\033[27m"
+ FmtHidden = "\033[8m"
+ FmtHiddenReset = "\033[28m"
+ FmtStrikethrough = "\033[9m"
+ FmtStrikethroughReset = "\033[29m"
+)
+
+// Foreground Colors
+const (
+ FgColorGrey Fmt = "\033[38;5;247m"
+ FgColorGold = "\033[38;5;214m"
+ FgColorGreen = "\033[38;5;34m"
+ FgColorRed = "\033[38;5;167m"
+)
+
+func BlankLine() {
+
+ width, _, _ := term.GetSize(int(os.Stdin.Fd()))
+
+ // Assume ptero terminal if no width.
+ if width == 0 {
+ fmt.Print("\033[1B")
+ } else {
+ fmt.Println("")
+ }
+
+}
+
+func Format(codes ...string) string {
+
+ var str strings.Builder
+
+ for _, code := range codes {
+ str.WriteString(code)
+ }
+
+ return str.String()
+}
diff --git a/confirmation/confirmation.go b/confirmation/confirmation.go
new file mode 100644
index 0000000..a43e6b5
--- /dev/null
+++ b/confirmation/confirmation.go
@@ -0,0 +1,113 @@
+package confirmation
+
+import (
+ "fmt"
+ "gitlab.com/omnibill/tui"
+ "strings"
+
+ "golang.org/x/term"
+)
+
+type InputData struct {
+ Notice string
+ Question string
+}
+
+func New(data InputData) (*bool, error) {
+
+ if data.Notice != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + data.Notice + tui.FmtReset + "\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3m%s\033[0m\n", data.Notice)
+ }
+
+ if data.Question != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtReset + " " +
+ data.Question + " " +
+ "(" + tui.FgColorGreen + "y" + tui.FmtReset + "/" + tui.FgColorRed + "n" + tui.FmtReset + ") " +
+ tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m?\033[38;5;247m]\033[0m %s (\033[38;5;34my\033[0m/\033[38;5;167mn\033[0m) \033[38;5;247m>>\033[3m\033[38;5;214m\n", data.Question)
+ }
+
+ var input string
+ var chosen bool
+
+inputLoop:
+ for {
+ if _, err := fmt.Scanln(&input); err != nil {
+ return nil, err
+ }
+
+ width, _, err := term.GetSize(0)
+ if err != nil {
+ return nil, err
+ }
+
+ switch strings.ToLower(input) {
+ case "y", "yes":
+ chosen = true
+ break inputLoop
+ case "n", "no":
+ chosen = false
+ break inputLoop
+ default:
+ var lineNum int
+
+ if width == 0 {
+ if data.Notice != "" {
+ lineNum++
+ }
+ lineNum += 2
+ } else {
+ if data.Notice == "" {
+ lineNum = ((len(data.Question) + 5 + width - 1) / width) + 1
+ } else {
+ lineNum = ((len(data.Notice) + 5 + width) / width) + ((len(data.Question) + 5 + width - 1) / width) + 1
+ }
+
+ }
+
+ for i := 0; i < lineNum; i++ {
+ fmt.Printf("\033[A\033[K\033[0G")
+ }
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtReset + " " +
+ "Invalid input, please try again!\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;167m!\033[38;5;247m]\033[0m Invalid input, please try again!\n")
+
+ if data.Notice != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + data.Notice + tui.FmtReset + "\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3m%s\033[0m\n", data.Notice)
+ }
+
+ if data.Question != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtReset + " " +
+ data.Question + " " +
+ "(" + tui.FgColorGreen + "y" + tui.FmtReset + "/" + tui.FgColorRed + "n" + tui.FmtReset + ") " +
+ tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m?\033[38;5;247m]\033[0m %s (\033[38;5;34my\033[0m/\033[38;5;167mn\033[0m) \033[38;5;247m>>\033[3m\033[38;5;214m\n", data.Question)
+ }
+ continue inputLoop
+ }
+ }
+
+ fmt.Println("\033[0m")
+
+ return &chosen, nil
+
+}
diff --git a/displaylist/displaylist.go b/displaylist/displaylist.go
new file mode 100644
index 0000000..e6f719e
--- /dev/null
+++ b/displaylist/displaylist.go
@@ -0,0 +1,31 @@
+package displaylist
+
+import (
+ "fmt"
+
+ "git.eggactyl.cloud/Eggactyl/tui"
+)
+
+type ListOptions struct {
+ Title string
+ Items []Item
+}
+
+type Item struct {
+ Key string
+ Value string
+}
+
+func New(opts ListOptions) {
+
+ fmt.Println(tui.FgColorGrey + "[ " + tui.Format(tui.FgColorGold, tui.FmtUnderline) + opts.Title + tui.Format(tui.FmtUnderlineReset, tui.FgColorGrey) + " ]" + tui.FmtReset)
+
+ for _, item := range opts.Items {
+ fmt.Println(itemString(item.Key, item.Value))
+ }
+
+}
+
+func itemString(key string, value string) string {
+ return tui.FgColorGrey + "[ " + tui.FgColorGold + key + " " + tui.FgColorGrey + value + tui.FgColorGrey + " ]" + tui.FmtReset
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..5392218
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,13 @@
+module gitlab.com/omnibill/tui
+
+go 1.23.2
+
+require (
+ github.com/nicksnyder/go-i18n/v2 v2.4.1
+ golang.org/x/term v0.25.0
+)
+
+require (
+ golang.org/x/sys v0.26.0 // indirect
+ golang.org/x/text v0.19.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..7ffa050
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,14 @@
+git.eggactyl.cloud/Eggactyl/tui v0.0.0-20240808144724-4c617ba4b5e2 h1:Moamm0JZr2J+DnkxNonZMmTjIC6lZxMMKQ93sPs4OfU=
+git.eggactyl.cloud/Eggactyl/tui v0.0.0-20240808144724-4c617ba4b5e2/go.mod h1:nDnIAL3nj3ZRfJIjESD752RtHbInjn2ASxBeeRgSv1g=
+github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
+github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g=
+github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
+golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/list/list.go b/list/list.go
new file mode 100644
index 0000000..7823603
--- /dev/null
+++ b/list/list.go
@@ -0,0 +1,345 @@
+package list
+
+import (
+ "bufio"
+ "fmt"
+ "gitlab.com/omnibill/tui"
+ "os"
+ "regexp"
+ "sort"
+ "strconv"
+
+ "github.com/nicksnyder/go-i18n/v2/i18n"
+ "golang.org/x/term"
+)
+
+var regex = regexp.MustCompile("\033\\[[0-9;]+m")
+
+func removeANSIEscapeCodes(input string) string {
+ return regex.ReplaceAllString(input, "")
+}
+
+type ListData struct {
+ pages []ListPage
+ history []int
+ currentPage int
+ strLengths []int
+ localizer *i18n.Localizer
+}
+
+type ListPage struct {
+ Title string
+ Items []ListItem
+ Render func() []ListItem
+ Cache []ListItem
+}
+
+type ListItem struct {
+ Label string
+ Notice string
+ Order int
+ Render func() ListItem
+ LinkTo int
+ Value interface{}
+}
+
+func New(localizer *i18n.Localizer) ListData {
+ return ListData{
+ pages: []ListPage{},
+ history: []int{},
+ localizer: localizer,
+ }
+}
+
+func (l *ListData) Execute() interface{} {
+ return l.listRunner()
+}
+
+func (l *ListData) AddPage(page ListPage) {
+ l.pages = append(l.pages, page)
+}
+
+func (l *ListData) listRunner() interface{} {
+
+ var option interface{}
+
+ for {
+
+ var tempItemStore []ListItem
+
+ for _, item := range l.pages[l.currentPage].Items {
+
+ if len(l.pages[l.currentPage].Cache) == 0 {
+ if item.Render != nil {
+ renderedItem := item.Render()
+ renderedItem.Value = item.Value
+ tempItemStore = append(tempItemStore, renderedItem)
+ } else {
+ tempItemStore = append(tempItemStore, item)
+ }
+
+ sort.SliceStable(tempItemStore, func(i, j int) bool {
+ return tempItemStore[i].Order < tempItemStore[j].Order
+ })
+ }
+
+ }
+
+ if len(tempItemStore) != 0 {
+ l.pages[l.currentPage].Cache = append(l.pages[l.currentPage].Cache, tempItemStore...)
+
+ if l.currentPage != 0 {
+ l.pages[l.currentPage].Cache = append(
+ l.pages[l.currentPage].Cache,
+ []ListItem{
+ {
+ Label: "Back",
+ Value: "action_back",
+ },
+ }...,
+ )
+ }
+
+ }
+
+ l.renderList()
+
+ chosen := l.inputHandler(l.pages[l.currentPage].Cache)
+
+ if chosen.LinkTo != 0 {
+
+ width, _, _ := term.GetSize(0)
+
+ var totalLineNum int
+
+ if width == 0 {
+ totalLineNum = len(l.strLengths)
+ } else {
+ for _, strLength := range l.strLengths {
+ totalLineNum += ((strLength) / width)
+ }
+ }
+ totalLineNum++ //User input line
+
+ for i := 0; i < totalLineNum; i++ {
+ fmt.Printf("\033[A\033[K\033[0G")
+ }
+
+ l.history = append(l.history, l.currentPage)
+ l.currentPage = chosen.LinkTo
+
+ continue
+
+ }
+
+ if chosen.Value != nil {
+
+ if _, ok := chosen.Value.(string); ok {
+ if chosen.Value == "action_home" {
+
+ l.currentPage = 0
+ l.history = []int{}
+
+ width, _, _ := term.GetSize(0)
+
+ var totalLineNum int
+
+ if width == 0 {
+ totalLineNum = len(l.strLengths)
+ } else {
+ for _, strLength := range l.strLengths {
+ totalLineNum += ((strLength) / width)
+ }
+ }
+ totalLineNum++ //User input line
+
+ for i := 0; i < totalLineNum; i++ {
+ fmt.Printf("\033[A\033[K\033[0G")
+ }
+
+ continue
+
+ }
+
+ if chosen.Value == "action_back" {
+
+ l.currentPage = l.history[len(l.history)-1]
+
+ if len(l.history) == 1 {
+ l.history = []int{}
+ } else {
+ l.history = l.history[0 : len(l.history)-2]
+ }
+
+ width, _, _ := term.GetSize(0)
+
+ var totalLineNum int
+
+ if width == 0 {
+ totalLineNum = len(l.strLengths)
+ } else {
+ for _, strLength := range l.strLengths {
+ totalLineNum += ((strLength) / width)
+ }
+ }
+ totalLineNum++ //User input line
+
+ for i := 0; i < totalLineNum; i++ {
+ fmt.Printf("\033[A\033[K\033[0G")
+ }
+
+ continue
+
+ }
+ }
+
+ option = chosen.Value
+ break
+
+ }
+
+ }
+
+ return option
+
+}
+
+func (l *ListData) renderList() {
+
+ l.strLengths = []int{}
+
+ currentPage := l.pages[l.currentPage]
+
+ listNotice := tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + fmt.Sprintf("Please choose an option from 1 - %d", len(currentPage.Cache)) + "\n"
+
+ //listNotice := fmt.Sprintf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3mPlease choose an option from 1 - %d\033[0m\n", len(currentPage.Cache))
+ l.strLengths = append(l.strLengths, len(removeANSIEscapeCodes(listNotice)))
+ fmt.Print(listNotice)
+
+ listTitle := tui.FgColorGrey + "[ " + tui.Format(tui.FgColorGold, tui.FmtUnderline) + currentPage.Title + ":" + tui.Format(tui.FmtUnderlineReset, tui.FgColorGrey) + " ]" + tui.FmtReset + "\n"
+ l.strLengths = append(l.strLengths, len(removeANSIEscapeCodes(listTitle)))
+ fmt.Print(listTitle)
+
+ longestStrLength := 0
+
+ for _, item := range currentPage.Cache {
+ formattedLabel := removeANSIEscapeCodes(item.Label)
+ if len([]rune(formattedLabel)) > longestStrLength {
+ longestStrLength = len([]rune(formattedLabel))
+ }
+ }
+
+ for index, item := range currentPage.Cache {
+ var listItem string
+
+ var userInputColor string
+ if index == len(currentPage.Cache)-1 {
+ userInputColor = tui.Format(tui.FmtItalic, tui.FgColorGrey)
+ }
+
+ if _, ok := item.Value.(string); ok {
+ if item.Value == "action_back" {
+ listItem = " " + tui.FgColorGrey + "[" + tui.Format(tui.FmtBold, tui.FgColorRed) + strconv.Itoa(index+1) + tui.Format(tui.FmtBoldReset, tui.FgColorGrey) + "]" + tui.FmtReset +
+ fmt.Sprintf(" %-*s ", longestStrLength, item.Label) + tui.Format(tui.FmtItalic, tui.FgColorGrey) + item.Notice + tui.FmtReset + userInputColor + "\n"
+ //listItem = fmt.Sprintf(" \033[38;5;247m[\033[1m\033[38;5;167m%d\033[22m\033[38;5;247m]\033[0m %-*s \033[3m\033[38;5;247m%s\033[0m%s\n", index+1, longestStrLength, item.Label, item.Notice, userInputColor)
+ } else {
+ listItem = " " + tui.FgColorGrey + "[" + tui.Format(tui.FmtBold, tui.FgColorGold) + strconv.Itoa(index+1) + tui.Format(tui.FmtBoldReset, tui.FgColorGrey) + "]" + tui.FmtReset +
+ fmt.Sprintf(" %-*s ", longestStrLength, item.Label) + tui.Format(tui.FmtItalic, tui.FgColorGrey) + item.Notice + tui.FmtReset + userInputColor + "\n"
+ //listItem = fmt.Sprintf(" \033[38;5;247m[\033[1m\033[38;5;214m%d\033[22m\033[38;5;247m]\033[0m %-*s \033[3m\033[38;5;247m%s\033[0m%s\n", index+1, longestStrLength, item.Label, item.Notice, userInputColor)
+ }
+ } else {
+ listItem = " " + tui.FgColorGrey + "[" + tui.Format(tui.FmtBold, tui.FgColorGold) + strconv.Itoa(index+1) + tui.Format(tui.FmtBoldReset, tui.FgColorGrey) + "]" + tui.FmtReset +
+ fmt.Sprintf(" %-*s ", longestStrLength, item.Label) + tui.Format(tui.FmtItalic, tui.FgColorGrey) + item.Notice + tui.FmtReset + userInputColor + "\n"
+ //listItem = fmt.Sprintf(" \033[38;5;247m[\033[1m\033[38;5;214m%d\033[22m\033[38;5;247m]\033[0m %-*s \033[3m\033[38;5;247m%s\033[0m%s\n", index+1, longestStrLength, item.Label, item.Notice, userInputColor)
+ }
+
+ l.strLengths = append(l.strLengths, len(removeANSIEscapeCodes(listItem)))
+ fmt.Print(listItem)
+ }
+
+}
+
+func (l *ListData) inputHandler(items []ListItem) ListItem {
+
+ scanner := bufio.NewScanner(os.Stdin)
+ scanner.Split(bufio.ScanLines)
+
+ var input int
+
+ for scanner.Scan() {
+
+ text := scanner.Text()
+
+ inputAnswer, err := strconv.Atoi(text)
+ if err != nil {
+ width, _, _ := term.GetSize(0)
+
+ var totalLineNum int
+
+ if width == 0 {
+ totalLineNum = len(l.strLengths)
+ } else {
+ for _, strLength := range l.strLengths {
+ totalLineNum += (strLength) / width
+ }
+ }
+ totalLineNum++ //User input line
+
+ for i := 0; i < totalLineNum; i++ {
+ fmt.Printf("\033[0m\033[A\033[K\033[0G")
+ }
+
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorRed + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ " Invalid input, please try again!\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;167m!\033[38;5;247m]\033[0m Invalid input, please try again!\n")
+ l.strLengths = []int{}
+
+ l.renderList()
+ continue
+ }
+
+ if inputAnswer < 1 || inputAnswer > len(items) {
+ width, _, _ := term.GetSize(0)
+
+ var totalLineNum int
+
+ if width == 0 {
+ totalLineNum = len(l.strLengths)
+ } else {
+ for _, strLength := range l.strLengths {
+ totalLineNum += (strLength) / width
+ }
+ }
+ totalLineNum++ //User input line
+
+ for i := 0; i < totalLineNum; i++ {
+ fmt.Printf("\033[A\033[K\033[0G")
+ }
+
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorRed + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ " Invalid input, please try again!\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;167m!\033[38;5;247m]\033[0m Invalid input, please try again!\n")
+ l.strLengths = []int{}
+
+ l.renderList()
+ continue
+ }
+
+ input = inputAnswer
+
+ break
+
+ }
+
+ chosenItem := items[input-1]
+
+ return chosenItem
+}
diff --git a/progress/progress.go b/progress/progress.go
new file mode 100644
index 0000000..7dfd15d
--- /dev/null
+++ b/progress/progress.go
@@ -0,0 +1,190 @@
+package progress
+
+import (
+ "fmt"
+ "git.eggactyl.cloud/Eggactyl/tui"
+ "math"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+
+ "golang.org/x/term"
+)
+
+var regex = regexp.MustCompile("\033\\[[0-9;]+m")
+
+//║░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░║ 40 characters
+
+type ProgressInfo struct {
+ Size int64
+ Desc string
+ ClearOnFinish bool
+}
+
+type ProgressBar struct {
+ lock sync.Mutex
+ maxBytes int64
+ currentBytes int64
+ hasStarted bool
+ currentPercent int
+ desc string
+ clearFinish bool
+ textWidth int
+}
+
+func New(info ProgressInfo) *ProgressBar {
+ return &ProgressBar{
+ maxBytes: info.Size,
+ desc: info.Desc,
+ clearFinish: info.ClearOnFinish,
+ }
+
+}
+
+func (p *ProgressBar) render(final bool) {
+
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ var sb strings.Builder
+
+ numFilled := math.Round((float64(p.currentBytes) / float64(p.maxBytes)) * 40)
+ numBlank := 40 - numFilled
+ donePercent := math.Round(float64(p.currentBytes) / float64(p.maxBytes) * 100)
+
+ if int(donePercent) == p.currentPercent {
+ return
+ }
+
+ var blockColor string
+
+ if final {
+ blockColor = tui.FgColorGreen
+ } else {
+ blockColor = tui.FgColorGold
+ }
+
+ sb.WriteString(p.desc)
+ sb.WriteString(" ")
+
+ percentString := strconv.Itoa(int(donePercent)) + "%"
+ blankPercent := 4 - len(percentString)
+
+ for i := 0; i < blankPercent; i++ {
+ sb.WriteString(" ")
+ }
+
+ sb.WriteString(percentString)
+
+ sb.WriteString(tui.Format(tui.FmtBold, tui.FgColorGrey) + " [" + tui.FmtReset)
+ //sb.WriteString("\033[1m\033[38;5;247m [\033[0m")
+
+ for i := 0; i < int(numFilled); i++ {
+
+ sb.WriteString(blockColor + "█" + tui.FmtReset)
+ //sb.WriteString(fmt.Sprintf("%sm█\033[0m", blockColor))
+
+ }
+
+ if numFilled < 40 {
+
+ numBlank = numBlank - 1
+
+ sb.WriteString(tui.FgColorGold + "▒" + tui.FmtReset)
+ //sb.WriteString("\033[38;5;214m▒\033[0m")
+
+ for i := 0; i < int(numBlank); i++ {
+ sb.WriteString(tui.FgColorGrey + "░" + tui.FmtReset)
+ //sb.WriteString("\033[38;5;247m░\033[0m")
+ }
+
+ } else {
+
+ for i := 0; i < int(numBlank); i++ {
+ sb.WriteString(tui.FgColorGrey + "░" + tui.FmtReset)
+ //sb.WriteString("\033[38;5;247m░\033[0m")
+ }
+
+ }
+
+ sb.WriteString(tui.Format(tui.FmtBold, tui.FgColorGrey) + "]" + tui.FmtReset)
+ //sb.WriteString("\033[1m\033[38;5;247m]\033[0m")
+
+ width, _, err := term.GetSize(0)
+ if err != nil {
+ return
+ }
+
+ var lineNum int
+
+ if width == 0 {
+ lineNum = 1
+ } else {
+ lineNum = ((len(removeANSIEscapeCodes(sb.String())) + width - 1) / width)
+ }
+
+ if p.hasStarted {
+ for i := 0; i < lineNum; i++ {
+ if i == lineNum-1 {
+ fmt.Println("\033[A\033[0G\033[K" + sb.String())
+ } else {
+ fmt.Println("\033[A\033[0G\033[K")
+ }
+ }
+ } else {
+ fmt.Println(sb.String())
+ p.hasStarted = true
+ }
+
+ p.textWidth = len(removeANSIEscapeCodes(sb.String()))
+
+}
+
+func (p *ProgressBar) Add(num int64) {
+ p.currentBytes = p.currentBytes + num
+
+ p.render(false)
+}
+
+func (p *ProgressBar) Close() (err error) {
+
+ if p.clearFinish {
+
+ width, _, err := term.GetSize(0)
+ if err != nil {
+ return err
+ }
+
+ var lineNum int
+
+ if width == 0 {
+ lineNum = 1
+ } else {
+ lineNum = ((p.textWidth + width - 1) / width)
+ }
+
+ for i := 0; i < lineNum; i++ {
+ fmt.Println("\033[A\033[0G\033[K")
+ }
+
+ } else {
+ p.render(true)
+ }
+
+ return
+}
+func (p *ProgressBar) Write(b []byte) (n int, err error) {
+ n = len(b)
+ p.Add(int64(n))
+ return
+}
+
+func (p *ProgressBar) Read(b []byte) (n int, err error) {
+ p.Add(int64(n))
+ return
+}
+
+func removeANSIEscapeCodes(input string) string {
+ return regex.ReplaceAllString(input, "")
+}
diff --git a/textinput/text.go b/textinput/text.go
new file mode 100644
index 0000000..6efb51f
--- /dev/null
+++ b/textinput/text.go
@@ -0,0 +1,172 @@
+package textinput
+
+import (
+ "fmt"
+ "git.eggactyl.cloud/Eggactyl/tui"
+ "git.eggactyl.cloud/Eggactyl/tui/validators"
+
+ "golang.org/x/term"
+)
+
+type InputData struct {
+ Notice string
+ Question string
+ Validator validators.TextInputValidator
+ ValidationFunc func(input string) bool
+}
+
+func New(data InputData) (*string, error) {
+
+ if data.Notice != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + data.Notice + tui.FmtReset + "\n",
+ )
+ } else if data.Validator != nil && data.Validator.Notice() != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + data.Validator.Notice() + tui.FmtReset + "\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3m%s\033[0m\n", data.Validator.Notice())
+ }
+
+ if data.Question != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "?" + tui.FgColorGrey + "]" + tui.FmtReset + " " +
+ data.Question + " " + tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n",
+ )
+ }
+
+ var input string
+
+ for {
+
+ if _, err := fmt.Scanln(&input); err != nil {
+ return nil, err
+ }
+
+ width, _, err := term.GetSize(0)
+ if err != nil {
+ return nil, err
+ }
+
+ if data.ValidationFunc != nil {
+ if !data.ValidationFunc(input) {
+ var lineNum int
+
+ if width == 0 {
+ if data.Notice != "" {
+ lineNum++
+ }
+ lineNum += 2
+ } else {
+ if data.Notice == "" {
+ lineNum = ((len(data.Question) + 5 + width - 1) / width) + 1
+ } else {
+ lineNum = ((len(data.Notice) + 5 + width) / width) + ((len(data.Question) + 5 + width - 1) / width) + 1
+ }
+ }
+
+ for i := 0; i < lineNum; i++ {
+ fmt.Printf("\033[A\033[K\033[0G")
+ }
+
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorRed + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ " Invalid input, please try again!\n",
+ )
+
+ if data.Notice != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + data.Notice + tui.FmtReset + "\n",
+ )
+ } else if data.Validator != nil {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + data.Validator.Notice() + tui.FmtReset + "\n",
+ )
+ }
+
+ if data.Question != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "?" + tui.FgColorGrey + "]" + tui.FmtReset + " " +
+ data.Question + " " + tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n",
+ )
+ }
+ continue
+ }
+ } else if data.Validator != nil {
+ if !data.Validator.ValidationFunc(input) {
+ var lineNum int
+
+ if width == 0 {
+ if data.Notice != "" || data.Validator.Notice() != "" {
+ lineNum++
+ }
+ lineNum += 2
+ } else {
+ if data.Notice == "" && data.Validator.Notice() == "" {
+ lineNum = ((len(data.Question) + 5 + width - 1) / width) + 1
+ } else {
+ if data.Notice != "" {
+ lineNum = ((len(data.Notice) + 5 + width) / width) + ((len(data.Question) + 5 + width - 1) / width) + 1
+ } else if data.Validator.Notice() != "" {
+ lineNum = ((len(data.Validator.Notice()) + 5 + width - 1) / width) + ((len(data.Question) + 5 + width - 1) / width) + 1
+ }
+ }
+ }
+
+ for i := 0; i < lineNum; i++ {
+ fmt.Printf("\033[A\033[K\033[0G")
+ }
+
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorRed + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ " Invalid input, please try again!\n",
+ )
+
+ if data.Notice != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + data.Notice + tui.FmtReset + "\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3m%s\033[0m\n", data.Notice)
+ } else if data.Validator.Notice() != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " +
+ tui.FmtItalic + data.Validator.Notice() + tui.FmtReset + "\n",
+ )
+ }
+
+ if data.Question != "" {
+ fmt.Printf(
+ tui.Format(tui.FmtBold, tui.FgColorGrey) +
+ "[" + tui.FgColorGold + "?" + tui.FgColorGrey + "]" + tui.FmtReset + " " +
+ data.Question + " " + tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n",
+ )
+ //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m?\033[38;5;247m]\033[0m %s \033[38;5;247m>>\033[3m\033[38;5;214m\n", data.Question)
+ }
+ continue
+ }
+ }
+
+ break
+
+ }
+
+ fmt.Println("\033[0m")
+
+ return &input, nil
+
+}
diff --git a/validators/interface.go b/validators/interface.go
new file mode 100644
index 0000000..d0c6c67
--- /dev/null
+++ b/validators/interface.go
@@ -0,0 +1,6 @@
+package validators
+
+type TextInputValidator interface {
+ Notice() string
+ ValidationFunc(input string) bool
+}
diff --git a/validators/mc_version.go b/validators/mc_version.go
new file mode 100644
index 0000000..6d562dc
--- /dev/null
+++ b/validators/mc_version.go
@@ -0,0 +1,46 @@
+package validators
+
+import (
+ "fmt"
+)
+
+func McVersion(versions []string) TextInputValidator {
+ return TextInputMCVersion{
+ versions: versions,
+ }
+}
+
+type TextInputMCVersion struct {
+ versions []string
+}
+
+func (d TextInputMCVersion) Notice() string {
+ var versionString string
+
+ for index, version := range d.versions {
+ if index == (len(d.versions) - 1) {
+ versionString = versionString + version
+ } else {
+ versionString = versionString + version + ", "
+ }
+ }
+
+ return fmt.Sprintf("Available Versions: %s", versionString)
+}
+
+func (d TextInputMCVersion) ValidationFunc(input string) bool {
+
+ isFound := false
+
+ for _, ver := range d.versions {
+
+ if ver == input {
+ isFound = true
+ break
+ }
+
+ }
+
+ return isFound
+
+}
diff --git a/validators/range.go b/validators/range.go
new file mode 100644
index 0000000..1cea93b
--- /dev/null
+++ b/validators/range.go
@@ -0,0 +1,33 @@
+package validators
+
+import (
+ "fmt"
+ "strconv"
+)
+
+func Range(min int, max int) TextInputValidator {
+ return TextInputRange{
+ min: min,
+ max: max,
+ }
+}
+
+type TextInputRange struct {
+ min int
+ max int
+}
+
+func (d TextInputRange) Notice() string {
+ return fmt.Sprintf("Valid values: (%d - %d)", d.min, d.max)
+}
+
+func (d TextInputRange) ValidationFunc(input string) bool {
+ portNum, err := strconv.Atoi(input)
+ if err != nil {
+ return false
+ }
+ if portNum > 65535 || portNum < 1 {
+ return false
+ }
+ return true
+}