Use termbox-screen library

This commit is contained in:
Brian Buller 2019-02-28 07:44:03 -06:00
parent 1c290b6b0b
commit 5d95e080ef
8 changed files with 122 additions and 226 deletions

View File

@ -6,7 +6,9 @@ import (
"strings"
todotxt "github.com/br0xen/go-todotxt"
"github.com/br0xen/termbox-screen"
"github.com/br0xen/user-config"
termbox "github.com/nsf/termbox-go"
)
type AppState struct {
@ -28,11 +30,22 @@ type AppState struct {
taskListLoaded bool
doneListLoaded bool
screens []Screen
uiManager *termboxScreen.Manager
lang *Translator
}
const (
DefaultFg = termbox.ColorWhite
DefaultBg = termbox.ColorBlack
TitleFg = termbox.ColorWhite
TitleBg = termbox.ColorBlue
CursorFg = termbox.ColorBlack
CursorBg = termbox.ColorGreen
ExitScreenId = -1
)
func NewApp() *AppState {
app := &AppState{Name: AppName, Version: AppVersion}
app.initialize()
@ -49,7 +62,25 @@ func (a *AppState) run(parms []string) int {
if len(parms) == 0 || parms[0] == "ui" {
// UI Mode
a.mode = ResModeUI
return uiLoop()
a.uiManager = termboxScreen.NewManager()
a.uiManager.AddScreen(&MainScreen{})
a.uiManager.AddScreen(&TaskScreen{})
a.uiManager.AddScreen(&AboutScreen{})
for k := range a.uiManager.GetScreens() {
fmt.Print("Screen:", k, "\n")
}
a.uiManager.SetDisplayScreen(MainScreenId)
mainBundle := termboxScreen.Bundle{}
mainBundle.SetValue(MainBundleListKey, MainBundleListTodo)
if err := a.uiManager.InitializeScreen(MainScreenId, mainBundle); err != nil {
return 1
}
if err := a.uiManager.Loop(); err != nil {
return 1
}
return 0
}
a.mode = ResModeCLI
if fn, ok := a.OpFuncs[parms[0]]; ok {

View File

@ -1,28 +0,0 @@
package main
type Bundle map[string]interface{}
func (b Bundle) setValue(key string, val interface{}) {
b[key] = val
}
func (b Bundle) getBool(key string, def bool) bool {
if v, ok := b[key].(bool); ok {
return v
}
return def
}
func (b Bundle) getString(key, def string) string {
if v, ok := b[key].(string); ok {
return v
}
return def
}
func (b Bundle) getInt(key string, def int) int {
if v, ok := b[key].(int); ok {
return v
}
return def
}

View File

@ -2,7 +2,7 @@ package main
import "errors"
type ResourceId uint16
type ResourceId uint32
type Translator struct {
values map[ResourceId]string

View File

@ -1,73 +0,0 @@
package main
import (
"time"
termbox "github.com/nsf/termbox-go"
)
type Screen interface {
handleKeyEvent(termbox.Event) int
initialize(Bundle) error
drawScreen()
}
const (
ScreenMain = iota
ScreenTask
ScreenAbout
ScreenExit
DefaultBg = termbox.ColorBlack
DefaultFg = termbox.ColorWhite
TitleFg = termbox.ColorWhite
TitleBg = termbox.ColorBlue
CursorFg = termbox.ColorBlack
CursorBg = termbox.ColorGreen
)
func (a *AppState) BuildScreens() {
mainScreen := MainScreen{}
aboutScreen := AboutScreen{}
taskScreen := TaskScreen{}
a.screens = append(a.screens, &mainScreen)
a.screens = append(a.screens, &taskScreen)
a.screens = append(a.screens, &aboutScreen)
}
func (a *AppState) drawBackground(bg termbox.Attribute) {
termbox.Clear(0, bg)
}
func (a *AppState) layoutAndDrawScreen(s Screen) {
a.drawBackground(DefaultBg)
s.drawScreen()
termbox.Flush()
}
func readUserInput(e chan termbox.Event) {
for {
e <- termbox.PollEvent()
}
}
func checkForUpdate(e chan termbox.Event) {
for {
time.Sleep(time.Minute)
if app.diskListChanged() {
e <- termbox.Event{
Type: termbox.EventError,
Err: app.e(ResStrListChanged),
}
}
}
}
/*
* ViewPort helps keep track of what's being displayed on the screen
*/
type ViewPort struct {
bytesPerRow int
numberOfRows int
firstRow int
}

View File

@ -4,13 +4,15 @@ import (
"fmt"
"time"
"github.com/br0xen/termbox-screen"
"github.com/br0xen/termbox-util"
termbox "github.com/nsf/termbox-go"
)
const AboutScreenId = 2
// AboutScreen holds all that's going on
type AboutScreen struct {
viewPort ViewPort
message string
messageTimeout time.Duration
messageTime time.Time
@ -25,7 +27,9 @@ type Command struct {
description string
}
func (screen *AboutScreen) initialize(bundle Bundle) error {
func (screen *AboutScreen) Id() int { return AboutScreenId }
func (screen *AboutScreen) Initialize(bundle termboxScreen.Bundle) error {
screen.titleTemplate = []string{
" __ ",
" _________ _____| | __",
@ -53,12 +57,13 @@ func (screen *AboutScreen) initialize(bundle Bundle) error {
return nil
}
func (screen *AboutScreen) ResizeScreen() { screen.Initialize(nil) }
func (screen *AboutScreen) handleKeyEvent(event termbox.Event) int {
return ScreenMain
func (screen *AboutScreen) HandleKeyEvent(event termbox.Event) int {
return MainScreenId
}
func (screen *AboutScreen) drawScreen() {
func (screen *AboutScreen) DrawScreen() {
width, height := termbox.Size()
xPos := (width - len(screen.titleTemplate[0])) / 2
yPos := 1

View File

@ -7,10 +7,19 @@ import (
"time"
todotxt "github.com/br0xen/go-todotxt"
"github.com/br0xen/termbox-screen"
"github.com/br0xen/termbox-util"
termbox "github.com/nsf/termbox-go"
)
const MainScreenId = 0
type ViewPort struct {
bytesPerRow int
numberOfRows int
firstRow int
}
// MainScreen holds all that's going on
type MainScreen struct {
viewPort ViewPort
@ -51,7 +60,9 @@ const (
InputIDUnArchiveTask = "move task to active list? (y/n)"
)
func (screen *MainScreen) initialize(bundle Bundle) error {
func (screen *MainScreen) Id() int { return MainScreenId }
func (screen *MainScreen) Initialize(bundle termboxScreen.Bundle) error {
width, height := termbox.Size()
screen.inputField = termboxUtil.CreateInputField(0, (height - 3), width, 1, DefaultFg, DefaultBg)
@ -66,9 +77,10 @@ func (screen *MainScreen) initialize(bundle Bundle) error {
return nil
}
func (screen *MainScreen) ResizeScreen() { screen.Initialize(nil) }
func (screen *MainScreen) refreshList(bundle Bundle) error {
whichList := bundle.getString(MainBundleListKey, MainBundleListTodo)
func (screen *MainScreen) refreshList(bundle termboxScreen.Bundle) error {
whichList := bundle.GetString(MainBundleListKey, MainBundleListTodo)
switch whichList {
case MainBundleListTodo:
return app.LoadTaskList()
@ -78,14 +90,14 @@ func (screen *MainScreen) refreshList(bundle Bundle) error {
return errors.New("Invalid refresh request.")
}
func (screen *MainScreen) reloadList(bundle Bundle) error {
func (screen *MainScreen) reloadList(bundle termboxScreen.Bundle) error {
// We add tasks to the display list using append because we want to persist task Ids
screen.displayList = todotxt.NewTaskList()
screen.currentList = bundle.getString(MainBundleListKey, MainBundleListTodo)
screen.currentList = bundle.GetString(MainBundleListKey, MainBundleListTodo)
switch screen.currentList {
case MainBundleListTodo:
screen.setActiveList(app.TaskList)
if screen.currentFilter = bundle.getString(MainBundleFilterKey, ""); screen.currentFilter != "" {
if screen.currentFilter = bundle.GetString(MainBundleFilterKey, ""); screen.currentFilter != "" {
filteredList := app.filterList(screen.activeList, screen.currentFilter)
for _, av := range *screen.activeList {
for _, fv := range *filteredList {
@ -105,7 +117,7 @@ func (screen *MainScreen) reloadList(bundle Bundle) error {
return err
}
screen.setActiveList(app.DoneList)
if screen.currentFilter = bundle.getString(MainBundleFilterKey, ""); screen.currentFilter != "" {
if screen.currentFilter = bundle.GetString(MainBundleFilterKey, ""); screen.currentFilter != "" {
filteredList := app.filterList(screen.activeList, screen.currentFilter)
for _, av := range *screen.activeList {
for _, fv := range *filteredList {
@ -127,17 +139,17 @@ func (screen *MainScreen) reloadList(bundle Bundle) error {
return nil
}
func (screen *MainScreen) handleKeyEvent(event termbox.Event) int {
func (screen *MainScreen) HandleKeyEvent(event termbox.Event) int {
if screen.inputField.GetID() != "" {
return screen.handleInputKeyEvent(event)
}
if event.Ch == '?' {
// Go to About Screen
b := Bundle{}
if err := app.screens[ScreenAbout].initialize(b); err != nil {
b := termboxScreen.Bundle{}
if err := app.uiManager.InitializeScreen(AboutScreenId, b); err != nil {
screen.setErrorMessage(err.Error())
}
return ScreenAbout
return AboutScreenId
} else if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 {
if screen.backspaceDoes == MainBackspaceNothing {
@ -151,7 +163,7 @@ func (screen *MainScreen) handleKeyEvent(event termbox.Event) int {
} else if screen.backspaceDoes == MainBackspaceFilter {
screen.reloadList(screen.buildBundle(screen.currentList, ""))
}
return ScreenMain
return MainScreenId
} else if event.Key == termbox.KeySpace {
return screen.toggleTaskComplete()
@ -211,9 +223,9 @@ func (screen *MainScreen) handleKeyEvent(event termbox.Event) int {
screen.confirmArchiveItem()
} else if event.Ch == 'q' {
return ScreenExit
return ExitScreenId
}
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) handleInputKeyEvent(event termbox.Event) int {
@ -226,7 +238,7 @@ func (screen *MainScreen) handleInputKeyEvent(event termbox.Event) int {
screen.inputField.SetValue("")
screen.backspaceDoes = MainBackspaceFilter
screen.reloadList(screen.buildBundle(screen.currentList, filter))
return ScreenMain
return MainScreenId
}
case InputIDAddTask:
if event.Key == termbox.KeyEnter {
@ -238,7 +250,7 @@ func (screen *MainScreen) handleInputKeyEvent(event termbox.Event) int {
screen.inputField.SetID("")
screen.inputField.SetValue("")
screen.reloadList(screen.buildBundle(screen.currentList, screen.currentFilter))
return ScreenMain
return MainScreenId
}
case InputIDIncompleteArchive:
if event.Ch == 'y' || event.Ch == 'Y' {
@ -247,7 +259,7 @@ func (screen *MainScreen) handleInputKeyEvent(event termbox.Event) int {
screen.inputField.SetID("")
screen.inputField.SetValue("")
screen.reloadList(screen.buildBundle(screen.currentList, screen.currentFilter))
return ScreenMain
return MainScreenId
case InputIDUnArchiveTask:
if event.Ch == 'y' || event.Ch == 'Y' {
screen.inputField.SetID("")
@ -258,30 +270,30 @@ func (screen *MainScreen) handleInputKeyEvent(event termbox.Event) int {
screen.inputField.SetID("")
screen.inputField.SetValue("")
screen.reloadList(screen.buildBundle(screen.currentList, screen.currentFilter))
return ScreenMain
return MainScreenId
}
if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 {
if screen.inputField.GetValue() == "" {
screen.reloadList(screen.buildBundle(screen.currentList, screen.inputField.GetValue()))
screen.inputField.SetID("")
screen.inputField.SetValue("")
return ScreenMain
return MainScreenId
}
} else if event.Key == termbox.KeyEsc {
screen.reloadList(screen.buildBundle(screen.currentList, screen.currentFilter))
screen.inputField.SetID("")
screen.inputField.SetValue("")
return ScreenMain
return MainScreenId
}
screen.inputField.HandleEvent(event)
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) setActiveList(list *todotxt.TaskList) {
screen.activeList = list
}
func (screen *MainScreen) drawScreen() {
func (screen *MainScreen) DrawScreen() {
_, height := termbox.Size()
screen.viewPort.numberOfRows = height - 1
if screen.inputField.GetID() != "" {
@ -352,7 +364,7 @@ func (screen *MainScreen) drawFooter() {
func (screen *MainScreen) confirmArchiveItem() int {
if screen.currentList != MainBundleListTodo {
screen.inputField.SetID(InputIDUnArchiveTask)
return ScreenMain
return MainScreenId
}
// Find the task under the cursor
if screen.cursor[screen.currentList] < len(*screen.displayList) {
@ -364,20 +376,20 @@ func (screen *MainScreen) confirmArchiveItem() int {
return screen.archiveCurrentItem()
}
}
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) archiveCurrentItem() int {
if screen.currentList != MainBundleListTodo {
screen.setErrorMessage("Task is already archived")
return ScreenMain
return MainScreenId
}
// Find the task under the cursor
if len(*screen.displayList) > screen.cursor[screen.currentList] {
t := (*screen.displayList)[screen.cursor[screen.currentList]]
if err := app.archiveTask(t.Id); err != nil {
screen.setErrorMessage(err.Error())
return ScreenMain
return MainScreenId
}
// Reload the list
b := screen.buildBundle(screen.currentList, screen.currentFilter)
@ -385,20 +397,20 @@ func (screen *MainScreen) archiveCurrentItem() int {
screen.setErrorMessage(err.Error())
}
}
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) unarchiveCurrentItem() int {
if screen.currentList == MainBundleListTodo {
screen.setErrorMessage("Task is not archived")
return ScreenMain
return MainScreenId
}
// Find the task under the cursor
if len(*screen.displayList) > screen.cursor[screen.currentList] {
t := (*screen.displayList)[screen.cursor[screen.currentList]]
if err := app.unarchiveTask(t.Id); err != nil {
screen.setErrorMessage(err.Error())
return ScreenMain
return MainScreenId
}
// Reload the list
b := screen.buildBundle(screen.currentList, screen.currentFilter)
@ -406,7 +418,7 @@ func (screen *MainScreen) unarchiveCurrentItem() int {
screen.setErrorMessage(err.Error())
}
}
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) startEditTaskScreen() int {
@ -414,46 +426,46 @@ func (screen *MainScreen) startEditTaskScreen() int {
if len(*screen.displayList) > screen.cursor[screen.currentList] {
t := (*screen.displayList)[screen.cursor[screen.currentList]]
// Load the task screen with this task
b := Bundle{}
b.setValue(TaskBundleTaskIdKey, t.Id)
if err := app.screens[ScreenTask].initialize(b); err != nil {
b := termboxScreen.Bundle{}
b.SetValue(TaskBundleTaskIdKey, t.Id)
if err := app.uiManager.InitializeScreen(TaskScreenId, b); err != nil {
screen.setErrorMessage(err.Error())
return ScreenMain
return MainScreenId
}
return ScreenTask
return TaskScreenId
}
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) reloadCurrentView() {
bundle := Bundle{}
bundle.setValue(MainBundleListKey, screen.currentList)
bundle.setValue(MainBundleFilterKey, screen.currentFilter)
bundle := termboxScreen.Bundle{}
bundle.SetValue(MainBundleListKey, screen.currentList)
bundle.SetValue(MainBundleFilterKey, screen.currentFilter)
screen.reloadList(bundle)
}
func (screen *MainScreen) toggleViewList() int {
bundle := Bundle{}
bundle := termboxScreen.Bundle{}
if screen.currentList == MainBundleListTodo {
bundle.setValue(MainBundleListKey, MainBundleListDone)
bundle.SetValue(MainBundleListKey, MainBundleListDone)
screen.backspaceDoes = MainBackspaceMain
} else {
bundle.setValue(MainBundleListKey, MainBundleListTodo)
bundle.SetValue(MainBundleListKey, MainBundleListTodo)
}
bundle.setValue(MainBundleFilterKey, screen.currentFilter)
bundle.SetValue(MainBundleFilterKey, screen.currentFilter)
screen.reloadList(bundle)
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) startAddNewTask() int {
screen.inputField.SetID(InputIDAddTask)
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) toggleTaskComplete() int {
if screen.currentList == MainBundleListDone {
screen.setErrorMessage("Task is archived, unable to modify.")
return ScreenMain
return MainScreenId
}
// Find the task under the cursor
@ -462,11 +474,11 @@ func (screen *MainScreen) toggleTaskComplete() int {
err := app.toggleTaskComplete(t.Id)
if err != nil {
screen.setErrorMessage(err.Error())
return ScreenMain
return MainScreenId
}
}
screen.reloadCurrentView()
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) moveCursorDown() bool {
@ -489,7 +501,7 @@ func (screen *MainScreen) moveCursorUp() bool {
func (screen *MainScreen) startFilter() int {
screen.inputField.SetID(InputIDFilter)
return ScreenMain
return MainScreenId
}
func (screen *MainScreen) setErrorMessage(msg string) {
@ -524,9 +536,9 @@ func (screen *MainScreen) clearMessage() {
screen.messageColorFg = DefaultFg
}
func (screen *MainScreen) buildBundle(list, filter string) Bundle {
bundle := Bundle{}
bundle.setValue(MainBundleListKey, list)
bundle.setValue(MainBundleFilterKey, filter)
func (screen *MainScreen) buildBundle(list, filter string) termboxScreen.Bundle {
bundle := termboxScreen.Bundle{}
bundle.SetValue(MainBundleListKey, list)
bundle.SetValue(MainBundleFilterKey, filter)
return bundle
}

View File

@ -7,10 +7,13 @@ import (
"time"
todotxt "github.com/br0xen/go-todotxt"
"github.com/br0xen/termbox-screen"
"github.com/br0xen/termbox-util"
termbox "github.com/nsf/termbox-go"
)
const TaskScreenId = 1
// TaskScreen holds all that's going on
type TaskScreen struct {
message string
@ -29,10 +32,12 @@ const (
TaskBundleTaskIdKey = "taskscreen.taskid"
)
func (screen *TaskScreen) initialize(bundle Bundle) error {
func (screen *TaskScreen) Id() int { return TaskScreenId }
func (screen *TaskScreen) Initialize(bundle termboxScreen.Bundle) error {
var err error
if bundle != nil {
screen.currentTaskId = bundle.getInt(TaskBundleTaskIdKey, -1)
screen.currentTaskId = bundle.GetInt(TaskBundleTaskIdKey, -1)
}
if screen.currentTaskId == -1 {
return errors.New("Task Screen Initialization Failed")
@ -43,9 +48,11 @@ func (screen *TaskScreen) initialize(bundle Bundle) error {
return nil
}
func (screen *TaskScreen) handleKeyEvent(event termbox.Event) int {
func (screen *TaskScreen) ResizeScreen() { screen.Initialize(nil) }
func (screen *TaskScreen) HandleKeyEvent(event termbox.Event) int {
if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 || event.Ch == 'h' || event.Key == termbox.KeyArrowLeft {
return ScreenMain
return MainScreenId
} else if event.Ch == 'j' || event.Key == termbox.KeyArrowDown {
screen.moveCursorDown()
@ -53,10 +60,10 @@ func (screen *TaskScreen) handleKeyEvent(event termbox.Event) int {
} else if event.Ch == 'k' || event.Key == termbox.KeyArrowUp {
screen.moveCursorUp()
}
return ScreenTask
return TaskScreenId
}
func (screen *TaskScreen) drawScreen() {
func (screen *TaskScreen) DrawScreen() {
screen.drawHeader()
yPos := 1

View File

@ -1,58 +0,0 @@
package main
import (
"fmt"
"os"
"runtime"
"syscall"
termbox "github.com/nsf/termbox-go"
)
func uiLoop() int {
err := termbox.Init()
if err != nil {
fmt.Println(err.Error())
return 1
}
termbox.SetOutputMode(termbox.Output256)
app.BuildScreens()
displayScreen := app.screens[ScreenMain]
bundle := Bundle{}
bundle.setValue(MainBundleListKey, MainBundleListTodo)
displayScreen.initialize(bundle)
app.layoutAndDrawScreen(displayScreen)
eventChan := make(chan termbox.Event)
go readUserInput(eventChan)
go checkForUpdate(eventChan)
for {
event := <-eventChan
if event.Type == termbox.EventKey {
if event.Key == termbox.KeyCtrlC {
break
} else if event.Key == termbox.KeyCtrlZ {
if runtime.GOOS != "windows" {
process, _ := os.FindProcess(os.Getpid())
termbox.Close()
process.Signal(syscall.SIGSTOP)
termbox.Init()
}
}
newScreenIndex := displayScreen.handleKeyEvent(event)
if newScreenIndex < len(app.screens) {
displayScreen = app.screens[newScreenIndex]
app.layoutAndDrawScreen(displayScreen)
} else {
break
}
}
if event.Type == termbox.EventResize {
displayScreen.initialize(nil)
app.layoutAndDrawScreen(displayScreen)
}
}
termbox.Close()
// Any wrap up should be done here...
return 0
}