package tui import ( "embed" "log" "strconv" "time" "unicode" "github.com/gdamore/tcell/v2" "github.com/mrusme/superhighway84/cache" "github.com/mrusme/superhighway84/config" "github.com/mrusme/superhighway84/models" "github.com/rivo/tview" "go.uber.org/zap" ) type TUI struct { App *tview.Application Views map[string]View ActiveView string Modal *tview.Modal ModalVisible bool ModalButtons map[string]ModalButton ArticlesDatasource *[]*models.Article ArticlesRoots *[]*models.Article CallbackRefreshArticles func() (error) CallbackSubmitArticle func(article *models.Article) (error) Config *config.Config Cache *cache.Cache Logger *zap.Logger Stats map[string]int64 Version string VersionLatest string Meta map[string]interface{} } type View interface { GetCanvas() (tview.Primitive) GetDefaultFocus() (tview.Primitive) Refresh() HandleInput(event *tcell.EventKey) (*tcell.EventKey) } type ModalButton struct { Rune rune Callback func() } func Init(embedfs *embed.FS, cfg *config.Config, cch *cache.Cache, logger *zap.Logger) (*TUI) { t := new(TUI) tview.Styles = tview.Theme{ PrimitiveBackgroundColor: tcell.ColorDefault, ContrastBackgroundColor: tcell.ColorPink, MoreContrastBackgroundColor: tcell.ColorTeal, BorderColor: tcell.ColorWhite, TitleColor: tcell.ColorWhite, GraphicsColor: tcell.ColorWhite, PrimaryTextColor: tcell.ColorDefault, SecondaryTextColor: tcell.ColorBlue, TertiaryTextColor: tcell.ColorGreen, InverseTextColor: tcell.ColorBlack, ContrastSecondaryTextColor: tcell.ColorDarkCyan, } t.App = tview.NewApplication() t.Config = cfg t.Cache = cch t.Logger = logger t.Stats = make(map[string]int64) t.Meta = make(map[string]interface{}) logoBytes, err := embedfs.ReadFile("superhighway84.jpeg") if err != nil { log.Panicln(err) } t.Views = make(map[string]View) t.Views["splashscreen"] = t.NewSplashscreen(&logoBytes) t.Views["mainscreen"] = t.NewMainscreen() t.ModalVisible = false t.initInput() return t } func (t *TUI) getInputEvent(event *tcell.EventKey) (string) { var action string var exist bool if event.Key() == tcell.KeyRune { action, exist = t.Config.Shortcuts[strconv.FormatInt(int64(event.Rune()), 10)] } else { action, exist = t.Config.Shortcuts[strconv.FormatInt(int64(event.Key()), 10)] } if exist == false { return "" } return action } func (t *TUI) initInput() { t.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { action := t.getInputEvent(event) switch action { case "refresh": t.RefreshMainscreen() t.SetInfo(true) t.App.Sync() return nil case "quit": t.App.Stop() return nil default: if t.ModalVisible == true { for _, modalButton := range t.ModalButtons { if modalButton.Rune == '*' || (event.Key() == tcell.KeyRune && unicode.ToLower(modalButton.Rune) == unicode.ToLower(event.Rune())) { modalButton.Callback() t.HideModal() return nil } } return nil } else { return t.Views[t.ActiveView].HandleInput(event) } } }) } func (t *TUI) Launch() { go func() { time.Sleep(time.Millisecond * 200) t.SetView("splashscreen", true) t.Refresh() t.App.Draw() }() go func() { for { t.RefreshMainscreen() t.SetInfo(true) time.Sleep(time.Second * 60) } }() if err := t.App.Run(); err != nil { panic(err) } } func(t *TUI) SetView(name string, redraw bool) { t.ActiveView = name t.App.SetRoot(t.Views[t.ActiveView].GetCanvas(), true). SetFocus(t.Views[t.ActiveView].GetDefaultFocus()) if redraw { t.App.Draw() } } func (t *TUI) RefreshMainscreen() { if t.ActiveView != "mainscreen" { return } t.RefreshData() t.Refresh() return } func (t *TUI) Refresh() { t.Views[t.ActiveView].Refresh() return } func (t *TUI) RefreshData() { if t.CallbackRefreshArticles != nil { err := t.CallbackRefreshArticles() if err != nil { t.ShowErrorModal(err.Error()) } } } func(t *TUI) ShowModal(text string, buttons map[string]ModalButton) { t.Modal = tview.NewModal(). SetTextColor(tcell.ColorBlack). SetBackgroundColor(tcell.ColorTeal). SetButtonBackgroundColor(tcell.ColorHotPink). SetButtonTextColor(tcell.ColorWhite). SetText(text) // SetDoneFunc(func(buttonIndex int, buttonLabel string) { // modalButton := buttons[buttonLabel] // modalButton.Callback() // t.HideModal() // }) var buttonLabels []string for buttonLabel := range buttons { buttonLabels = append(buttonLabels, buttonLabel) } t.Modal.AddButtons(buttonLabels) t.ModalVisible = true t.ModalButtons = buttons t.App.SetRoot(t.Modal, false).SetFocus(t.Modal) } func(t *TUI) HideModal() { t.ModalVisible = false t.SetView(t.ActiveView, false) } func(t *TUI) ShowErrorModal(text string) { t.ShowModal( text, map[string]ModalButton{ "(F)uck": { Rune: '*', Callback: func() { return }, }, }) } func(t *TUI) ShowHelpModal(text string) { t.ShowModal( text, map[string]ModalButton{ "Press any key to close": { Rune: '*', Callback: func() { return }, }, }) } func (t *TUI) SetInfo(refresh bool) { if refresh == true { t.Views["mainscreen"].(*Mainscreen).SetInfo(map[string]string{ "refresh": "[green]◙[-]", }) time.AfterFunc(time.Second * 3, func() { t.SetInfo(false) }) } else { t.Views["mainscreen"].(*Mainscreen).SetInfo(map[string]string{ "refresh": "[grey]◙[-]", }) } } func (t* TUI) SetStats(peers, rateIn, rateOut, totalIn, totalOut int64) () { t.Stats["peers"] = peers t.Stats["rate_in"] = rateIn t.Stats["rate_out"] = rateOut t.Stats["total_in"] = totalIn t.Stats["total_out"] = totalOut t.Views["mainscreen"].(*Mainscreen).SetStats(t.Stats) t.App.Draw() } func (t* TUI) SetVersion(version string, versionLatest string) { t.Version = version t.VersionLatest = versionLatest t.Views["mainscreen"].(*Mainscreen).SetVersion(t.Version, t.VersionLatest) }