Tony Grosinger
dc4dc2b967
Code changes improve the testability of the code, breaking out functionality into pure functions wherever possible.
235 lines
4.5 KiB
Go
235 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/jroimartin/gocui"
|
|
)
|
|
|
|
//
|
|
// This file does not need to be edited!
|
|
//
|
|
// Please feel free to dig through this file if you are curious, however the
|
|
// contents are fully implemented, so no edits are required to arrive at a
|
|
// functional chat client.
|
|
//
|
|
|
|
var (
|
|
gui *gocui.Gui
|
|
|
|
logsVisible = false
|
|
)
|
|
|
|
func runGUI(cl ClientList) {
|
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
|
if err != nil {
|
|
fmt.Println("Fatal GUI error: ", err)
|
|
os.Exit(1)
|
|
}
|
|
defer gui.Close()
|
|
|
|
gui = g
|
|
|
|
// Set GUI managers and key bindings
|
|
|
|
gui.Cursor = true
|
|
gui.SetManagerFunc(layout)
|
|
|
|
err = gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit)
|
|
if err != nil {
|
|
fmt.Println("Fatal GUI error: ", err)
|
|
os.Exit(1)
|
|
}
|
|
err = gui.SetKeybinding("", gocui.KeyCtrlL, gocui.ModNone, toggleLogs)
|
|
if err != nil {
|
|
fmt.Println("Fatal GUI error: ", err)
|
|
os.Exit(1)
|
|
}
|
|
err = gui.SetKeybinding("enter-text", gocui.KeyEnter, gocui.ModNone, readGuiMsg)
|
|
if err != nil {
|
|
fmt.Println("Fatal GUI error: ", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// We will update the client list after the GUI is initialized because we
|
|
// need to print the name of the initial client we connected to when
|
|
// creating Smudge.
|
|
// If this is skipped, we will not see the initial node connected until
|
|
// another node is added or removed.
|
|
printClientList(cl)
|
|
|
|
if err := gui.MainLoop(); err != nil && err != gocui.ErrQuit {
|
|
fmt.Println("Fatal GUI error: ", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func layout(g *gocui.Gui) error {
|
|
maxX, maxY := g.Size()
|
|
|
|
helpY := maxY - 2
|
|
chatX := 25
|
|
if maxX < 25 {
|
|
// Support for small terminals
|
|
chatX = 15
|
|
}
|
|
|
|
chatMaxY := maxY - 6
|
|
|
|
if v, err := g.SetView("logs", 3, 2, maxX-3, chatMaxY-2); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
|
|
v.Title = "Logs"
|
|
v.Autoscroll = true
|
|
v.Wrap = true
|
|
_, err = g.SetViewOnBottom("logs")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if v, err := g.SetView("help", 0, helpY, maxX, maxY); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
|
|
v.Frame = false
|
|
|
|
fmt.Fprintf(v, "%s %s %s %s %s %s",
|
|
frameText("Ctrl-L"), "Toggle Logs",
|
|
frameText("Ctrl-C"), "Quit",
|
|
frameText("Enter"), "Send Message")
|
|
}
|
|
|
|
if v, err := g.SetView("clients", 0, 0, chatX-1, helpY); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
|
|
v.Title = "Clients"
|
|
}
|
|
|
|
if v, err := g.SetView("messages", chatX, 0, maxX-1, chatMaxY); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
|
|
v.Autoscroll = true
|
|
v.Wrap = true
|
|
v.Title = "Message-History"
|
|
}
|
|
|
|
if v, err := g.SetView("enter-text", chatX, chatMaxY+1, maxX-1, helpY); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
if _, err := g.SetCurrentView("enter-text"); err != nil {
|
|
return err
|
|
}
|
|
|
|
v.Title = "Send:"
|
|
v.Editable = true
|
|
v.Wrap = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readGuiMsg(g *gocui.Gui, v *gocui.View) error {
|
|
msgText := v.Buffer()
|
|
v.Clear()
|
|
|
|
if err := v.SetCursor(0, 0); err != nil {
|
|
return err
|
|
}
|
|
|
|
return SendMessage(msgText)
|
|
}
|
|
|
|
func printChatMessage(msg, sender string) {
|
|
gui.Update(func(g *gocui.Gui) error {
|
|
v, err := g.View("messages")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Fprintf(v, "%s: %s\n", sender, msg)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// printClientList takes a ClientList and prints the username or NodeAddress for
|
|
// each entry into the clients section of the UI.
|
|
func printClientList(cl ClientList) {
|
|
if gui == nil {
|
|
return
|
|
}
|
|
|
|
gui.Update(func(g *gocui.Gui) error {
|
|
v, err := g.View("clients")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
v.Clear()
|
|
err = v.SetCursor(0, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, client := range cl {
|
|
fmt.Fprintln(v, client.GetName())
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func printLogs(msg string) {
|
|
if gui == nil {
|
|
fmt.Println(msg)
|
|
} else {
|
|
gui.Update(func(g *gocui.Gui) error {
|
|
v, err := g.View("logs")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Fprintln(v, msg)
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
func toggleLogs(g *gocui.Gui, v *gocui.View) error {
|
|
if logsVisible {
|
|
_, err := g.SetViewOnBottom("logs")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
_, err := g.SetViewOnTop("logs")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
logsVisible = !logsVisible
|
|
return nil
|
|
}
|
|
|
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
|
return gocui.ErrQuit
|
|
}
|
|
|
|
func stringFormatBoth(fg, bg int, str string, args []string) string {
|
|
return fmt.Sprintf("\x1b[48;5;%dm\x1b[38;5;%d;%sm%s\x1b[0m", bg, fg, strings.Join(args, ";"), str)
|
|
}
|
|
|
|
// Frame text with colors
|
|
func frameText(text string) string {
|
|
return stringFormatBoth(15, 0, text, []string{"1"})
|
|
}
|