Some changes in tui.

This commit is contained in:
qowevisa 2024-05-01 17:21:10 +03:00
parent b33d5f036c
commit 9e9eb70cd2
12 changed files with 383 additions and 147 deletions

15
tui/channel_all.go Normal file
View File

@ -0,0 +1,15 @@
package tui
func (t *TUI) launchAllChannels() error {
var err error
err = t.launchErrorsChannel()
if err != nil {
return err
}
t.errorsChannel <- t.launchInputChannel()
t.errorsChannel <- t.launchInputChannel()
t.errorsChannel <- t.launchMessageChannel()
t.errorsChannel <- t.launchSignalsChannel()
t.errorsChannel <- t.launchStateChannel()
return nil
}

17
tui/channel_errors.go Normal file
View File

@ -0,0 +1,17 @@
package tui
import "git.qowevisa.me/Qowevisa/gotell/errors"
func (t *TUI) launchErrorsChannel() error {
if t.errorsChannel == nil {
return errors.WrapErr("t.errors", errors.NOT_INIT)
}
go func() {
for err := range t.errorsChannel {
if err != nil {
t.createNotification(err.Error(), CONST_ERROR_N_TITLE)
}
}
}()
return nil
}

71
tui/channel_input.go Normal file
View File

@ -0,0 +1,71 @@
package tui
import (
"log"
"git.qowevisa.me/Qowevisa/gotell/errors"
)
func (t *TUI) launchInputChannel() error {
if t.inputChannel == nil {
return errors.WrapErr("t.inputChannel", errors.NOT_INIT)
}
go func() {
for r := range t.inputChannel {
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.errorsChannel <- selWidget.Handler(t, selWidget.Data)
buf := selWidget.Clear()
t.writeMu.Lock()
err := t.write(buf)
t.writeMu.Unlock()
if err != nil {
t.errorsChannel <- errors.WrapErr("t.write", err)
}
if selWidget.Next != nil {
log.Printf("Seeing that widget.Next is not nil")
t.errorsChannel <- t.addWidget(*selWidget.Next)
t.errorsChannel <- t.drawSelectedWidget()
} else {
t.readInputState <- false
}
if selWidget.Finale != nil {
log.Printf("Seeing that widget.Finale is not nil")
t.errorsChannel <- 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.errorsChannel <- t.moveCursor(t.cursorPosRow, t.cursorPosCol-1)
t.errorsChannel <- t.writeRune(' ')
t.errorsChannel <- 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.errorsChannel <- err
}
}
}
}()
return nil
}

37
tui/channel_message.go Normal file
View File

@ -0,0 +1,37 @@
package tui
import (
"git.qowevisa.me/Qowevisa/gotell/communication"
"git.qowevisa.me/Qowevisa/gotell/errors"
)
// Basically every X_channel.go file launches some sort of channel
func (t *TUI) launchMessageChannel() error {
if t.messageChannel == nil {
return errors.WrapErr("t.messageChannel", errors.NOT_INIT)
}
go func() {
for message := range t.messageChannel {
t.createNotification(string(message), "Message!")
msg, err := communication.Decode(message)
t.errorsChannel <- err
if err != nil {
continue
}
switch msg.Type {
case communication.SERVER_COMMAND:
t.handleServerCommands(msg.Data)
}
}
}()
return nil
}
func (t *TUI) handleServerCommands(data []byte) {
if len(data) == 1 {
if data[0] == communication.NICKNAME {
t.SendMessageToServer("Nickname", 16)
}
}
}

24
tui/channel_signals.go Normal file
View File

@ -0,0 +1,24 @@
package tui
import (
"log"
"syscall"
"git.qowevisa.me/Qowevisa/gotell/errors"
)
func (t *TUI) launchSignalsChannel() error {
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.errorsChannel <- t.redraw()
}
}
}()
return nil
}

21
tui/channel_state.go Normal file
View File

@ -0,0 +1,21 @@
package tui
import "git.qowevisa.me/Qowevisa/gotell/errors"
func (t *TUI) launchStateChannel() error {
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.errorsChannel <- t.moveCursor(t.height, len(footerStart)+1)
t._clearLine()
t.errorsChannel <- t.write(state)
t.errorsChannel <- t.moveCursor(oldRow, oldCol)
t.writeMu.Unlock()
}
}()
return nil
}

View File

