Working on UI
This commit is contained in:
parent
a8c1812bbf
commit
c98a4dea0c
5
Makefile
5
Makefile
@ -1,4 +1,4 @@
|
|||||||
# This how we want to name the binary output
|
# This is what we want to name the binary output
|
||||||
BINARY=gime
|
BINARY=gime
|
||||||
|
|
||||||
# These are the values we want to pass for VERSION and BUILD
|
# These are the values we want to pass for VERSION and BUILD
|
||||||
@ -14,5 +14,8 @@ LDFLAGS=-ldflags "-w -s -X cmd.Version=${VERSION} -X cmd.Build=${BUILD}"
|
|||||||
gime:
|
gime:
|
||||||
go build ${LDFLAGS} -o build/${BINARY}
|
go build ${LDFLAGS} -o build/${BINARY}
|
||||||
|
|
||||||
|
runui:
|
||||||
|
cd build && ./gime ui
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm build/*
|
rm build/*
|
||||||
|
@ -4,8 +4,8 @@ Copyright © 2022 Brian Buller <brian@bullercodeworks.com>
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"git.bullercodeworks.com/brian/gime/cli"
|
||||||
|
"git.bullercodeworks.com/brian/gime/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,6 +21,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func opUi(cmd *cobra.Command, args []string) error {
|
func opUi(cmd *cobra.Command, args []string) error {
|
||||||
fmt.Println("ui called")
|
p := &cli.Program{}
|
||||||
return nil
|
return ui.RunTUI(p)
|
||||||
}
|
}
|
||||||
|
8
go.mod
8
go.mod
@ -1,11 +1,17 @@
|
|||||||
module git.bullercodeworks.com/brian/gime
|
module git.bullercodeworks.com/brian/gime
|
||||||
|
|
||||||
go 1.17
|
go 1.18
|
||||||
|
|
||||||
replace git.bullercodeworks.com/brian/go-timertxt => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/go-timertxt
|
replace git.bullercodeworks.com/brian/go-timertxt => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/go-timertxt
|
||||||
|
|
||||||
|
replace git.bullercodeworks.com/brian/wandle => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/wandle
|
||||||
|
|
||||||
|
replace git.bullercodeworks.com/brian/widdles => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/widdles
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.bullercodeworks.com/brian/go-timertxt v0.0.0-20210302170637-d35b67037e23
|
git.bullercodeworks.com/brian/go-timertxt v0.0.0-20210302170637-d35b67037e23
|
||||||
|
git.bullercodeworks.com/brian/wandle v1.0.3
|
||||||
|
git.bullercodeworks.com/brian/widdles v0.0.0-00010101000000-000000000000
|
||||||
github.com/br0xen/termbox-util v0.0.0-20200220160819-dc6d6950ba00
|
github.com/br0xen/termbox-util v0.0.0-20200220160819-dc6d6950ba00
|
||||||
github.com/br0xen/user-config v0.0.0-20170914134719-16e743ec93a2
|
github.com/br0xen/user-config v0.0.0-20170914134719-16e743ec93a2
|
||||||
github.com/muesli/go-app-paths v0.2.2
|
github.com/muesli/go-app-paths v0.2.2
|
||||||
|
168
ui/list_timers.go
Normal file
168
ui/list_timers.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/gime/util"
|
||||||
|
"git.bullercodeworks.com/brian/go-timertxt"
|
||||||
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
|
"git.bullercodeworks.com/brian/widdles"
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listTimersScreen struct {
|
||||||
|
ui *Ui
|
||||||
|
|
||||||
|
initialized bool
|
||||||
|
menu *widdles.TopMenu
|
||||||
|
scrollbar *widdles.Scrollbar
|
||||||
|
|
||||||
|
cursor int
|
||||||
|
|
||||||
|
timerList *timertxt.TimerList
|
||||||
|
doneList *timertxt.TimerList
|
||||||
|
|
||||||
|
timerFilterList *timertxt.TimerList
|
||||||
|
doneFilterList *timertxt.TimerList
|
||||||
|
|
||||||
|
filter string
|
||||||
|
|
||||||
|
msg string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListTimersMsg ScreenMsg
|
||||||
|
|
||||||
|
func NewListTimersScreen(u *Ui) *listTimersScreen {
|
||||||
|
w, h := termbox.Size()
|
||||||
|
s := listTimersScreen{
|
||||||
|
ui: u,
|
||||||
|
menu: widdles.NewTopMenu(0, 0, 0),
|
||||||
|
scrollbar: widdles.NewScrollbar(w-2, 2, 1, h-2),
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *listTimersScreen) Init() wandle.Cmd {
|
||||||
|
if s.initialized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.initialized = true
|
||||||
|
// Set up the top menu
|
||||||
|
fileMenu := s.menu.NewSubMenu("File")
|
||||||
|
settingsOption := widdles.NewMenuItem("Settings")
|
||||||
|
settingsOption.SetCommand(s.ui.GotoScreen(SettingsId))
|
||||||
|
fileMenu.AddOption(settingsOption)
|
||||||
|
quitOption := widdles.NewMenuItem("Quit")
|
||||||
|
quitOption.Hotkey = termbox.KeyCtrlC
|
||||||
|
quitOption.SetCommand(func() wandle.Msg { return wandle.Quit() })
|
||||||
|
fileMenu.AddOption(quitOption)
|
||||||
|
s.menu.Measure()
|
||||||
|
// Timer Lists
|
||||||
|
s.timerList, s.doneList = s.ui.program.TimerList, s.ui.program.DoneList
|
||||||
|
s.timerFilterList, s.doneFilterList = s.timerList, s.doneList
|
||||||
|
s.timerFilterList.Sort(timertxt.SORT_START_DATE_DESC)
|
||||||
|
s.doneFilterList.Sort(timertxt.SORT_START_DATE_DESC)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *listTimersScreen) Update(msg wandle.Msg) wandle.Cmd {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case ScreenMsg:
|
||||||
|
case termbox.Event:
|
||||||
|
return s.handleTermboxEvent(msg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *listTimersScreen) View(style wandle.Style) {
|
||||||
|
_, h := termbox.Size()
|
||||||
|
if s.menu.IsActive() {
|
||||||
|
s.menu.View(style)
|
||||||
|
}
|
||||||
|
y := 2
|
||||||
|
wandle.Print(1, y, style.Bold(true), "Active Timers")
|
||||||
|
y++
|
||||||
|
for idx, tmr := range s.timerFilterList.GetTimerSlice() {
|
||||||
|
if y > h-2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
st := style
|
||||||
|
if s.cursor == idx {
|
||||||
|
st = st.Invert()
|
||||||
|
}
|
||||||
|
wandle.Print(1, y, st, "[ ]")
|
||||||
|
s.ViewTimer(5, y, st, tmr)
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
y++
|
||||||
|
wandle.Print(1, y, style.Bold(true), "Done Timers")
|
||||||
|
y++
|
||||||
|
for idx, tmr := range s.doneFilterList.GetTimerSlice() {
|
||||||
|
if y > h-2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
st := style
|
||||||
|
if s.cursor == s.timerFilterList.Size()+idx {
|
||||||
|
st = st.Invert()
|
||||||
|
}
|
||||||
|
wandle.Print(1, y, st, "[ ]")
|
||||||
|
s.ViewTimer(5, y, st, tmr)
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
|
||||||
|
s.scrollbar.View(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *listTimersScreen) ViewTimer(x, y int, style wandle.Style, tmr *timertxt.Timer) {
|
||||||
|
var tags []string
|
||||||
|
for _, k := range util.SortedTagKeyList(tmr.AdditionalTags) {
|
||||||
|
tags = append(tags, fmt.Sprintf("%s:%s", k, tmr.AdditionalTags[k]))
|
||||||
|
}
|
||||||
|
|
||||||
|
wandle.Print(x, y, style, fmt.Sprintf("%s %s %s %s %s", tmr.StartDate.Format(time.Stamp), tmr.Duration(), tmr.Contexts, tmr.Projects, strings.Join(tags, "; ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *listTimersScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
|
if (msg.Type == termbox.EventKey && msg.Key == termbox.KeyEsc) || s.menu.IsActive() {
|
||||||
|
return s.menu.Update(msg)
|
||||||
|
}
|
||||||
|
switch msg.Type {
|
||||||
|
case termbox.EventKey:
|
||||||
|
top := s.timerFilterList.Size() + s.doneFilterList.Size() - 2
|
||||||
|
if msg.Key == termbox.KeyEnter {
|
||||||
|
/*
|
||||||
|
if s.cursor >= 0 && s.cursor < s.timerFilterList.Size()+s.doneFilterList.Size() {
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} else if msg.Key == termbox.KeyArrowUp || msg.Ch == 'k' {
|
||||||
|
if s.cursor > 0 {
|
||||||
|
s.cursor--
|
||||||
|
} else {
|
||||||
|
s.cursor = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else if msg.Key == termbox.KeyArrowDown || msg.Ch == 'j' {
|
||||||
|
if s.cursor < top {
|
||||||
|
s.cursor++
|
||||||
|
} else {
|
||||||
|
s.cursor = top
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else if msg.Ch == 'G' {
|
||||||
|
s.cursor = top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *listTimersScreen) gotoSettingsScreen() wandle.Msg {
|
||||||
|
return ScreenMsg{
|
||||||
|
source: ListTimersId,
|
||||||
|
command: CmdGotoSettings,
|
||||||
|
}
|
||||||
|
}
|
71
ui/screen_settings.go
Normal file
71
ui/screen_settings.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
|
"git.bullercodeworks.com/brian/widdles"
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type settingsScreen struct {
|
||||||
|
ui *Ui
|
||||||
|
initialized bool
|
||||||
|
menu *widdles.TopMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsMsg ScreenMsg
|
||||||
|
|
||||||
|
func NewSettingsScreen(u *Ui) *settingsScreen {
|
||||||
|
return &settingsScreen{
|
||||||
|
ui: u,
|
||||||
|
menu: widdles.NewTopMenu(0, 0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsScreen) Init() wandle.Cmd {
|
||||||
|
if s.initialized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.initialized = true
|
||||||
|
// Set up the top menu
|
||||||
|
fileMenu := s.menu.NewSubMenu("File")
|
||||||
|
quitOption := widdles.NewMenuItem("Quit")
|
||||||
|
quitOption.Hotkey = termbox.KeyCtrlC
|
||||||
|
quitOption.SetCommand(func() wandle.Msg { return wandle.Quit() })
|
||||||
|
fileMenu.AddOption(quitOption)
|
||||||
|
gotoMenu := s.menu.NewSubMenu("Goto")
|
||||||
|
timerListOption := widdles.NewMenuItem("Timer List")
|
||||||
|
timerListOption.SetCommand(s.ui.GotoScreen(ListTimersId))
|
||||||
|
gotoMenu.AddOption(timerListOption)
|
||||||
|
s.menu.Measure()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsScreen) Update(msg wandle.Msg) wandle.Cmd {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case ScreenMsg:
|
||||||
|
case termbox.Event:
|
||||||
|
return s.handleTermboxEvent(msg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsScreen) View(style wandle.Style) {
|
||||||
|
wandle.Print(1, 1, style, "Settings")
|
||||||
|
if s.menu.IsActive() {
|
||||||
|
s.menu.View(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
|
if (msg.Type == termbox.EventKey && msg.Key == termbox.KeyEsc) || s.menu.IsActive() {
|
||||||
|
return s.menu.Update(msg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsScreen) gotoTimerList() wandle.Msg {
|
||||||
|
return ScreenMsg{
|
||||||
|
source: SettingsId,
|
||||||
|
command: CmdGotoTimerList,
|
||||||
|
}
|
||||||
|
}
|
128
ui/ui.go
Normal file
128
ui/ui.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/gime/cli"
|
||||||
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Screen/Source Ids
|
||||||
|
const (
|
||||||
|
ListTimersId = ScreenMsgSource(iota << 8)
|
||||||
|
SettingsId
|
||||||
|
ErrorId
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
const (
|
||||||
|
CmdCanceled = ScreenMsgCommand(iota)
|
||||||
|
CmdSaved
|
||||||
|
|
||||||
|
// Goto Screen Commands
|
||||||
|
CmdGotoSettings
|
||||||
|
CmdGotoTimerList
|
||||||
|
)
|
||||||
|
|
||||||
|
func RunTUI(p *cli.Program) error {
|
||||||
|
ui := NewUi(p)
|
||||||
|
ui.debug = true
|
||||||
|
if err := ui.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Exiting
|
||||||
|
fmt.Printf("Done\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ui struct {
|
||||||
|
debug bool
|
||||||
|
wandle *wandle.Program
|
||||||
|
program *cli.Program
|
||||||
|
err error
|
||||||
|
|
||||||
|
screens map[ScreenMsgSource]wandle.Screen
|
||||||
|
|
||||||
|
prevScreen, currScreen ScreenMsgSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUi(p *cli.Program) *Ui {
|
||||||
|
ui := &Ui{
|
||||||
|
screens: make(map[ScreenMsgSource]wandle.Screen),
|
||||||
|
program: p,
|
||||||
|
err: p.Initialize(),
|
||||||
|
}
|
||||||
|
var s wandle.Screen
|
||||||
|
var sId ScreenMsgSource
|
||||||
|
if ui.err != nil {
|
||||||
|
s, sId = NewSettingsScreen(ui), SettingsId
|
||||||
|
} else {
|
||||||
|
if ui.err = ui.program.LoadTimerList(); ui.err != nil {
|
||||||
|
s, sId = NewSettingsScreen(ui), SettingsId
|
||||||
|
} else if ui.err = ui.program.LoadDoneList(); ui.err != nil {
|
||||||
|
s, sId = NewSettingsScreen(ui), SettingsId
|
||||||
|
} else {
|
||||||
|
s, sId = NewListTimersScreen(ui), ListTimersId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.screens[sId] = s
|
||||||
|
ui.currScreen = sId
|
||||||
|
ui.wandle = wandle.NewProgram(s)
|
||||||
|
ui.wandle.Style(wandle.NewStyle(
|
||||||
|
termbox.RGBToAttribute(uint8(0), uint8(255), uint8(0)),
|
||||||
|
termbox.RGBToAttribute(uint8(0), uint8(0), uint8(0)),
|
||||||
|
))
|
||||||
|
return ui
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Ui) GotoScreen(id ScreenMsgSource) func() wandle.Msg {
|
||||||
|
u.prevScreen, u.currScreen = u.currScreen, id
|
||||||
|
if s, ok := u.screens[id]; ok {
|
||||||
|
return wandle.SwitchScreenCmd(s)
|
||||||
|
}
|
||||||
|
var s wandle.Screen
|
||||||
|
switch id {
|
||||||
|
case ListTimersId:
|
||||||
|
s = NewListTimersScreen(u)
|
||||||
|
case SettingsId:
|
||||||
|
s = NewSettingsScreen(u)
|
||||||
|
}
|
||||||
|
u.screens[id] = s
|
||||||
|
return wandle.SwitchScreenCmd(s)
|
||||||
|
}
|
||||||
|
func (u *Ui) CanGoBack() bool {
|
||||||
|
return u.prevScreen > 0
|
||||||
|
}
|
||||||
|
func (u *Ui) GoBack() func() wandle.Msg {
|
||||||
|
if !u.CanGoBack() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u.GotoScreen(u.prevScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Ui) Log(v string) error {
|
||||||
|
f, err := os.OpenFile("gime.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = f.WriteString(fmt.Sprintf("%s: %s\n", time.Now().Format(time.RFC3339), v))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Ui) Start() error {
|
||||||
|
return u.wandle.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenMsg struct {
|
||||||
|
source ScreenMsgSource
|
||||||
|
command ScreenMsgCommand
|
||||||
|
data interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenMsgSource int
|
||||||
|
type ScreenMsgCommand int
|
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -416,3 +417,12 @@ func BuildFilterFromArgs(args []string) func(*timertxt.Timer) bool {
|
|||||||
}
|
}
|
||||||
return doFilters
|
return doFilters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SortedTagKeyList(m map[string]string) []string {
|
||||||
|
var ret []string
|
||||||
|
for k := range m {
|
||||||
|
ret = append(ret, k)
|
||||||
|
}
|
||||||
|
sort.Strings(ret)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user