2
1
Fork 0

Some screen drawing is happening now but it is janky

This commit is contained in:
sloum 2021-03-22 22:46:01 -07:00
parent 4dae95f7d6
commit c972b2e2f5
12 changed files with 483 additions and 7 deletions

179
board.go Normal file
View File

@ -0,0 +1,179 @@
package main
import (
"fmt"
"os"
"strings"
"time"
"tildegit.org/sloum/swim/termios"
)
const (
swimLogo string = "\033[7m ▟\033[27m "
cursorHome string = "\033[0;0H"
styleOff string = "\033[0m"
upAndLeft string = "\033[1A\033[500D"
cursorEnd string = "\033[500;500H"
)
type Board struct {
Title string
Body string
Created time.Time
Lanes []Lane
Current int // Index of current lane
Message string
MsgErr bool
Width int
Height int
Zoom int
}
func (b *Board) Run() {
defer termios.Restore()
termios.SetCharMode()
var ch rune
for {
b.Draw()
ch = Getch()
switch ch {
case 'Q':
termios.Restore()
os.Exit(0)
case 'N':
b.CreateLane()
case 'n':
b.CreateStory()
case '\n':
// View current story
case 'h', 'j', 'k', 'l':
// Move cursor, context dependent
// If a story is open, will scroll
// the story, otherwise will select
// a story
case 'c':
// Comment on current story
case 'a':
// Archive the current story
case 'd':
// Delete current story
case 'D':
// Delete current lane
case 'e':
// Edit current story
case ':':
b.EnterCommand()
case '+':
// b.ZoomIn()
case '-':
// b.ZoomOut()
}
}
}
func (b *Board) ClearMessage() {
b.Message = ""
b.MsgErr = false
}
func (b *Board) SetMessage(msg string, isError bool) {
b.Message = msg
b.MsgErr = isError
}
func (b *Board) CreateLane() {
laneTitle, err := GetAndConfirmCommandLine("Lane Title: ")
if err != nil {
b.SetMessage(err.Error(), true)
return
}
b.Lanes = append(b.Lanes, MakeLane(laneTitle))
if b.Current < 0 {
b.Current = 0
}
}
func (b *Board) CreateStory() {
b.Lanes[b.Current].CreateStory(b)
}
func (b Board) PrintHeader() string {
return fmt.Sprintf("%s%s%-*.*s%s\n", style.Header, swimLogo, b.Width-12, b.Width-12, b.Title, styleOff)
}
func (b Board) PrintInputArea() string {
return fmt.Sprintf("%s%-*.*s%s\n", style.Input, b.Width, b.Width, " ", styleOff)
}
func (b Board) PrintLanes() string {
var out strings.Builder
laneWidth := b.Width / b.Zoom
if len(b.Lanes) == 0 {
for i := 0; i < b.Height - 3; i++ {
out.WriteString(fmt.Sprintf("%s%*.*s%s\n", style.Lane, b.Width, b.Width, " ", styleOff))
}
return out.String()
}
laneText := make([][]string, 0, len(b.Lanes))
maxLen := 0
for _, l := range b.Lanes {
s := l.StringSlice(laneWidth)
laneText = append(laneText, s)
if len(s) > maxLen {
maxLen = len(s)
}
}
for i := 0; i < b.Height - 3; i++ {
for li, l := range laneText {
// TODO fix this
if li >= b.Zoom {
out.WriteRune('\n')
break
}
if i < len(l) {
out.WriteString(l[i])
} else {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, laneWidth, laneWidth, " ", styleOff))
}
}
}
out.WriteString(styleOff)
return out.String()
}
func (b *Board) EnterCommand() {
var out strings.Builder
out.WriteString(upAndLeft) // Move up one and over all
out.WriteString(style.Input)
fmt.Print(out.String())
command, err := GetLine(":")
if err != nil {
b.SetMessage(err.Error(), true)
}
b.SetMessage(command, false)
}
func (b Board) PrintMessage() string {
var out strings.Builder
if b.MsgErr {
out.WriteString(style.MessageErr)
} else {
out.WriteString(style.Message)
}
out.WriteString(fmt.Sprintf("%-*.*s%s", b.Width, b.Width, b.Message, styleOff))
return out.String()
}
func (b Board) Draw() {
var out strings.Builder
out.WriteString(cursorHome)
out.WriteString(b.PrintHeader())
out.WriteString(b.PrintLanes())
out.WriteString(b.PrintInputArea())
out.WriteString(b.PrintMessage())
fmt.Print(out.String())
}

