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_MESSAGE
MY_SIGNAL_CONNECT MY_SIGNAL_CONNECT
MY_SIGNAL_CLOSE MY_SIGNAL_CLOSE
MY_SIGNAL_MOVE_CURSOR_UP
MY_SIGNAL_MOVE_CURSOR_DOWN
MY_SIGNAL_MOVE_CURSOR_LEFT
MY_SIGNAL_MOVE_CURSOR_RIGHT
) )
const ( const (
@ -22,8 +26,8 @@ const (
footerStart = "State: " footerStart = "State: "
) )
func (c *cursorPosConfigValue) isGeneral() bool { func (c cursorPosConfigValue) isGeneral() bool {
switch *c { switch c {
case cursorPosGeneralCenter: case cursorPosGeneralCenter:
return true return true
case cursorPosGeneralLeft: case cursorPosGeneralLeft:
@ -40,8 +44,8 @@ const (
widgetPosGeneralRightCenter widgetPosConfigValue = -3 widgetPosGeneralRightCenter widgetPosConfigValue = -3
) )
func (w *widgetPosConfigValue) isGeneral() bool { func (w widgetPosConfigValue) isGeneral() bool {
switch *w { switch w {
case widgetPosGeneralCenter: case widgetPosGeneralCenter:
return true return true
case widgetPosGeneralLeftCenter: 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 defer wg.Done() // Mark this goroutine as done when it exits
if t.messageChannel == nil { if t.messageChannel == nil {
t.errors <- errors.WrapErr("t.messageChannel", errors.NOT_INIT) t.errorsChannel <- errors.WrapErr("t.messageChannel", errors.NOT_INIT)
return return
} }
buf := make([]byte, CONST_MESSAGE_LEN) buf := make([]byte, CONST_MESSAGE_LEN)
@ -27,7 +27,7 @@ func (t *TUI) launchReadingMessagesFromConnection(ctx context.Context, wg *sync.
timeoutDuration := 5 * time.Second timeoutDuration := 5 * time.Second
err := t.tlsConnection.SetReadDeadline(time.Now().Add(timeoutDuration)) err := t.tlsConnection.SetReadDeadline(time.Now().Add(timeoutDuration))
if err != nil { if err != nil {
t.errors <- errors.WrapErr("SetReadDeadline", err) t.errorsChannel <- errors.WrapErr("SetReadDeadline", err)
return return
} }
n, err := t.tlsConnection.Read(buf) 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() { if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue continue
} }
t.errors <- errors.WrapErr("t.tlsConnection.Read", err) t.errorsChannel <- errors.WrapErr("t.tlsConnection.Read", err)
} }
return return
} }

View File

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

159
tui/ui.go
View File

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