Well.. I created some sort of tui for GNU/Linux

This commit is contained in:
qowevisa 2024-03-14 00:06:13 +02:00
parent db5136a33e
commit 544bf2b219
9 changed files with 1117 additions and 0 deletions

47
tui/consts.go Normal file
View File

@ -0,0 +1,47 @@
package tui
const (
MY_SIGNAL_EXIT = iota
MY_SIGNAL_MESSAGE
MY_SIGNAL_CONNECT
)
const (
cursorPosGeneralCenter cursorPosConfigValue = -1
cursorPosGeneralLeft cursorPosConfigValue = -2
cursorPosGeneralRight cursorPosConfigValue = -3
)
const (
footerStart = "State: "
)
func (c *cursorPosConfigValue) isGeneral() bool {
switch *c {
case cursorPosGeneralCenter:
return true
case cursorPosGeneralLeft:
return true
case cursorPosGeneralRight:
return true
}
return false
}
const (
widgetPosGeneralCenter widgetPosConfigValue = -1
widgetPosGeneralLeftCenter widgetPosConfigValue = -2
widgetPosGeneralRightCenter widgetPosConfigValue = -3
)
func (w *widgetPosConfigValue) isGeneral() bool {
switch *w {
case widgetPosGeneralCenter:
return true
case widgetPosGeneralLeftCenter:
return true
case widgetPosGeneralRightCenter:
return true
}
return false
}

101
tui/funcs.go Normal file
View File

@ -0,0 +1,101 @@
package tui
import (
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"os"
"strconv"
"git.qowevisa.me/Qowevisa/gotell/env"
"git.qowevisa.me/Qowevisa/gotell/errors"
)
func GetIntPercentFromData(a, b int) int {
return int(float64((a * 100)) / float64(b))
}
func SendMessageToConnectionEasy(msg *[]rune) (dataProcessHandler, dataT) {
return SendMessageToConnection, dataT{rawP: msg}
}
func SendMessageToConnection(t *TUI, data dataT) error {
if t.tlsConnection == nil {
return errors.WrapErr("t.tlsConnection", errors.NOT_SET)
}
if data.rawP == nil {
return errors.WrapErr("data.rawP", errors.NOT_SET)
}
message := string(*data.rawP)
n, err := t.tlsConnection.Write([]byte(message))
if err != nil {
return errors.WrapErr("t.tlsConnection.Write", err)
}
log.Printf("Successfully wrote %d bytes to connection; Message: %s", n, message)
return nil
}
// takes data from storage
func FE_ConnectTLS(t *TUI, data dataT) error {
log.Printf("Start of FE_ConnectTLS")
host, exist := t.storage[STORAGE_HOST_CONST]
if !exist {
errors.WrapErr("t.storage:host", errors.NOT_SET)
}
portStr, exist := t.storage[STORAGE_PORT_CONST]
if !exist {
errors.WrapErr("t.storage:host", errors.NOT_SET)
}
port, err := strconv.ParseInt(portStr, 10, 32)
if err != nil {
errors.WrapErr("port.strconv.ParseInt", err)
}
loadingFileName := env.ServerFullchainFileName
cert, err := os.ReadFile(loadingFileName)
if err != nil {
errors.WrapErr("os.ReadFile", err)
}
log.Printf("Certificate %s loaded successfully!\n", loadingFileName)
//
roots := x509.NewCertPool()
if ok := roots.AppendCertsFromPEM(cert); !ok {
errors.WrapErr("client: failed to parse root certificate", nil)
}
config := &tls.Config{
RootCAs: roots,
}
conn, err := tls.Dial(
"tcp",
fmt.Sprintf("%s:%d", host, int(port)),
config,
)
if err != nil {
return errors.WrapErr("tls.Dial", err)
}
// log.Printf("Set connection to %#v\n", conn)
t.tlsConnection = conn
if t.stateChannel == nil {
return errors.WrapErr("t.stateChannel", errors.NOT_INIT)
}
t.stateChannel <- "TLS Connected"
t.isConnected = true
return nil
}
func AddToStorageEasy(key string, val *[]rune) (dataProcessHandler, dataT) {
// that's why I create wrapper around it.
// try to understand that, dear viewer!
return H_AddToStorage, dataT{rawP: val, op1: key}
}
func H_AddToStorage(t *TUI, data dataT) error {
log.Printf("Debug: %#v", data)
log.Printf("Adding to storage: %s = %s", data.op1, string(*data.rawP))
if t.storage == nil {
return errors.WrapErr("t.storage", errors.NOT_INIT)
}
t.storage[data.op1] = string(*data.rawP)
return nil
}

