package main import ( "context" "fmt" "log" "os" "sync" "git.yetaga.in/alazyreader/library/book" "git.yetaga.in/alazyreader/library/config" "git.yetaga.in/alazyreader/library/database" "git.yetaga.in/alazyreader/library/ui" "github.com/gdamore/tcell" "github.com/kelseyhightower/envconfig" ) // State holds the UI state keys=>value map and manages access to the map with a mutex type State struct { m sync.Mutex stateMap map[string]interface{} } // key, present func (s *State) Get(key string) interface{} { s.m.Lock() defer s.m.Unlock() if s.stateMap == nil { s.stateMap = make(map[string]interface{}) } k, ok := s.stateMap[key] if !ok { return nil } return k } // key, value func (s *State) Set(key string, value interface{}) { s.m.Lock() defer s.m.Unlock() if s.stateMap == nil { s.stateMap = make(map[string]interface{}) } s.stateMap[key] = value } const ( IN_MENU = iota IN_BOOK ) type EventBookUpdate struct { tcell.EventTime book *book.Book } func NewEventBookUpdate(b *book.Book) *EventBookUpdate { e := &EventBookUpdate{book: b} e.SetEventNow() return e } func (e *EventBookUpdate) Book() *book.Book { return e.book } type EventLoadBook struct { tcell.EventTime ID int } func NewEventLoadBook(id int) *EventLoadBook { e := &EventLoadBook{ID: id} e.SetEventNow() return e } func main() { var c config.Config err := envconfig.Process("library", &c) if err != nil { log.Fatalln(err) } // create state state := State{} // set up DB connection if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" { log.Fatalf("vars: %+v", c) } lib, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName) if err != nil { log.Fatalln(err) } err = lib.PrepareDatabase(context.Background()) if err != nil { log.Fatalln(err) } _, _, err = lib.RunMigrations(context.Background()) if err != nil { log.Fatalln(err) } books, err := lib.GetAllBooks(context.Background()) if err != nil { log.Fatalln(err) } state.Set("library", books) screen, err := tcell.NewScreen() if err != nil { log.Fatalln(err) } err = screen.Init() if err != nil { log.Fatalln(err) } l := ui.NewList(Titles(state.Get("library").([]book.Book)), 0) menu := ui.NewBox( "library", []string{"(n)ew", "(i)mport", "(q)uit"}, ui.Contents{{ Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: -2, Right: -2}, Container: l, }}, ui.StyleActive, false, ) book := ui.NewBookDetails(&book.Book{ Title: "test title", }) activeBook := ui.NewBox( "book", nil, ui.Contents{{ Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: 0, Right: 0}, Container: book, }}, ui.StyleInactive, false, ) container := ui.NewContainer( ui.Contents{ {Container: menu, Offsets: ui.Offsets{Percent: 1}}, {Container: activeBook, Offsets: ui.Offsets{Percent: 2}}, }, ui.LayoutHorizontalPercent, ) // init screen.Clear() w, h := screen.Size() container.SetSize(0, 0, h, w) container.Draw(screen) screen.Sync() // init UI state state.Set("ui_state", IN_MENU) screen.PostEvent(NewEventLoadBook(l.SelectedID())) // UI loop for { e := screen.PollEvent() switch v := e.(type) { case *tcell.EventError: fmt.Fprintf(os.Stderr, "%v", v) screen.Beep() case *tcell.EventKey: // input handling curr := state.Get("ui_state").(int) if curr == IN_MENU { if v.Key() == tcell.KeyUp && l.Selected() > 0 { l.SetSelected(l.Selected() - 1) screen.PostEvent(NewEventLoadBook(l.SelectedID())) } if v.Key() == tcell.KeyDown && l.Selected() < len(l.ListMembers())-1 { l.SetSelected(l.Selected() + 1) screen.PostEvent(NewEventLoadBook(l.SelectedID())) } if v.Rune() == 'q' { screen.Fini() fmt.Printf("Thank you for playing Wing Commander!\n\n") return } if v.Key() == tcell.KeyEnter { activeBook.SetStyle(ui.StyleActive) menu.SetStyle(ui.StyleInactive) state.Set("ui_state", IN_BOOK) } } else if curr == IN_BOOK { if v.Key() == tcell.KeyEsc { activeBook.SetStyle(ui.StyleInactive) menu.SetStyle(ui.StyleActive) state.Set("ui_state", IN_MENU) } } screen.Clear() container.Draw(screen) case *tcell.EventResize: // screen redraw w, h := screen.Size() container.SetSize(0, 0, h, w) screen.Clear() container.Draw(screen) case *EventBookUpdate: // TK case *EventLoadBook: book.SetBook(GetBookByID(v.ID, books)) screen.Clear() container.Draw(screen) case *tcell.EventInterrupt: case *tcell.EventMouse: case *tcell.EventTime: default: } screen.Show() // repaint } } func Titles(lb []book.Book) []ui.ListKeyValue { r := []ui.ListKeyValue{} for i := range lb { r = append(r, ui.ListKeyValue{ Key: lb[i].ID, Value: lb[i].Title, }) } return r } func GetBookByID(id int, lb []book.Book) *book.Book { for i := range lb { if lb[i].ID == id { return &lb[i] } } return &book.Book{} }