bombadillo/cui/screen.go

164 lines
4.1 KiB
Go

package cui
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
)
// screenInit records whether or not the screen has been initialized
// this is used to prevent more than one screen from being used
var screenInit bool = false
// Screen represent the top level abstraction for a cui application.
// It takes up the full width and height of the terminal window and
// holds the various Windows and MsgBars for the application as well
// as a record of which window is active for control purposes.
type Screen struct {
Height int
Width int
Windows []*Window
Activewindow int
Bars []*MsgBar
}
// AddWindow adds a new window to the Screen struct in question
func (s *Screen) AddWindow(r1, c1, r2, c2 int, scroll, border, show bool) {
w := Window{box{r1, c1, r2, c2}, scroll, 0, []string{}, border, false, show}
s.Windows = append(s.Windows, &w)
}
// AddMsgBar adds a new MsgBar to the Screen struct in question
func (s *Screen) AddMsgBar(row int, title, msg string, showTitle bool) {
b := MsgBar{row, title, msg, showTitle}
s.Bars = append(s.Bars, &b)
}
// DrawAllWindows loops over every window in the Screen struct and
// draws it to screen in index order (smallest to largest)
func (s Screen) DrawAllWindows() {
s.Clear()
for _, w := range s.Windows {
if w.Show {
w.DrawWindow()
}
}
MoveCursorTo(s.Height-1, 1)
}
// Clear removes all content from the interior of the screen
func (s Screen) Clear() {
for i := 0; i <= s.Height; i++ {
MoveCursorTo(i, 0)
Clear("line")
}
}
// ReflashScreen checks for a screen resize and resizes windows if
// needed then redraws the screen. It takes a bool to decide whether
// to redraw the full screen or just the content. On a resize
// event, the full screen will always be redrawn.
func (s *Screen) ReflashScreen(clearScreen bool) {
oldh, oldw := s.Height, s.Width
s.GetSize()
if s.Height != oldh || s.Width != oldw {
// TODO this should be pure library code and not rely on
// specific windows being present with specific behaviors.
// Maybe allow windows to have a resize function that can
// be declared within the application?
// For now this will be ok though.
s.Windows[0].Box.row2 = s.Height - 2
s.Windows[0].Box.col2 = s.Width
bookmarksWidth := 40
if s.Width < 40 {
bookmarksWidth = s.Width
}
s.Windows[1].Box.row2 = s.Height - 2
s.Windows[1].Box.col1 = s.Width - bookmarksWidth
s.Windows[1].Box.col2 = s.Width
s.DrawAllWindows()
s.DrawMsgBars()
} else if clearScreen {
s.DrawAllWindows()
s.DrawMsgBars()
} else {
for _, w := range s.Windows {
if w.Show {
w.DrawWindow()
}
}
}
}
// DrawMsgBars draws all MsgBars present in the Screen struct.
// All MsgBars are looped over and drawn in index order (sm - lg).
func (s *Screen) DrawMsgBars() {
for _, bar := range s.Bars {
MoveCursorTo(bar.row, 1)
Clear("line")
fmt.Print("\033[7m")
fmt.Print(strings.Repeat(" ", s.Width))
MoveCursorTo(bar.row, 1)
var buf bytes.Buffer
title := bar.title
fmt.Print(title)
if len(bar.title) > s.Width {
title = string(bar.title[:s.Width-3]) + "..."
}
_, _ = buf.WriteString(title)
msg := bar.message
if len(bar.message) > s.Width-len(title) {
msg = string(bar.message[:s.Width-len(title)-3]) + "..."
}
_, _ = buf.WriteString(msg)
MoveCursorTo(bar.row, 1)
fmt.Print(buf.String())
fmt.Print("\033[0m")
}
}
// GetSize retrieves the terminal size and sets the Screen
// width and height to that size
func (s *Screen) GetSize() {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
fmt.Println("Fatal error: Unable to retrieve terminal size")
os.Exit(1)
}
var h, w int
fmt.Sscan(string(out), &h, &w)
s.Height = h
s.Width = w
}
// - - - - - - - - - - - - - - - - - - - - - - - - - -
// NewScreen is a constructor function that returns a pointer
// to a Screen struct
func NewScreen() (*Screen, error) {
if screenInit {
fmt.Println("Fatal error: Cannot create multiple screens")
os.Exit(1)
}
var s Screen
s.GetSize()
for i := 0; i < s.Height; i++ {
fmt.Println()
}
err := SetCharMode()
if err != nil {
return nil, err
}
Clear("screen")
screenInit = true
return &s, nil
}