1
tui/input.go Normal file
View File

@ -0,0 +1 @@
package tui

111
tui/notification.go Normal file
View File

@ -0,0 +1,111 @@
package tui
import (
"fmt"
"strings"
"git.qowevisa.me/Qowevisa/gotell/errors"
)
func centerText(width int, text string) string {
emptyLen := width - len(text) - 2
leftEmptyLen := emptyLen / 2
return fmt.Sprintf(
"|%s%s%s|",
strings.Repeat(" ", leftEmptyLen),
text,
strings.Repeat(" ", emptyLen-leftEmptyLen),
)
}
func createNotification(message string) (notifier, error) {
title := "ERROR"
var buf string
width, height := UI.getSizes()
if width == 0 {
return notifier{}, errors.WrapErr("width", errors.NOT_INIT)
}
if height == 0 {
return notifier{}, errors.WrapErr("height", errors.NOT_INIT)
}
maxWidth := width / 3
maxHeight := 5
errMsgLen := len(message)
innerPart := maxWidth - 2
if errMsgLen <= innerPart {
maxWidth = errMsgLen + 2
} else {
for {
if errMsgLen <= innerPart {
break
}
maxHeight++
errMsgLen -= innerPart
}
}
innerPart = maxWidth - 2
col := (width - maxWidth) / 2
row := (height - maxHeight) / 2
startCol := col
startRow := row
buf += getBufForMovingCursorTo(row, col)
buf += strings.Repeat("-", maxWidth)
row++
buf += getBufForMovingCursorTo(row, col)
buf += centerText(maxWidth, title)
row++
buf += getBufForMovingCursorTo(row, col)
buf += strings.Repeat("-", maxWidth)
startI := 0
endI := innerPart
for i := 3; i < maxHeight-1; i++ {
var tmp string
if endI > len(message) {
tmp = message[startI:]
} else {
tmp = message[startI:endI]
}
row++
buf += getBufForMovingCursorTo(row, col)
var spaces string
if innerPart > len(tmp) {
spaces = strings.Repeat(" ", innerPart-len(tmp))
}
buf += fmt.Sprintf("|%s%s|", tmp, spaces)
startI += innerPart
endI += innerPart
}
row++
buf += getBufForMovingCursorTo(row, col)
buf += strings.Repeat("-", maxWidth)
row++
buf += getBufForMovingCursorTo(row, col)
buf += centerText(maxWidth, "OK")
row++
buf += getBufForMovingCursorTo(row, col)
buf += strings.Repeat("-", maxWidth)
return notifier{
Row: startRow,
Col: startCol,
Width: maxWidth,
Height: row - startRow + 1,
Buf: buf,
}, nil
}
func (n *notifier) Clear() string {
var buf string
for i := 0; i < n.Height; i++ {
buf += getBufForMovingCursorTo(n.Row, n.Col)
buf += strings.Repeat(" ", n.Width)
n.Row++
}
return buf
}

6
tui/storage_consts.go Normal file
View File

@ -0,0 +1,6 @@
package tui
const (
STORAGE_HOST_CONST = "host"
STORAGE_PORT_CONST = "port"
)

30
tui/table.go Normal file
View File

@ -0,0 +1,30 @@
package tui
const (
CTRL_A = 1 << iota
CTRL_B
CTRL_C
CTRL_D
CTRL_E
CTRL_F
CTRL_G
CTRL_H
CTRL_I
CTRL_J
CTRL_K
CTRL_L
CTRL_M
CTRL_N
CTRL_O
CTRL_P
CTRL_Q
CTRL_R
CTRL_S
CTRL_T
CTRL_U
CTRL_V
CTRL_W
CTRL_X
CTRL_Y
CTRL_Z
)

110
tui/types.go Normal file
View File

