2
1
Fork 0
swim/board.go

305 lines
7.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"fmt"
"os"
"reflect"
"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
LaneOff int
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()
fmt.Print("\033[?25h")
os.Exit(0)
case 'N':
b.CreateLane()
case 'n':
b.CreateStory()
case '\n':
// View current story
case 'h', 'j', 'k', 'l', 'H', 'L', 'K', 'J':
b.ClearMessage()
b.Move(ch)
case 'c':
// Comment on current story
case 'D':
// Delete current story
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
}
b.SetMessage("Lane created", false)
}
func (b *Board) CreateStory() {
b.Lanes[b.Current].CreateStory(b)
b.SetMessage("Story created", false)
}
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) GetLaneSlices(width int) [][]string {
laneText := make([][]string, b.Zoom)
for i := b.LaneOff; i < b.LaneOff + b.Zoom; i++ {
var s []string
if i < len(b.Lanes) {
s = b.Lanes[i].StringSlice(width, i == b.Current)
} else {
s = make([]string, 0)
}
laneText[i] = s
}
return laneText
}
func (b Board) LaneHeaderRow(width, pad int) string {
var out strings.Builder
for i := b.LaneOff; i < b.LaneOff + b.Zoom; i++ {
if i < len(b.Lanes) {
out.WriteString(b.Lanes[i].Header(width, i == b.Current))
} else {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, width, width, " ", styleOff))
}
}
if pad > 0 {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, pad, pad, " ", styleOff))
}
out.WriteRune('\n')
return out.String()
}
func (b Board) PrintLanes() string {
var out strings.Builder
laneWidth := b.Width / b.Zoom
laneSlices := b.GetLaneSlices(laneWidth)
pad := b.Width - laneWidth * b.Zoom
out.WriteString(b.LaneHeaderRow(laneWidth, pad))
for row := 0; row < b.Height - 4; row++ {
for _, l := range laneSlices {
if row < len(l) {
out.WriteString(l[row])
} else {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, laneWidth, laneWidth, " ", styleOff))
}
}
if pad > 0 {
out.WriteString(fmt.Sprintf("%s%*.*s%s", style.Lane, pad, pad, " ", styleOff))
}
out.WriteRune('\n')
}
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) ZoomIn() {
if b.Zoom > 1 {
b.Zoom -= 1
}
}
func (b *Board) ZoomOut() {
if b.Width / (b.Zoom+1) > 15 {
b.Zoom += 1
}
}
func (b *Board) Move(ch rune) {
if len(b.Lanes) == 0 {
b.SetMessage("You cannot move what does not exist", true)
return
}
switch ch {
case 'h':
// move left a lane
if b.Current > 0 {
b.Current -= 1
} else {
b.SetMessage("Cannot move further left", true)
}
case 'l':
// move selection right a lane
if b.Current < len(b.Lanes)-1 {
b.Current += 1
} else {
b.SetMessage("Cannot move further right", true)
}
case 'j':
// move selection down a story
if b.Lanes[b.Current].Current < len(b.Lanes[b.Current].Stories)-1 {
b.Lanes[b.Current].Current += 1
} else {
b.SetMessage("Cannot move further down", true)
}
case 'k':
// move selection up a story
if b.Lanes[b.Current].Current > 0 {
b.Lanes[b.Current].Current -= 1
} else {
b.SetMessage("Cannot move further up", true)
}
case 'H':
// move story left a lane
if b.Current == 0 {
b.SetMessage("Cannot move story left", true)
break
}
storyIndex := b.Lanes[b.Current].Current
if len(b.Lanes[b.Current].Stories) <= 0 {
goto MoveLeft
}
b.Lanes[b.Current-1].Stories = append(b.Lanes[b.Current-1].Stories, b.Lanes[b.Current].Stories[storyIndex].Duplicate())
if storyIndex == len(b.Lanes[b.Current].Stories)-1 {
b.Lanes[b.Current].Stories = b.Lanes[b.Current].Stories[:len(b.Lanes[b.Current].Stories)-1]
b.Lanes[b.Current].Current -= 1
} else {
b.Lanes[b.Current].Stories[storyIndex] = b.Lanes[b.Current].Stories[len(b.Lanes[b.Current].Stories)-1]
b.Lanes[b.Current].Stories = b.Lanes[b.Current].Stories[:len(b.Lanes[b.Current].Stories)-1]
}
b.Lanes[b.Current-1].Current = len(b.Lanes[b.Current-1].Stories)-1
MoveLeft: b.Move('h')
case 'L':
// move story right a lane
if b.Current == len(b.Lanes)-1 {
b.SetMessage("Cannot move story right", true)
break
}
storyIndex := b.Lanes[b.Current].Current
if len(b.Lanes[b.Current].Stories) <= 0 {
goto MoveRight
}
b.Lanes[b.Current+1].Stories = append(b.Lanes[b.Current+1].Stories, b.Lanes[b.Current].Stories[storyIndex].Duplicate())
if storyIndex == len(b.Lanes[b.Current].Stories)-1 {
b.Lanes[b.Current].Stories = b.Lanes[b.Current].Stories[:len(b.Lanes[b.Current].Stories)-1]
b.Lanes[b.Current].Current -= 1
} else {
b.Lanes[b.Current].Stories[storyIndex] = b.Lanes[b.Current].Stories[len(b.Lanes[b.Current].Stories)-1]
b.Lanes[b.Current].Stories = b.Lanes[b.Current].Stories[:len(b.Lanes[b.Current].Stories)-1]
}
b.Lanes[b.Current+1].Current = len(b.Lanes[b.Current+1].Stories)-1
MoveRight: b.Move('l')
case 'K':
storyIndex := b.Lanes[b.Current].Current
if storyIndex <= 0 {
b.SetMessage("Cannot move story up", true)
break
}
swapper := reflect.Swapper(b.Lanes[b.Current].Stories)
swapper(storyIndex, storyIndex-1)
b.Move('k')
case 'J':
storyIndex := b.Lanes[b.Current].Current
if storyIndex > len(b.Lanes[b.Current].Stories)-2 {
b.SetMessage("Cannot move story down", true)
break
}
swapper := reflect.Swapper(b.Lanes[b.Current].Stories)
swapper(storyIndex, storyIndex+1)
b.Move('j')
}
}
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())
}