@ -5,6 +5,10 @@ const (
MY_SIGNAL_MESSAGE
MY_SIGNAL_CONNECT
MY_SIGNAL_CLOSE
MY_SIGNAL_MOVE_CURSOR_UP
MY_SIGNAL_MOVE_CURSOR_DOWN
MY_SIGNAL_MOVE_CURSOR_LEFT
MY_SIGNAL_MOVE_CURSOR_RIGHT
)
const (
@ -22,8 +26,8 @@ const (
footerStart = "State: "
)
func (c *cursorPosConfigValue) isGeneral() bool {
switch *c {
func (c cursorPosConfigValue) isGeneral() bool {
switch c {
case cursorPosGeneralCenter:
return true
case cursorPosGeneralLeft:
@ -40,8 +44,8 @@ const (
widgetPosGeneralRightCenter widgetPosConfigValue = -3
)
func (w *widgetPosConfigValue) isGeneral() bool {
switch *w {
func (w widgetPosConfigValue) isGeneral() bool {
switch w {
case widgetPosGeneralCenter:
return true
case widgetPosGeneralLeftCenter:

View File

@ -15,7 +15,7 @@ func (t *TUI) launchReadingMessagesFromConnection(ctx context.Context, wg *sync.
defer wg.Done() // Mark this goroutine as done when it exits
if t.messageChannel == nil {
t.errors <- errors.WrapErr("t.messageChannel", errors.NOT_INIT)
t.errorsChannel <- errors.WrapErr("t.messageChannel", errors.NOT_INIT)
return
}
buf := make([]byte, CONST_MESSAGE_LEN)
@ -27,7 +27,7 @@ func (t *TUI) launchReadingMessagesFromConnection(ctx context.Context, wg *sync.
timeoutDuration := 5 * time.Second
err := t.tlsConnection.SetReadDeadline(time.Now().Add(timeoutDuration))
if err != nil {
t.errors <- errors.WrapErr("SetReadDeadline", err)
t.errorsChannel <- errors.WrapErr("SetReadDeadline", err)
return
}
n, err := t.tlsConnection.Read(buf)
@ -36,7 +36,7 @@ func (t *TUI) launchReadingMessagesFromConnection(ctx context.Context, wg *sync.
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
t.errors <- errors.WrapErr("t.tlsConnection.Read", err)
t.errorsChannel <- errors.WrapErr("t.tlsConnection.Read", err)
}
return
}

View File

@ -60,6 +60,15 @@ type notifier struct {
Buf string
}
type dialog struct {
Row int
Col int
Width int
Height int
Buf string
Catcher RuneCatcher
}
type widgetDraw struct {
Buf string
Row int
@ -89,6 +98,8 @@ type closeData struct {
cancel context.CancelFunc
}
type RuneCatcher func(runes chan (rune)) error
type TUI struct {
width int
height int
@ -97,11 +108,12 @@ type TUI struct {
writeMu sync.Mutex
sizeMutex sync.Mutex
oldState *term.State
input chan (rune)
inputChannel chan (rune)
inputCatcher RuneCatcher
printRunes chan (rune)
mySignals chan (mySignal)
osSignals chan (os.Signal)
errors chan (error)
errorsChannel chan (error)
readInputState chan (bool)
readEnterState chan (bool)
stateChannel chan (string)

159
tui/ui.go
View File

@ -17,10 +17,10 @@ import (
func (t *TUI) init() error {
var err error
t.input = make(chan rune, 32)
t.inputChannel = make(chan rune, 32)
t.printRunes = make(chan rune, 32)
t.widgets = make([]*widget, 8)
t.errors = make(chan error, 4)
t.errorsChannel = make(chan error, 4)
t.mySignals = make(chan mySignal, 1)
t.osSignals = make(chan os.Signal, 1)
t.writer = bufio.NewWriter(os.Stdout)
@ -46,6 +46,10 @@ func (t *TUI) init() error {
if err != nil {
return errors.WrapErr("t.readRoutines", err)
}
err = t.launchAllChannels()
if err != nil {
return errors.WrapErr("t.launchAllChannels", err)
}
return nil
}
@ -64,7 +68,7 @@ func (t *TUI) Run() error {
}
//
if t.mySignals == nil {
return errors.WrapErr("t.signals", errors.NOT_INIT)
return errors.WrapErr("t.mySignals", errors.NOT_INIT)
}
err = t.Draw()
if err != nil {
@ -73,8 +77,8 @@ func (t *TUI) Run() error {
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)
t.errorsChannel <- t.clearScreen()
t.errorsChannel <- t.moveCursor(0, 0)
break
}
switch mySignal.Type {
@ -108,43 +112,23 @@ func (t *TUI) Run() error {
},
})
if err != nil {
t.errors <- errors.WrapErr("t.addWidget", err)
t.errorsChannel <- errors.WrapErr("t.addWidget", err)
}
err = t.drawSelectedWidget()
if err != nil {
t.errors <- errors.WrapErr("t.drawSelectedWidget", err)
t.errorsChannel <- 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)
}
t.SendMessageToServer("Message", 20)
}
case MY_SIGNAL_CLOSE:
if t.isConnected {
CloseConnection(t.tlsConnCloseData.wg, t.tlsConnCloseData.cancel)
t.isConnected = false
t.errors <- t.tlsConnection.Close()
t.errorsChannel <- t.tlsConnection.Close()
t.stateChannel <- "Disconnected"
}
default:
@ -157,23 +141,13 @@ func (t *TUI) Run() error {
func (t *TUI) createNotification(text, title string) {
notifier, err := createNotification(text, title)
t.selectedNotifier = &notifier
t.errors <- err
t.errors <- t.write(notifier.Buf)
t.errorsChannel <- err
t.errorsChannel <- 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(), CONST_ERROR_N_TITLE)
}
}
}()
if t.input == nil {
if t.inputChannel == nil {
return errors.WrapErr("t.input", errors.NOT_INIT)
}
if t.oldState == nil {
@ -191,22 +165,6 @@ func (t *TUI) setRoutines() error {
if t.messageChannel == nil {
return errors.WrapErr("t.messageChannel", errors.NOT_INIT)
}
go func() {
for message := range t.messageChannel {
t.createNotification(string(message), "Message!")
}
}()
go func() {
for state := range t.stateChannel {
t.writeMu.Lock()
oldRow, oldCol := t.getCursorPos()
t.errors <- t.moveCursor(t.height, len(footerStart)+1)
t._clearLine()
t.errors <- t.write(state)
t.errors <- t.moveCursor(oldRow, oldCol)
t.writeMu.Unlock()
}
}()
var readInputMu sync.Mutex
var readEnterdMu sync.Mutex
readInput := false
@ -239,7 +197,7 @@ func (t *TUI) setRoutines() error {
if r == 13 {
readEnter = false
if t.selectedNotifier != nil {
t.errors <- t.write(t.selectedNotifier.Clear())
t.errorsChannel <- t.write(t.selectedNotifier.Clear())
}
continue
}
@ -298,101 +256,26 @@ func (t *TUI) setRoutines() error {
} else {
if readInput {
log.Printf("Send %c | %d to t.input", r, r)
t.input <- r
t.inputChannel <- r
}
}
readInputMu.Lock()
if readInput {
switch r {
case 13:
t.input <- r
t.inputChannel <- r
case 127:
t.input <- r
t.inputChannel <- 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
}
@ -457,7 +340,7 @@ func (t *TUI) getCursorPos() (int, int) {
}
func (t *TUI) _clearLine() {
t.errors <- t.write("\033[0K")
t.errorsChannel <- t.write("\033[0K")
}
func (t *TUI) moveCursor(row, col int) error {

27
tui/util.go Normal file
View File

@ -0,0 +1,27 @@
package tui
import "git.qowevisa.me/Qowevisa/gotell/errors"
func (t *TUI) SendMessageToServer(title string, minW int) {
var msg []rune
h, d := SendMessageToConnectionEasy(&msg)
err := t.addWidget(widgetConfig{
Input: &msg,
Title: title,
MinWidth: minW,
HasBorder: true,
WidgetPosConfig: widgetPosGeneralCenter,
CursorPosConfig: cursorPosGeneralCenter,
DataHandler: h,
Data: d,
Next: nil,
Finale: nil,
})
if err != nil {
t.errorsChannel <- errors.WrapErr("t.addWidget", err)
}
err = t.drawSelectedWidget()
if err != nil {
t.errorsChannel <- errors.WrapErr("t.drawSelectedWidget", err)
}
}

125
tui/widget_dialog.go Normal file
View File

@ -0,0 +1,125 @@
package tui
import (
"fmt"
"strings"
"git.qowevisa.me/Qowevisa/gotell/errors"
)
func createDialog(message, title string) (dialog, error) {
var buf string
width, height := UI.getSizes()
if width == 0 {
return dialog{}, errors.WrapErr("width", errors.NOT_INIT)
}
if height == 0 {
return dialog{}, 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, "YES | NO")
row++
buf += getBufForMovingCursorTo(row, col)
buf += strings.Repeat("-", maxWidth)
return dialog{
Row: startRow,
Col: startCol,
Width: maxWidth,
Height: row - startRow + 1,
Buf: buf,
}, nil
}
const (
_int_CatcherNone = iota
_int_Catcher1Arrow
_int_Catcher2Arrow
_int_CatcherArrow
)
func dialogRuneCatcher(t *TUI, runes chan (rune)) error {
state := _int_CatcherNone
for r := range runes {
if r == 27 && state == _int_CatcherNone {
state = _int_Catcher1Arrow
} else {
continue
}
if r == 91 && state == _int_Catcher1Arrow {
state = _int_Catcher2Arrow
} else {
continue
}
if state == _int_Catcher2Arrow {
switch r {
case 65:
t.mySignals <- mySignal{Type: MY_SIGNAL_MOVE_CURSOR_UP}
case 66:
t.mySignals <- mySignal{Type: MY_SIGNAL_MOVE_CURSOR_DOWN}
case 67:
t.mySignals <- mySignal{Type: MY_SIGNAL_MOVE_CURSOR_RIGHT}
case 68:
t.mySignals <- mySignal{Type: MY_SIGNAL_MOVE_CURSOR_LEFT}
}
}
}
return nil
}