@ -0,0 +1,110 @@
package tui
import (
"bufio"
"crypto/tls"
"os"
"sync"
"golang.org/x/term"
)
type mySignal struct {
Type int
}
var UI TUI
type dataT struct {
raw string
rawP *[]rune
op1 string
op2 string
ops []string
}
type dataProcessHandler func(t *TUI, data dataT) error
type tuiPointPair struct {
startCol int
startRow int
endCol int
endRow int
}
type widget struct {
row int
col int
MinWidth int
MinHeight int
Width int
Height int
Title string
Input *[]rune
Handler dataProcessHandler
Data dataT
Next *widgetConfig
Finale dataProcessHandler
FinaleData dataT
percentPair tuiPointPair
snappedPair tuiPointPair
startupConfig widgetConfig
}
type notifier struct {
Row int
Col int
Width int
Height int
Buf string
}
type widgetDraw struct {
Buf string
Row int
Col int
}
type cursorPosConfigValue int
type widgetPosConfigValue int
type widgetConfig struct {
MinWidth int
MinHeight int
Input *[]rune
CursorPosConfig cursorPosConfigValue
Title string
WidgetPosConfig widgetPosConfigValue
HasBorder bool
DataHandler dataProcessHandler
Data dataT
Next *widgetConfig
Finale dataProcessHandler
FinaleData dataT
}
type TUI struct {
width int
height int
cursorPosRow int
cursorPosCol int
writeMu sync.Mutex
sizeMutex sync.Mutex
oldState *term.State
input chan (rune)
printRunes chan (rune)
mySignals chan (mySignal)
osSignals chan (os.Signal)
errors chan (error)
readInputState chan (bool)
readEnterState chan (bool)
stateChannel chan (string)
widgets []*widget
widgetsMutext sync.Mutex
writer *bufio.Writer
isConnected bool
selectedWidget *widget
selectedNotifier *notifier
storage map[string]string
tlsConnection *tls.Conn
}

536
tui/ui.go Normal file
View File

