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"}) }