52
colors.go Normal file
View File

@ -0,0 +1,52 @@
package main
const (
SimpleColor int = iota
EightBitColor
TrueColor
)
type Styles struct {
Mode int
Header string
Message string
MessageErr string
Lane string
Input string
}
var style Styles
var colors = map[int]map[string]string{
SimpleColor: map[string]string{
"Header": "\033[34;107;1m", // bold blue on bright white
"Message": "\033[97;42m", // bright white on green
"MessageErr": "\033[97;41m", // bright white on red
"Lane": "\033[30;104m", // black on bright blue
"Input": "\033[30;107m"}, // black on bright white
EightBitColor: map[string]string{
"Header": "\033[48;5;254m\033[38;5;21\033[1m",
"Message": "\033[48;5;35m\033[38;5;231m",
"MessageErr": "\033[48;5;124m\033[38;5;231m",
"Lane": "\033[48;5;63m\033[38;5;235m",
"Input": "\033[48;5;231m\033[38;5;235"},
TrueColor: map[string]string{
"Header": "",
"Message": "",
"MessageErr": "",
"Lane": "",
"Input": ""}}
func (s *Styles) Init(mode int) {
if mode == TrueColor || mode == EightBitColor {
s.Mode = mode
} else {
s.Mode = SimpleColor
}
s.Header = colors[s.Mode]["Header"]
s.Message = colors[s.Mode]["Message"]
s.MessageErr = colors[s.Mode]["MessageErr"]
s.Lane = colors[s.Mode]["Lane"]
s.Input = colors[s.Mode]["Input"]
}

11
comment.go Normal file
View File

@ -0,0 +1,11 @@
package main
import (
"time"
)
type Comment struct {
User string
Body string
Created time.Time
}