@ -0,0 +1,536 @@
package tui
import (
"bufio"
"fmt"
"log"
"os"
"os/signal"
"sync"
"syscall"
"unicode"
"git.qowevisa.me/Qowevisa/gotell/errors"
"golang.org/x/term"
)
func (t *TUI) init() error {
var err error
t.input = make(chan rune, 32)
t.printRunes = make(chan rune, 32)
t.widgets = make([]*widget, 8)
t.errors = make(chan error, 4)
t.mySignals = make(chan mySignal, 1)
t.osSignals = make(chan os.Signal, 1)
t.writer = bufio.NewWriter(os.Stdout)
t.storage = make(map[string]string)
t.readInputState = make(chan bool, 1)
t.readEnterState = make(chan bool, 1)
t.stateChannel = make(chan string, 1)
signal.Notify(t.osSignals, syscall.SIGWINCH)
err = t.setSizes()
if err != nil {
return errors.WrapErr("t.getSizes", err)
}
err = t.setTermToRaw()
if err != nil {
return errors.WrapErr("t.setTermToRaw", err)
}
err = t.setRoutines()
if err != nil {
return errors.WrapErr("t.setRoutines", err)
}
err = t.readRoutines()
if err != nil {
return errors.WrapErr("t.readRoutines", err)
}
return nil
}
func (t *TUI) exit() {
if t.oldState != nil {
term.Restore(int(os.Stdin.Fd()), t.oldState)
}
}
func (t *TUI) Run() error {
defer t.exit()
var err error
err = t.init()
if err != nil {
return errors.WrapErr("t.init", err)
}
//
if t.mySignals == nil {
return errors.WrapErr("t.signals", errors.NOT_INIT)
}
err = t.Draw()
if err != nil {
return errors.WrapErr("t.Draw", err)
}
for mySignal := range t.mySignals {
log.Printf("Receive signal: %#v\n", mySignal)
if mySignal.Type == MY_SIGNAL_EXIT {
t.errors <- t.clearScreen()
t.errors <- t.moveCursor(0, 0)
break
}
switch mySignal.Type {
case MY_SIGNAL_CONNECT:
var host []rune
var port []rune
hostHandler, hostData := AddToStorageEasy("host", &host)
portHandler, portData := AddToStorageEasy("port", &port)
err := t.addWidget(widgetConfig{
Input: &host,
Title: "Host",
MinWidth: 16,
HasBorder: true,
WidgetPosConfig: widgetPosGeneralCenter,
CursorPosConfig: cursorPosGeneralCenter,
DataHandler: hostHandler,
Data: hostData,
Finale: nil,
Next: &widgetConfig{
Input: &port,
Title: "Port",
MinWidth: 8,
HasBorder: true,
WidgetPosConfig: widgetPosGeneralCenter,
CursorPosConfig: cursorPosGeneralCenter,
DataHandler: portHandler,
Data: portData,
Next: nil,
Finale: FE_ConnectTLS,
FinaleData: dataT{},
},
})
if err != nil {
t.errors <- errors.WrapErr("t.addWidget", err)
}
err = t.drawSelectedWidget()
if err != nil {
t.errors <- errors.WrapErr("t.drawSelectedWidget", err)
}
case MY_SIGNAL_MESSAGE:
if t.isConnected {
var msg []rune
h, d := SendMessageToConnectionEasy(&msg)
err := t.addWidget(widgetConfig{
Input: &msg,
Title: "Message",
MinWidth: 20,
HasBorder: true,
WidgetPosConfig: widgetPosGeneralCenter,
CursorPosConfig: cursorPosGeneralCenter,
DataHandler: h,
Data: d,
Next: nil,
Finale: nil,
})
if err != nil {
t.errors <- errors.WrapErr("t.addWidget", err)
}
err = t.drawSelectedWidget()
if err != nil {
t.errors <- errors.WrapErr("t.drawSelectedWidget", err)
}
}
}
}
//
return nil
}
func (t *TUI) createNotification(text string) {
notifier, err := createNotification(text)
t.selectedNotifier = &notifier
t.errors <- err
t.errors <- t.write(notifier.Buf)
t.readEnterState <- true
}
func (t *TUI) setRoutines() error {
if t.errors == nil {
return errors.WrapErr("t.errors", errors.NOT_INIT)
}
go func() {
for err := range t.errors {
if err != nil {
t.createNotification(err.Error())
}
}
}()
if t.input == nil {
return errors.WrapErr("t.input", errors.NOT_INIT)
}
if t.oldState == nil {
return errors.WrapErr("t.oldState", errors.NOT_INIT)
}
if t.readInputState == nil {
return errors.WrapErr("t.readInputState", errors.NOT_INIT)
}
if t.readEnterState == nil {
return errors.WrapErr("t.readEnterState", errors.NOT_INIT)
}
if t.stateChannel == nil {
return errors.WrapErr("t.stateChannel", errors.NOT_INIT)
}
go func() {
for state := range t.stateChannel {
t.writeMu.Lock()
oldRow, oldCol := t.getCursorPos()
t.moveCursor(t.height, len(footerStart)+1)
t.write(state)
t.moveCursor(oldRow, oldCol)
t.writeMu.Unlock()
}
}()
var readInputMu sync.Mutex
var readEnterdMu sync.Mutex
readInput := false
readCommand := false
readEnter := false
go func() {
for newState := range t.readInputState {
readInputMu.Lock()
readInput = newState
readInputMu.Unlock()
}
}()
go func() {
for newState := range t.readEnterState {
readEnterdMu.Lock()
readEnter = newState
readEnterdMu.Unlock()
}
}()
go func() {
reader := bufio.NewReader(os.Stdin)
for {
r, _, err := reader.ReadRune()
if err != nil {
panic(err)
}
log.Printf("Read %#v rune\n", r)
if readEnter {
if r == 13 {
readEnter = false
if t.selectedNotifier != nil {
t.errors <- t.write(t.selectedNotifier.Clear())
}
continue
}
}
if readCommand {
switch r {
case 'q':
t.mySignals <- mySignal{
Type: MY_SIGNAL_EXIT,
}
case 'c':
t.mySignals <- mySignal{
Type: MY_SIGNAL_CONNECT,
}
readInputMu.Lock()
readInput = true
readInputMu.Unlock()
case 'm':
t.mySignals <- mySignal{
Type: MY_SIGNAL_MESSAGE,
}
readInputMu.Lock()
readInput = true
readInputMu.Unlock()
case 't':
t.createNotification("some notify")
readEnter = true
}
readCommand = false
continue
}
//
if unicode.IsControl(r) {
switch r {
case CTRL_A:
readCommand = true
}
} else {
if readInput {
log.Printf("Send %c | %d to t.input", r, r)
t.input <- r
}
}
readInputMu.Lock()
if readInput {
switch r {
case 13:
t.input <- r
case 127:
t.input <- r
}
}
readInputMu.Unlock()
}
}()
//
if t.osSignals == nil {
return errors.WrapErr("t.osSignals", errors.NOT_INIT)
}
go func() {
for sig := range t.osSignals {
log.Printf("Receive OS.signal: %#v\n", sig)
switch sig {
case syscall.SIGWINCH:
t.errors <- t.redraw()
}
}
}()
if t.input == nil {
return errors.WrapErr("t.input", errors.NOT_INIT)
}
go func() {
for r := range t.input {
log.Printf("Read rune: %#v from t.input\n", r)
selWidget := t.selectedWidget
// Enter
if r == 13 {
log.Printf("Debug: selWidget: %#v ; selWidget.Input: %#v", selWidget, selWidget.Input)
if selWidget != nil && selWidget.Input != nil {
t.errors <- selWidget.Handler(t, selWidget.Data)
buf := selWidget.Clear()
t.writeMu.Lock()
err := t.write(buf)
t.writeMu.Unlock()
if err != nil {
t.errors <- errors.WrapErr("t.write", err)
}
if selWidget.Next != nil {
log.Printf("Seeing that widget.Next is not nil")
t.errors <- t.addWidget(*selWidget.Next)
t.errors <- t.drawSelectedWidget()
} else {
t.readInputState <- false
}
if selWidget.Finale != nil {
log.Printf("Seeing that widget.Finale is not nil")
t.errors <- selWidget.Finale(t, selWidget.FinaleData)
}
}
continue
} else if r == 127 {
log.Printf("seeing r = 127")
sliceLen := len(*selWidget.Input)
log.Printf("sliceLen = %d\n", sliceLen)
if sliceLen > 0 {
log.Printf("sliceLen > 0")
*selWidget.Input = (*selWidget.Input)[:sliceLen-1]
t.writeMu.Lock()
t.errors <- t.moveCursor(t.cursorPosRow, t.cursorPosCol-1)
t.errors <- t.writeRune(' ')
t.errors <- t.moveCursor(t.cursorPosRow, t.cursorPosCol-1)
t.writeMu.Unlock()
}
continue
}
if selWidget != nil && selWidget.Input != nil {
log.Printf("t.input: append %#v to widget input\n", r)
*selWidget.Input = append(*selWidget.Input, r)
log.Printf("t.input: trying to write %#v", r)
t.writeMu.Lock()
err := t.writeRune(r)
t.writeMu.Unlock()
if err != nil {
t.errors <- err
}
}
}
}()
return nil
}
func (t *TUI) readRoutines() error {
if t.input == nil {
return errors.WrapErr("t.input", errors.NOT_INIT)
}
return nil
}
func (t *TUI) Draw() error {
err := t.clearScreen()
if err != nil {
return errors.WrapErr("t.clearScreen", err)
}
err = t.drawFooter()
if err != nil {
return errors.WrapErr("t.drawFooter", err)
}
return nil
}
func (t *TUI) drawFooter() error {
t.writeMu.Lock()
defer t.writeMu.Unlock()
err := t.moveCursor(t.height, 0)
if err != nil {
return errors.WrapErr("t.moveCursor", err)
}
err = t.write(footerStart)
if err != nil {
return errors.WrapErr("t.write", err)
}
return nil
}
func (t *TUI) write(s string) error {
_, err := t.writer.WriteString(s)
if err != nil {
return errors.WrapErr("t.writer.WriteString", err)
}
err = t.writer.Flush()
if err != nil {
return errors.WrapErr("t.writer.Flush", err)
}
t.cursorPosCol += len(s)
if t.cursorPosCol > t.width {
t.cursorPosCol %= t.width
t.cursorPosRow++
}
return nil
}
func (t *TUI) writeRune(r rune) error {
_, err := t.writer.WriteRune(r)
if err != nil {
return errors.WrapErr("t.writer.WriteRune", err)
}
err = t.writer.Flush()
if err != nil {
return errors.WrapErr("t.writer.Flush", err)
}
t.cursorPosCol++
return nil
}
func (t *TUI) getCursorPos() (int, int) {
return t.cursorPosRow, t.cursorPosCol
}
func (t *TUI) moveCursor(row, col int) error {
t.sizeMutex.Lock()
defer t.sizeMutex.Unlock()
if row > t.height {
return errors.WrapErr(fmt.Sprintf("row: %d; height: %d", row, t.height), errors.OUT_OF_BOUND)
}
if col > t.width {
return errors.WrapErr(fmt.Sprintf("col: %d; width: %d", col, t.width), errors.OUT_OF_BOUND)
}
log.Printf("t.cursorPosRow: %d ; t.cursorPosCol: %d\n", t.cursorPosRow, t.cursorPosCol)
log.Printf("trying to move to row: %d ; col %d\n", row, col)
_, err := t.writer.WriteString(fmt.Sprintf("\033[%d;%dH", row, col))
if err != nil {
return errors.WrapErr("t.writer.WriteString", err)
}
err = t.writer.Flush()
if err != nil {
return errors.WrapErr("t.writer.Flush", err)
}
t.cursorPosCol = col
log.Printf("t.cursorPosCol now is = %d\n", col)
t.cursorPosRow = row
log.Printf("t.cursorPosRow now is = %d\n", row)
return nil
}
func (t *TUI) clearScreen() error {
_, err := t.writer.WriteString("\033[2J")
if err != nil {
return errors.WrapErr("t.writer.WriteString", err)
}
err = t.writer.Flush()
if err != nil {
return errors.WrapErr("t.writer.Flush", err)
}
return nil
}
func (t *TUI) drawSelectedWidget() error {
wDraw, err := t.selectedWidget.Draw()
if err != nil {
return errors.WrapErr("t.selectedWidget.Draw", err)
}
t.writeMu.Lock()
err = t.write(wDraw.Buf)
t.writeMu.Unlock()
if err != nil {
return errors.WrapErr("t.write", err)
}
t.cursorPosRow = wDraw.Row
t.cursorPosCol = wDraw.Col
return nil
}
// Creating and adding widget from config.
// Also sets created widget as selectedWidget
func (t *TUI) addWidget(config widgetConfig) error {
widget := &widget{}
err := widget.init(config)
if err != nil {
return errors.WrapErr("widget.init", err)
}
t.widgetsMutext.Lock()
defer t.widgetsMutext.Unlock()
t.widgets = append(t.widgets, widget)
t.selectedWidget = widget
return nil
}
func (t *TUI) redraw() error {
var err error
err = t.setSizes()
if err != nil {
return errors.WrapErr("t.getSizes", err)
}
err = t.redrawWidgets()
if err != nil {
return errors.WrapErr("t.redrawWidgets", err)
}
return nil
}
func (t *TUI) setSizes() error {
w, h, err := term.GetSize(int(os.Stdin.Fd()))
if err != nil {
return errors.WrapErr("term.GetSize", err)
}
t.sizeMutex.Lock()
t.width = w
t.height = h
t.sizeMutex.Unlock()
return nil
}
func (t *TUI) getSizes() (int, int) {
t.sizeMutex.Lock()
defer t.sizeMutex.Unlock()
return t.width, t.height
}
func (t *TUI) redrawWidgets() error {
if t.widgets == nil {
return errors.WrapErr("t.widgets", errors.NOT_INIT)
}
// TODO
return nil
}
func (t *TUI) setTermToRaw() error {
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return errors.WrapErr("term.MakeRaw", err)
}
t.oldState = oldState
return nil
}

