Well.. I created some sort of tui for GNU/Linux
This commit is contained in:
parent
db5136a33e
commit
544bf2b219
47
tui/consts.go
Normal file
47
tui/consts.go
Normal 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
101
tui/funcs.go
Normal 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
1
tui/input.go
Normal file
|
@ -0,0 +1 @@
|
|||
package tui
|
111
tui/notification.go
Normal file
111
tui/notification.go
Normal 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
6
tui/storage_consts.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package tui
|
||||
|
||||
const (
|
||||
STORAGE_HOST_CONST = "host"
|
||||
STORAGE_PORT_CONST = "port"
|
||||
)
|
30
tui/table.go
Normal file
30
tui/table.go
Normal 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
110
tui/types.go
Normal 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
536
tui/ui.go
Normal 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 = ¬ifier
|
||||
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
175
tui/widget.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user