ditty/gui.go

367 lines
8.4 KiB
Go
Raw Normal View History

2020-01-08 23:38:41 +00:00
package main
import (
2020-01-09 17:48:00 +00:00
"bytes"
2020-01-08 23:38:41 +00:00
"fmt"
"math"
"os"
"path"
2020-01-08 23:38:41 +00:00
"path/filepath"
"strings"
2020-01-09 17:48:00 +00:00
"sync"
2020-01-08 23:38:41 +00:00
"time"
"git.sr.ht/~tslocum/cview"
2020-01-09 23:51:37 +00:00
"github.com/faiface/beep"
2020-01-08 23:38:41 +00:00
"github.com/faiface/beep/speaker"
2020-01-09 23:51:37 +00:00
"github.com/gdamore/tcell"
"github.com/mattn/go-runewidth"
2020-01-08 23:38:41 +00:00
)
var (
app *cview.Application
mainbuf *cview.TextView
queuebuf *cview.TextView
topstatusbuf *cview.TextView
bottomstatusbuf *cview.TextView
mainBufferFiles []*LibraryEntry
mainBufferCursor int
mainBufferDirectory string
2020-01-09 01:50:52 +00:00
mainBufferOrigin int
mainBufferAutoFocus string // Entry path to focus after loading display
2020-01-08 23:38:41 +00:00
seekStart, seekEnd int
volumeStart, volumeEnd int
screenWidth, screenHeight int
mainBufHeight int
2020-01-09 17:48:00 +00:00
mainBuffer bytes.Buffer
mainLock = new(sync.Mutex)
statusText string
statusBuffer bytes.Buffer
statusLock = new(sync.Mutex)
2020-01-08 23:38:41 +00:00
)
func initTUI() error {
app = cview.NewApplication()
app.EnableMouse()
2020-01-22 01:10:20 +00:00
setDefaultKeyBinds()
app.SetInputCapture(inputConfig.Capture)
2020-01-08 23:38:41 +00:00
app.SetAfterResizeFunc(handleResize)
app.SetMouseCapture(handleMouse)
grid := cview.NewGrid().SetRows(-2, -1, 1, 1).SetColumns(-1)
mainbuf = cview.NewTextView().SetDynamicColors(true).SetWrap(true).SetWordWrap(false)
queuebuf = cview.NewTextView().SetDynamicColors(true).SetWrap(true).SetWordWrap(false)
topstatusbuf = cview.NewTextView().SetWrap(false).SetWordWrap(false)
bottomstatusbuf = cview.NewTextView().SetWrap(false).SetWordWrap(false)
mainbuf.SetBorder(true).SetTitleAlign(cview.AlignLeft)
queuebuf.SetBorder(true).SetTitleAlign(cview.AlignLeft).SetTitle(" Queue ")
grid.AddItem(mainbuf, 0, 0, 1, 1, 0, 0, false)
grid.AddItem(queuebuf, 1, 0, 1, 1, 0, 0, false)
grid.AddItem(topstatusbuf, 2, 0, 1, 1, 0, 0, false)
grid.AddItem(bottomstatusbuf, 3, 0, 1, 1, 0, 0, false)
mainbuf.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (i int, i2 int, i3 int, i4 int) {
mainBufHeight = height
return mainbuf.GetInnerRect()
})
app.SetRoot(grid, true)
return nil
}
func browseFolder(browse string) {
var err error
browse, err = filepath.Abs(browse)
if err != nil {
return
}
if browse == mainBufferDirectory {
mainBufferAutoFocus = ""
return
}
2020-01-08 23:38:41 +00:00
mainBufferFiles = scanFolder(browse)
mainBufferCursor = 0
2020-01-09 23:51:37 +00:00
mainBufferOrigin = 0
2020-01-08 23:38:41 +00:00
mainBufferDirectory = browse
if mainBufferAutoFocus != "" {
autoSelectAbs, err := filepath.Abs(mainBufferAutoFocus)
if err == nil && autoSelectAbs != mainBufferDirectory {
autoSelect := -1
var entryPath string
for i, entry := range mainBufferFiles {
if !entry.File.IsDir() {
continue
}
entryPath, err = filepath.Abs(path.Join(mainBufferDirectory, entry.File.Name()))
if err == nil {
if entryPath == autoSelectAbs {
autoSelect = i
break
}
}
}
if autoSelect >= 0 {
mainBufferCursor = autoSelect
mainBufferNewOrigin := (mainBufferCursor - (mainBufHeight - 4)) + ((mainBufHeight - 2) / 2)
if mainBufferNewOrigin <= 0 {
mainBufferNewOrigin = 0
} else if mainBufferNewOrigin > len(mainBufferFiles)-(mainBufHeight-3) {
mainBufferNewOrigin = len(mainBufferFiles) - (mainBufHeight - 3)
}
mainBufferOrigin = mainBufferNewOrigin
mainBufferAutoFocus = ""
go listNext()
return
}
}
mainBufferAutoFocus = ""
}
2020-01-09 23:51:37 +00:00
go app.QueueUpdateDraw(updateMain)
2020-01-08 23:38:41 +00:00
}
2020-01-22 01:10:20 +00:00
func browseParent() {
mainBufferAutoFocus = mainBufferDirectory
go browseFolder(path.Join(mainBufferDirectory, ".."))
}
2020-01-08 23:38:41 +00:00
func updateMain() {
2020-01-09 17:48:00 +00:00
mainLock.Lock()
defer mainLock.Unlock()
mainBuffer.Reset()
var statusMessage string
2020-01-08 23:38:41 +00:00
if statusText != "" {
2020-01-09 17:48:00 +00:00
statusMessage = statusText
2020-01-08 23:38:41 +00:00
} else {
2020-01-09 17:48:00 +00:00
statusMessage = mainBufferDirectory
}
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
truncated := false
widthRequirement := 4
for {
if runewidth.StringWidth(statusMessage) <= screenWidth-widthRequirement || !strings.ContainsRune(statusMessage, os.PathSeparator) {
break
}
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
statusMessage = statusMessage[strings.IndexRune(statusMessage, '/')+1:]
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
truncated = true
widthRequirement = 8
}
if truncated {
mainBuffer.WriteString(".../")
2020-01-08 23:38:41 +00:00
}
2020-01-09 17:48:00 +00:00
mainBuffer.WriteString(statusMessage)
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
mainbuf.SetTitle(" " + runewidth.Truncate(mainBuffer.String(), screenWidth-4, "...") + " ")
mainBuffer.Reset()
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
var printed int
var line string
2020-01-09 01:50:52 +00:00
if mainBufferOrigin == 0 {
if mainBufferCursor == 0 {
2020-01-09 17:48:00 +00:00
mainBuffer.WriteString("[::r]")
2020-01-09 01:50:52 +00:00
}
if mainBufferDirectory == "/" {
line = "./"
} else {
line = "../"
}
2020-01-09 17:48:00 +00:00
mainBuffer.WriteString(line)
2020-01-09 01:50:52 +00:00
for i := len(line); i < screenWidth-2; i++ {
2020-01-09 17:48:00 +00:00
mainBuffer.WriteRune(' ')
2020-01-09 01:50:52 +00:00
}
if mainBufferCursor == 0 {
2020-01-09 17:48:00 +00:00
mainBuffer.WriteString("[-]")
2020-01-09 01:50:52 +00:00
}
printed++
2020-01-08 23:38:41 +00:00
}
for i, entry := range mainBufferFiles {
2020-01-09 01:50:52 +00:00
if i < mainBufferOrigin-1 || i-mainBufferOrigin-1 > mainBufHeight-1 {
continue
}
if printed > 0 {
2020-01-09 17:48:00 +00:00
mainBuffer.WriteRune('\n')
2020-01-09 01:50:52 +00:00
}
2020-01-08 23:38:41 +00:00
if i == mainBufferCursor-1 {
2020-01-09 17:48:00 +00:00
mainBuffer.WriteString("[::r]")
2020-01-08 23:38:41 +00:00
}
if entry.File.IsDir() {
line = entry.File.Name() + "/"
} else {
line = entry.String()
}
2020-01-09 17:48:00 +00:00
mainBuffer.WriteString(line)
2020-01-08 23:38:41 +00:00
for i := runewidth.StringWidth(line); i < screenWidth-2; i++ {
2020-01-09 17:48:00 +00:00
mainBuffer.WriteRune(' ')
2020-01-08 23:38:41 +00:00
}
if i == mainBufferCursor-1 {
2020-01-09 17:48:00 +00:00
mainBuffer.WriteString("[-]")
2020-01-08 23:38:41 +00:00
}
printed++
2020-01-09 01:50:52 +00:00
if printed == mainBufHeight-2 {
2020-01-08 23:38:41 +00:00
break
}
}
2020-01-09 17:48:00 +00:00
mainbuf.SetText(mainBuffer.String())
2020-01-08 23:38:41 +00:00
}
func updateQueue() {
// TODO
}
func updateStatus() {
2020-01-09 17:48:00 +00:00
statusLock.Lock()
defer statusLock.Unlock()
2020-01-08 23:38:41 +00:00
var sampleRate beep.SampleRate
2020-01-09 17:48:00 +00:00
var p time.Duration
2020-01-08 23:38:41 +00:00
var l time.Duration
var v float64
2020-01-09 17:48:00 +00:00
var paused bool
var silent bool
if playingStreamer != nil && volume != nil && ctrl != nil {
2020-01-09 23:51:37 +00:00
audioLock.Lock()
2020-01-09 17:48:00 +00:00
speaker.Lock()
silent = volume.Silent
paused = ctrl.Paused
sampleRate = playingFormat.SampleRate
p = playingFormat.SampleRate.D(playingStreamer.Position()).Truncate(time.Second)
l = playingFormat.SampleRate.D(playingStreamer.Len()).Truncate(time.Second)
v = volume.Volume
2020-01-08 23:38:41 +00:00
speaker.Unlock()
2020-01-09 23:51:37 +00:00
audioLock.Unlock()
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
statusBuffer.Reset()
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
if paused {
statusBuffer.WriteString("Paused ")
}
statusBuffer.WriteString(fmt.Sprintf(" %dHz %s", sampleRate.N(time.Second), fileFormat(playingFileName)))
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
topStatusExtra := statusBuffer.String()
statusBuffer.Reset()
topStatusMaxLength := screenWidth - 2
printExtra := topStatusMaxLength >= (len(topStatusExtra)*2)+1
if printExtra {
topStatusMaxLength -= len(topStatusExtra)
2020-01-08 23:38:41 +00:00
}
2020-01-09 17:48:00 +00:00
statusBuffer.WriteRune(' ')
var trackInfo string
if playingFileInfo != "" {
trackInfo = runewidth.Truncate(playingFileInfo, topStatusMaxLength, "...")
} else {
trackInfo = runewidth.Truncate(playingFileName, topStatusMaxLength, "...")
2020-01-08 23:38:41 +00:00
}
2020-01-09 17:48:00 +00:00
statusBuffer.WriteString(trackInfo)
if printExtra {
padding := topStatusMaxLength - runewidth.StringWidth(trackInfo)
for i := 0; i < padding; i++ {
statusBuffer.WriteRune(' ')
}
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
statusBuffer.WriteString(topStatusExtra)
}
topstatusbuf.SetText(statusBuffer.String())
2020-01-08 23:38:41 +00:00
}
2020-01-09 17:48:00 +00:00
statusBuffer.Reset()
if silent {
statusBuffer.WriteString("Mut ")
2020-01-08 23:38:41 +00:00
for i := -7.5; i < 0.0; i += 0.5 {
2020-01-22 01:10:20 +00:00
statusBuffer.WriteRune(' ')
2020-01-08 23:38:41 +00:00
}
} else {
2020-01-09 17:48:00 +00:00
statusBuffer.WriteString("Vol ")
2020-01-08 23:38:41 +00:00
for i := -7.5; i < v-0.5; i += 0.5 {
2020-01-09 17:48:00 +00:00
statusBuffer.WriteRune(tcell.RuneHLine)
2020-01-08 23:38:41 +00:00
}
statusBuffer.WriteRune('▷')
2020-01-08 23:38:41 +00:00
for i := v; i < 0; i += 0.5 {
statusBuffer.WriteRune(' ')
2020-01-08 23:38:41 +00:00
}
}
2020-01-09 17:48:00 +00:00
bottomStatus := fmt.Sprintf("%s %s", formatDuration(l), statusBuffer.String())
statusBuffer.Reset()
2020-01-08 23:38:41 +00:00
2020-01-09 17:48:00 +00:00
var progressIndicator string
2020-01-08 23:38:41 +00:00
if paused {
2020-01-09 17:48:00 +00:00
progressIndicator = "||"
2020-01-08 23:38:41 +00:00
} else {
progressIndicator = "▷"
2020-01-08 23:38:41 +00:00
}
2020-01-09 17:48:00 +00:00
padding := screenWidth - runewidth.StringWidth(bottomStatus) - len(formatDuration(p)) - runewidth.StringWidth(progressIndicator) - 3
position := int(float64(padding) * (float64(p) / float64(l)))
2020-01-08 23:38:41 +00:00
if position > padding-1 {
position = padding - 1
}
if paused && position > 0 {
position--
}
for i := 0; i < padding; i++ {
if i == position {
2020-01-09 17:48:00 +00:00
statusBuffer.WriteString(progressIndicator)
2020-01-08 23:38:41 +00:00
} else {
2020-01-09 17:48:00 +00:00
statusBuffer.WriteRune(tcell.RuneHLine)
2020-01-08 23:38:41 +00:00
}
}
2020-01-09 17:48:00 +00:00
seekStart = len(formatDuration(p)) + 2
2020-01-08 23:38:41 +00:00
seekEnd = seekStart + padding - 1
volumeStart = seekEnd + len(formatDuration(l)) + 4
volumeEnd = screenWidth - 2
2020-01-09 17:48:00 +00:00
bottomstatusbuf.SetText(" " + formatDuration(p) + " " + statusBuffer.String() + " " + bottomStatus)
statusBuffer.Reset()
2020-01-08 23:38:41 +00:00
}
func formatDuration(d time.Duration) string {
minutes := int(math.Floor(float64(d) / float64(time.Minute)))
seconds := int((d % time.Minute) / time.Second)
return fmt.Sprintf("%02d:%02d", minutes, seconds)
}
2020-01-22 01:10:20 +00:00
func handleResize(width int, height int) {
screenWidth, screenHeight = width, height
2020-01-08 23:38:41 +00:00
updateMain()
updateQueue()
updateStatus()
}