175
tui/widget.go Normal file
View File

@ -0,0 +1,175 @@
package tui
import (
"fmt"
"strings"
"git.qowevisa.me/Qowevisa/gotell/errors"
)
// func (w *widget) init(startX, startY, endX, endY int) error {
// width, height := UI.getSizes()
//
// return nil
// }
func (w *widget) init(config widgetConfig) error {
width, height := UI.getSizes()
if width == 0 {
return errors.WrapErr("width", errors.NOT_INIT)
}
if height == 0 {
return errors.WrapErr("height", errors.NOT_INIT)
}
var startRow, startCol int
// I guess I really has to do it that way bc go doesn't like my C style
// of writting `-2 * config.HasBorder` or `-2 * (config.HasBorder == true)`
var factorIfHasBorder int
if config.HasBorder {
factorIfHasBorder = 1
} else {
factorIfHasBorder = 0
}
if config.WidgetPosConfig.isGeneral() {
switch config.WidgetPosConfig {
case widgetPosGeneralCenter:
startCol = (width - len(config.Title)) / 2
startRow = (height - 3 - 2*factorIfHasBorder) / 2
case widgetPosGeneralLeftCenter:
startCol = 0
startRow = (height - 3 - 2*factorIfHasBorder) / 2
case widgetPosGeneralRightCenter:
startCol = (width - len(config.Title) - 2*factorIfHasBorder)
startRow = (height - 3 - 2*factorIfHasBorder) / 2
default:
return errors.WrapErr(fmt.Sprintf("config.WidgetPosConfig: %d :", config.WidgetPosConfig), errors.NOT_HANDLED)
}
}
snappedPair := tuiPointPair{
startCol: startCol,
startRow: startRow,
endCol: startCol + len(config.Title) + 2,
endRow: startRow + 5,
}
percentPair := tuiPointPair{
startCol: GetIntPercentFromData(snappedPair.startCol, width),
startRow: GetIntPercentFromData(snappedPair.startRow, height),
endCol: GetIntPercentFromData(snappedPair.endCol, width),
endRow: GetIntPercentFromData(snappedPair.endRow, height),
}
w.percentPair = percentPair
w.snappedPair = snappedPair
w.startupConfig = config
w.Input = config.Input
w.Handler = config.DataHandler
w.Data = config.Data
w.Title = config.Title
w.MinWidth = config.MinWidth
w.MinHeight = config.MinHeight
if w.MinWidth > len(config.Title) {
w.Width = w.MinWidth
} else {
if w.startupConfig.HasBorder {
w.Width = len(config.Title) + 2
} else {
w.Width = len(config.Title)
}
}
if config.HasBorder {
if config.MinHeight > 5 {
w.Height = config.MinHeight
} else {
w.Height = 5
}
}
w.Next = config.Next
w.Finale = config.Finale
w.FinaleData = config.FinaleData
return nil
}
func getBufForMovingCursorTo(row, col int) string {
return fmt.Sprintf("\033[%d;%dH", row, col)
}
func (w *widget) moveToNextLine() string {
w.row++
return getBufForMovingCursorTo(w.row, w.col)
}
func (w *widget) Draw() (widgetDraw, error) {
w.row = w.snappedPair.startRow
w.col = w.snappedPair.startCol
var buf string
title := w.startupConfig.Title
buf += getBufForMovingCursorTo(w.row, w.col)
if w.startupConfig.HasBorder {
buf += strings.Repeat("-", w.Width)
buf += w.moveToNextLine()
emptyLen := w.Width - len(title) - 2
firstHalf := (emptyLen) / 2
buf += fmt.Sprintf("|%s%s%s|",
strings.Repeat(" ", firstHalf),
title, strings.Repeat(" ",
emptyLen-firstHalf))
buf += w.moveToNextLine()
buf += strings.Repeat("-", w.Width)
buf += w.moveToNextLine()
buf += fmt.Sprintf("|%s%s|", string(*w.Input), strings.Repeat(" ", w.Width-2-len(*w.Input)))
buf += w.moveToNextLine()
buf += strings.Repeat("-", w.Width)
w.col++
w.row--
buf += getBufForMovingCursorTo(w.row, w.col)
} else {
buf += fmt.Sprintf("%s", title)
buf += w.moveToNextLine()
buf += strings.Repeat("-", w.Width)
buf += w.moveToNextLine()
buf += fmt.Sprintf("%s%s", string(*w.Input), strings.Repeat(" ", len(title)-len(*w.Input)))
buf += getBufForMovingCursorTo(w.row, w.col)
}
return widgetDraw{
Buf: buf,
Row: w.row,
Col: w.col,
}, nil
}
func (w *widget) Clear() string {
var buf string
w.row = w.snappedPair.startRow
w.col = w.snappedPair.startCol
title := w.startupConfig.Title
buf += getBufForMovingCursorTo(w.row, w.col)
if w.startupConfig.HasBorder {
buf += strings.Repeat(" ", w.Width)
buf += w.moveToNextLine()
buf += strings.Repeat(" ", w.Width)
buf += w.moveToNextLine()
buf += strings.Repeat(" ", w.Width)
buf += w.moveToNextLine()
maxClearInpLen := len(*w.Input) + 2
if w.Width > maxClearInpLen {
maxClearInpLen = w.Width
}
buf += fmt.Sprintf("%s", strings.Repeat(" ", maxClearInpLen))
buf += w.moveToNextLine()
buf += strings.Repeat(" ", w.Width)
w.row++
w.col--
buf += getBufForMovingCursorTo(0, 0)
} else {
buf += fmt.Sprintf("%s", strings.Repeat(" ", len(title)))
buf += w.moveToNextLine()
buf += strings.Repeat(" ", w.Width)
buf += w.moveToNextLine()
maxClearInpLen := len(*w.Input)
if w.Width > maxClearInpLen {
maxClearInpLen = w.Width
}
buf += fmt.Sprintf("%s", strings.Repeat(" ", maxClearInpLen))
buf += getBufForMovingCursorTo(0, 0)
}
return buf
}