39
lane.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"fmt"
)
type Lane struct {
Title string
Stories []Story
Current int // Index of current story
}
func (l *Lane) CreateStory(b *Board) {
storyTitle, err := GetAndConfirmCommandLine("Story Title: ")
if err != nil {
b.SetMessage(err.Error(), true)
return
}
l.Stories = append(l.Stories, MakeStory(storyTitle))
if l.Current < 0 {
l.Current = 0
}
}
func (l *Lane) StringSlice(width int) []string {
out := make([]string, 0, len(l.Stories) * 3 + 1)
for _, story := range l.Stories {
out = append(out, fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
out = append(out, fmt.Sprintf("%s %s%-*.*s%s %s", style.Lane, style.Input, width-2, width-2, story.Title, style.Lane, styleOff))
}
if len(out) > 0 {
out = append(out, fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
}
return out
}
func MakeLane(title string) Lane {
return Lane{title, make([]Story,0,5), -1}
}

88
main.go
View File

@ -1,5 +1,89 @@
package main
func main() {
print("This will be a swim lane project planning application")
import (
"bufio"
"fmt"
"os"
"tildegit.org/sloum/swim/termios"
"time"
)
var board Board
func Getch() rune {
reader := bufio.NewReader(os.Stdin)
char, _, err := reader.ReadRune()
if err != nil {
return 0
}
return char
}
func GetLine(prefix string) (string, error) {
defer termios.SetCharMode()
termios.SetLineMode()
reader := bufio.NewReader(os.Stdin)
fmt.Print(prefix)
fmt.Print("\033[?25h")
text, err := reader.ReadString('\n')
if err != nil {
return "", err
}
fmt.Print("\033[?25l")
return text[:len(text)-1], nil
}
func GetCommandLine(prefix string) (string, error) {
fmt.Print(upAndLeft) // Move up one and over all
fmt.Print(style.Input)
line, err := GetLine(prefix)
fmt.Print(cursorEnd)
return line, err
}
func GetAndConfirmCommandLine(prefix string) (string, error) {
var conf rune
var err error
var line string
for {
line, err = GetCommandLine(prefix)
if err != nil {
return line, err
}
VerifyQuery: fmt.Print(upAndLeft) // Move up one and over all
fmt.Print(style.Input)
fmt.Printf("%s%s%sIs %q correct? (y/n/c)", cursorEnd, upAndLeft, style.Input, line)
conf = Getch()
if conf == 'y' {
break
} else if conf == 'n' {
continue
} else if conf == 'c' {
err = fmt.Errorf("Cancelled")
break
} else {
goto VerifyQuery
}
}
return line, err
}
func main() {
style.Init(SimpleColor)
cols, rows := termios.GetWindowSize()
board = Board{
Title: "My Test Board",
Body: "Some misc info",
Created: time.Now(),
Lanes: make([]Lane, 0, 1),
Current: -1,
Message: "Welcome to SWIM",
MsgErr: false,
Width: cols,
Height: rows,
Zoom: 3}
board.Run()
}

View File

@ -7,22 +7,21 @@ A project planning board for the terminal.
1. story
- title string
- details string
- body string
- points int
- tag int // enum representing a color
- assignee string // username
- reviewer string // username
- users []string
- comments []comment
- created time.time // the time the story was created
2. comment
- user string
- comment string
- body string
- created time.time
3. lane
- title string
- stories []story
4. board
- title string
- details string
- body string
- created time.time
- lanes []lane

20
story.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"time"
)
type Story struct {
Title string
Body string
Users []string
Tag int
Tasks []Task
Comments []Comment
Created time.Time
Updated time.Time
}
func MakeStory(title string) Story {
return Story{title,"", make([]string,0,2), -1, make([]Task,0,2), make([]Comment,0,2), time.Now(), time.Now()}
}

BIN
swim Executable file

Binary file not shown.

7
task.go Normal file
View File

@ -0,0 +1,7 @@
package main
type Task struct {
Body string
Complete bool
}

10
termios/consts_linux.go Normal file
View File

@ -0,0 +1,10 @@
// +build linux
package termios
import "syscall"
const (
getTermiosIoctl = syscall.TCGETS
setTermiosIoctl = syscall.TCSETS
)

View File

@ -0,0 +1,10 @@
// +build !linux
package termios
import "syscall"
const (
getTermiosIoctl = syscall.TIOCGETA
setTermiosIoctl = syscall.TIOCSETAF
)

65
termios/termios.go Normal file
View File

@ -0,0 +1,65 @@
package termios
import (
"os"
"runtime"
"syscall"
"unsafe"
)
type winsize struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
var fd = os.Stdin.Fd()
var initial = getTermios()
func ioctl(fd, request, argp uintptr) error {
if _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, request, argp); e != 0 {
return e
}
return nil
}
func GetWindowSize() (int, int) {
var value winsize
ioctl(fd, syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&value)))
return int(value.Col), int(value.Row)
}
func getTermios() syscall.Termios {
var value syscall.Termios
err := ioctl(fd, getTermiosIoctl, uintptr(unsafe.Pointer(&value)))
if err != nil {
panic(err)
}
return value
}
func setTermios(termios syscall.Termios) {
err := ioctl(fd, setTermiosIoctl, uintptr(unsafe.Pointer(&termios)))
if err != nil {
panic(err)
}
runtime.KeepAlive(termios)
}
func SetCharMode() {
t := getTermios()
t.Lflag = t.Lflag ^ syscall.ICANON
t.Lflag = t.Lflag ^ syscall.ECHO
setTermios(t)
}
func SetLineMode() {
var t = getTermios()
t.Lflag = t.Lflag | (syscall.ICANON | syscall.ECHO)
setTermios(t)
}
func Restore() {
setTermios(initial)
}