diff --git a/app.go b/app.go index 58f49d2..b29e2a7 100644 --- a/app.go +++ b/app.go @@ -2,11 +2,9 @@ package main import ( "github.com/mattn/go-mastodon" - "github.com/rivo/tview" ) type App struct { - App *tview.Application UI *UI Me *mastodon.Account API *API diff --git a/authoverlay.go b/authoverlay.go index 88b1ae3..56f59ff 100644 --- a/authoverlay.go +++ b/authoverlay.go @@ -16,28 +16,38 @@ const ( authCodeStep ) -func NewAuthoverlay(app *App, flex *tview.Flex, view *tview.InputField, controls *Controls) *AuthOverlay { - return &AuthOverlay{ +func NewAuthOverlay(app *App) *AuthOverlay { + a := &AuthOverlay{ app: app, - Flex: flex, - View: view, - Controls: controls, + Flex: tview.NewFlex(), + Input: tview.NewInputField(), + Text: tview.NewTextView(), authStep: authNoneStep, } + + a.Flex.SetBackgroundColor(app.Config.Style.Background) + a.Input.SetBackgroundColor(app.Config.Style.Background) + a.Input.SetFieldBackgroundColor(app.Config.Style.Background) + a.Input.SetFieldTextColor(app.Config.Style.Text) + a.Text.SetBackgroundColor(app.Config.Style.Background) + a.Text.SetTextColor(app.Config.Style.Text) + a.Flex.SetDrawFunc(app.Config.ClearContent) + a.Draw() + return a } type AuthOverlay struct { app *App Flex *tview.Flex - View *tview.InputField - Controls *Controls + Input *tview.InputField + Text *tview.TextView authStep authStep account AccountRegister redirectURL string } func (a *AuthOverlay) GotInput() { - input := strings.TrimSpace(a.View.GetText()) + input := strings.TrimSpace(a.Input.GetText()) switch a.authStep { case authInstanceStep: if !(strings.HasPrefix(input, "https://") || strings.HasPrefix(input, "http://")) { @@ -55,7 +65,7 @@ func (a *AuthOverlay) GotInput() { } a.account = acc openURL(acc.AuthURI) - a.View.SetText("") + a.Input.SetText("") a.authStep = authCodeStep a.Draw() case authCodeStep: @@ -91,14 +101,14 @@ func (a *AuthOverlay) Draw() { switch a.authStep { case authNoneStep: a.authStep = authInstanceStep - a.View.SetText("") + a.Input.SetText("") a.Draw() return case authInstanceStep: - a.View.SetLabel("Instance: ") - a.Controls.View.SetText("Enter the url of your instance. Will default to https://\nPress Enter when done") + a.Input.SetLabel("Instance: ") + a.Text.SetText("Enter the url of your instance. Will default to https://\nPress Enter when done") case authCodeStep: - a.Controls.View.SetText(fmt.Sprintf("The login URL has opened in your browser. If it didn't work open this URL\n%s", a.account.AuthURI)) - a.View.SetLabel("Authorization code: ") + a.Text.SetText(fmt.Sprintf("The login URL has opened in your browser. If it didn't work open this URL\n%s", a.account.AuthURI)) + a.Input.SetLabel("Authorization code: ") } } diff --git a/cmdbar.go b/cmdbar.go index 9b0cabe..8bc709a 100644 --- a/cmdbar.go +++ b/cmdbar.go @@ -6,22 +6,27 @@ import ( "github.com/rivo/tview" ) -func NewCmdBar(app *App, view *tview.InputField) *CmdBar { - return &CmdBar{ - app: app, - View: view, +func NewCmdBar(app *App) *CmdBar { + c := &CmdBar{ + app: app, + Input: tview.NewInputField(), } + + c.Input.SetFieldBackgroundColor(app.Config.Style.Background) + c.Input.SetFieldTextColor(app.Config.Style.Text) + + return c } type CmdBar struct { - app *App - View *tview.InputField + app *App + Input *tview.InputField } func (c *CmdBar) GetInput() string { - return strings.TrimSpace(c.View.GetText()) + return strings.TrimSpace(c.Input.GetText()) } func (c *CmdBar) ClearInput() { - c.View.SetText("") + c.Input.SetText("") } diff --git a/linkoverlay.go b/linkoverlay.go index 11da908..d860b02 100644 --- a/linkoverlay.go +++ b/linkoverlay.go @@ -12,6 +12,16 @@ func NewLinkOverlay(app *App) *LinkOverlay { TextBottom: tview.NewTextView(), List: tview.NewList(), } + + l.TextBottom.SetBackgroundColor(app.Config.Style.Background) + l.List.SetBackgroundColor(app.Config.Style.Background) + l.List.SetMainTextColor(app.Config.Style.Text) + l.List.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground) + l.List.SetSelectedTextColor(app.Config.Style.ListSelectedText) + l.List.ShowSecondaryText(false) + l.List.SetHighlightFullLine(true) + l.Flex.SetDrawFunc(app.Config.ClearContent) + l.TextBottom.SetText("[O]pen") return l } diff --git a/main.go b/main.go index 513dfec..a0d6add 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/gdamore/tcell" - "github.com/rivo/tview" ) func main() { @@ -36,34 +35,12 @@ func main() { log.Fatalln(err) } app := &App{ - App: tview.NewApplication(), API: &API{}, HaveAccount: false, Config: &config, } + app.UI = NewUI(app) - clearContent := func(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) { - for cx := x; cx < width+x; cx++ { - for cy := y; cy < height+y; cy++ { - screen.SetContent(cx, cy, ' ', nil, tcell.StyleDefault.Background(app.Config.Style.Background)) - } - } - y2 := y + height - for cx := x + 1; cx < width+x; cx++ { - screen.SetContent(cx, y, tview.BoxDrawingsLightHorizontal, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - screen.SetContent(cx, y2, tview.BoxDrawingsLightHorizontal, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - } - x2 := x + width - for cy := y + 1; cy < height+y; cy++ { - screen.SetContent(x, cy, tview.BoxDrawingsLightVertical, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - screen.SetContent(x2, cy, tview.BoxDrawingsLightVertical, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - } - screen.SetContent(x, y, tview.BoxDrawingsLightDownAndRight, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - screen.SetContent(x, y+height, tview.BoxDrawingsLightUpAndRight, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - screen.SetContent(x+width, y, tview.BoxDrawingsLightDownAndLeft, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - screen.SetContent(x+width, y+height, tview.BoxDrawingsLightUpAndLeft, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - return x + 1, y + 1, width - 1, height - 1 - } if exists { accounts, err := GetAccounts(path) if err != nil { @@ -79,190 +56,13 @@ func main() { } } - app.App.SetBeforeDrawFunc(func(screen tcell.Screen) bool { - screen.Clear() - return false - }) - - tview.Borders.HorizontalFocus = tview.BoxDrawingsLightHorizontal - tview.Borders.VerticalFocus = tview.BoxDrawingsLightVertical - tview.Borders.TopLeftFocus = tview.BoxDrawingsLightDownAndRight - tview.Borders.TopRightFocus = tview.BoxDrawingsLightDownAndLeft - tview.Borders.BottomLeftFocus = tview.BoxDrawingsLightUpAndRight - tview.Borders.BottomRightFocus = tview.BoxDrawingsLightUpAndLeft - - top := tview.NewTextView() - top.SetBackgroundColor(app.Config.Style.TopBarBackground) - top.SetTextColor(app.Config.Style.TopBarText) - - app.UI = &UI{app: app, Top: top, Timeline: TimelineHome} - - app.UI.TootList = NewTootList(app, tview.NewList()) - app.UI.TootList.View.SetBackgroundColor(app.Config.Style.Background) - app.UI.TootList.View.SetSelectedTextColor(app.Config.Style.ListSelectedText) - app.UI.TootList.View.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground) - app.UI.TootList.View.ShowSecondaryText(false) - app.UI.TootList.View.SetHighlightFullLine(true) - - app.UI.TootList.View.SetChangedFunc(func(index int, _ string, _ string, _ rune) { - if app.HaveAccount { - app.UI.StatusText.ShowToot(index) - } - }) - - app.UI.StatusText = NewStatusText(app, tview.NewTextView(), - NewControls(app, tview.NewTextView()), NewLinkOverlay(app), - ) - app.UI.StatusText.View.SetWordWrap(true).SetDynamicColors(true) - app.UI.StatusText.View.SetBackgroundColor(app.Config.Style.Background) - app.UI.StatusText.View.SetTextColor(app.Config.Style.Text) - app.UI.StatusText.Controls.View.SetDynamicColors(true) - app.UI.StatusText.Controls.View.SetBackgroundColor(app.Config.Style.Background) - - app.UI.CmdBar = NewCmdBar(app, - tview.NewInputField(), - ) - app.UI.CmdBar.View.SetFieldBackgroundColor(app.Config.Style.Background) - app.UI.CmdBar.View.SetFieldTextColor(app.Config.Style.Text) - app.UI.Status = tview.NewTextView() - app.UI.Status.SetBackgroundColor(app.Config.Style.StatusBarBackground) - app.UI.Status.SetTextColor(app.Config.Style.StatusBarText) - - verticalLine := tview.NewBox().SetBackgroundColor(app.Config.Style.Background) - verticalLine.SetDrawFunc(func(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) { - for cy := y; cy < y+height; cy++ { - screen.SetContent(x, cy, tview.BoxDrawingsLightVertical, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) - } - return 0, 0, 0, 0 - }) - - app.UI.Pages = tview.NewPages() - app.UI.Pages.SetBackgroundColor(app.Config.Style.Background) - app.UI.Pages.AddPage("main", - tview.NewFlex(). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(top, 1, 0, false). - AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). - AddItem(app.UI.TootList.View, 0, 2, false). - AddItem(verticalLine, 1, 0, false). - AddItem(tview.NewBox().SetBackgroundColor(app.Config.Style.Background), 1, 0, false). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(app.UI.StatusText.View, 0, 9, false). - AddItem(app.UI.StatusText.Controls.View, 1, 0, false), - 0, 4, false), - 0, 1, false). - AddItem(app.UI.Status, 1, 1, false). - AddItem(app.UI.CmdBar.View, 1, 0, false), 0, 1, false), true, true) - - flLinks := tview.NewFlex() - flLinks.SetDrawFunc(clearContent) - flToot := tview.NewFlex() - flToot.SetDrawFunc(clearContent) - modal := func(fl *tview.Flex, p tview.Primitive, c tview.Primitive) tview.Primitive { - return tview.NewFlex(). - AddItem(nil, 0, 1, false). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(nil, 0, 1, false). - AddItem(fl.SetDirection(tview.FlexRow). - AddItem(p, 0, 9, true). - AddItem(c, 2, 1, false), 0, 8, false). - AddItem(nil, 0, 1, false), 0, 8, true). - AddItem(nil, 0, 1, false) - } - - authModal := func(f *tview.Flex, p tview.Primitive, c tview.Primitive) tview.Primitive { - return tview.NewFlex(). - AddItem(nil, 0, 1, false). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(nil, 0, 1, false). - AddItem(f.SetDirection(tview.FlexRow). - AddItem(c, 4, 1, false). - AddItem(p, 0, 9, true), 0, 9, true). - AddItem(nil, 0, 1, false), 0, 6, true). - AddItem(nil, 0, 1, false) - } - - app.UI.MessageBox = NewMessageBox(app, tview.NewTextView(), - NewControls(app, tview.NewTextView()), - ) - - app.UI.MessageBox.View.SetBackgroundColor(app.Config.Style.Background) - app.UI.MessageBox.View.SetTextColor(app.Config.Style.Text) - app.UI.MessageBox.View.SetDynamicColors(true) - app.UI.MessageBox.Controls.View.SetDynamicColors(true) - app.UI.MessageBox.Controls.View.SetBackgroundColor(app.Config.Style.Background) - app.UI.MessageBox.Controls.View.SetTextColor(app.Config.Style.Text) - app.UI.Pages.AddPage("toot", - modal(flToot, app.UI.MessageBox.View, app.UI.MessageBox.Controls.View), true, false) - - app.UI.Pages.AddPage("links", tview.NewFlex().AddItem(nil, 0, 1, false). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(nil, 0, 1, false). - AddItem(app.UI.StatusText.LinkOverlay.Flex.SetDirection(tview.FlexRow). - AddItem(app.UI.StatusText.LinkOverlay.List, 0, 10, true). - AddItem(app.UI.StatusText.LinkOverlay.TextBottom, 1, 1, true), 0, 8, false). - AddItem(nil, 0, 1, false), 0, 8, true). - AddItem(nil, 0, 1, false), true, false) - - app.UI.StatusText.LinkOverlay.Flex.SetDrawFunc(clearContent) - app.UI.StatusText.LinkOverlay.TextBottom.SetBackgroundColor(app.Config.Style.Background) - app.UI.StatusText.LinkOverlay.List.SetBackgroundColor(app.Config.Style.Background) - app.UI.StatusText.LinkOverlay.List.SetMainTextColor(app.Config.Style.Text) - app.UI.StatusText.LinkOverlay.List.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground) - app.UI.StatusText.LinkOverlay.List.SetSelectedTextColor(app.Config.Style.ListSelectedText) - app.UI.StatusText.LinkOverlay.List.ShowSecondaryText(false) - app.UI.StatusText.LinkOverlay.List.SetHighlightFullLine(true) - - app.UI.AuthOverlay = NewAuthoverlay(app, tview.NewFlex(), tview.NewInputField(), - NewControls(app, tview.NewTextView())) - - app.UI.Pages.AddPage("login", - authModal(app.UI.AuthOverlay.Flex, app.UI.AuthOverlay.View, app.UI.AuthOverlay.Controls.View), true, false) - app.UI.AuthOverlay.Flex.SetDrawFunc(clearContent) - app.UI.AuthOverlay.Flex.SetBackgroundColor(app.Config.Style.Background) - app.UI.AuthOverlay.View.SetBackgroundColor(app.Config.Style.Background) - app.UI.AuthOverlay.View.SetFieldBackgroundColor(app.Config.Style.Background) - app.UI.AuthOverlay.View.SetFieldTextColor(app.Config.Style.Text) - app.UI.AuthOverlay.Controls.View.SetBackgroundColor(app.Config.Style.Background) - app.UI.AuthOverlay.Controls.View.SetTextColor(app.Config.Style.Text) - app.UI.AuthOverlay.Draw() - - app.UI.MediaOverlay = NewMediaView(app) - app.UI.Pages.AddPage("media", tview.NewFlex().AddItem(nil, 0, 1, false). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(nil, 0, 1, false). - AddItem(app.UI.MediaOverlay.Flex.SetDirection(tview.FlexRow). - AddItem(app.UI.MediaOverlay.TextTop, 1, 1, true). - AddItem(app.UI.MediaOverlay.FileList, 0, 10, true). - AddItem(app.UI.MediaOverlay.TextBottom, 1, 1, true). - AddItem(app.UI.MediaOverlay.InputField.View, 2, 1, false), 0, 8, false). - AddItem(nil, 0, 1, false), 0, 8, true). - AddItem(nil, 0, 1, false), true, false) - - app.UI.MediaOverlay.FileList.SetBackgroundColor(app.Config.Style.Background) - app.UI.MediaOverlay.FileList.SetMainTextColor(app.Config.Style.Text) - app.UI.MediaOverlay.FileList.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground) - app.UI.MediaOverlay.FileList.SetSelectedTextColor(app.Config.Style.ListSelectedText) - app.UI.MediaOverlay.FileList.ShowSecondaryText(false) - app.UI.MediaOverlay.FileList.SetHighlightFullLine(true) - - app.UI.MediaOverlay.Flex.SetBackgroundColor(app.Config.Style.Background) - app.UI.MediaOverlay.TextTop.SetBackgroundColor(app.Config.Style.Background) - app.UI.MediaOverlay.TextTop.SetTextColor(app.Config.Style.Text) - app.UI.MediaOverlay.TextBottom.SetBackgroundColor(app.Config.Style.Background) - app.UI.MediaOverlay.TextBottom.SetTextColor(app.Config.Style.Text) - app.UI.MediaOverlay.InputField.View.SetBackgroundColor(app.Config.Style.Background) - app.UI.MediaOverlay.InputField.View.SetFieldBackgroundColor(app.Config.Style.Background) - app.UI.MediaOverlay.InputField.View.SetFieldTextColor(app.Config.Style.Text) - app.UI.MediaOverlay.Flex.SetDrawFunc(clearContent) - if !app.HaveAccount { app.UI.SetFocus(AuthOverlayFocus) } else { app.UI.LoggedIn() } - app.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + app.UI.Root.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if !app.HaveAccount { if event.Key() == tcell.KeyRune { switch event.Rune() { @@ -272,15 +72,15 @@ func main() { } if app.UI.Focus == LinkOverlayFocus { - app.UI.StatusText.LinkOverlay.InputHandler(event) + app.UI.LinkOverlay.InputHandler(event) return nil } if app.UI.Focus == CmdBarFocus { switch event.Key() { case tcell.KeyEsc: - app.UI.CmdBar.View.SetText("") - app.UI.CmdBar.View.Autocomplete().Blur() + app.UI.CmdBar.Input.SetText("") + app.UI.CmdBar.Input.Autocomplete().Blur() app.UI.SetFocus(LeftPaneFocus) return nil } @@ -382,7 +182,11 @@ func main() { app.UI.TootList.Next() return nil case 'q', 'Q': - app.App.Stop() + if app.UI.TootList.Focus == TootListThreadFocus { + app.UI.TootList.GoBack() + } else { + app.UI.Root.Stop() + } return nil } } else { @@ -397,7 +201,7 @@ func main() { app.UI.TootList.GoBack() return nil case tcell.KeyCtrlC: - app.App.Stop() + app.UI.Root.Stop() return nil } } @@ -416,7 +220,7 @@ func main() { if event.Key() == tcell.KeyRune { switch event.Rune() { case ':': - app.UI.CmdBar.View.SetText(":") + app.UI.CmdBar.Input.SetText(":") app.UI.SetFocus(CmdBarFocus) return nil case 't', 'T': @@ -451,7 +255,7 @@ func main() { ) words := strings.Split(":q,:quit,:timeline", ",") - app.UI.CmdBar.View.SetAutocompleteFunc(func(currentText string) (entries []string) { + app.UI.CmdBar.Input.SetAutocompleteFunc(func(currentText string) (entries []string) { if currentText == "" { return } @@ -466,7 +270,7 @@ func main() { return }) - app.UI.CmdBar.View.SetDoneFunc(func(key tcell.Key) { + app.UI.CmdBar.Input.SetDoneFunc(func(key tcell.Key) { input := app.UI.CmdBar.GetInput() parts := strings.Split(input, " ") if len(parts) == 0 { @@ -476,7 +280,7 @@ func main() { case ":q": fallthrough case ":quit": - app.App.Stop() + app.UI.Root.Stop() case ":timeline": if len(parts) < 2 { break @@ -502,11 +306,11 @@ func main() { } }) - app.UI.AuthOverlay.View.SetDoneFunc(func(key tcell.Key) { + app.UI.AuthOverlay.Input.SetDoneFunc(func(key tcell.Key) { app.UI.AuthOverlay.GotInput() }) - if err := app.App.SetRoot(app.UI.Pages, true).Run(); err != nil { + if err := app.UI.Root.SetRoot(app.UI.Pages, true).Run(); err != nil { panic(err) } } diff --git a/media.go b/media.go index 334ed53..317935b 100644 --- a/media.go +++ b/media.go @@ -14,7 +14,7 @@ const ( MediaFocusAdd ) -func NewMediaView(app *App) *MediaView { +func NewMediaOverlay(app *App) *MediaView { m := &MediaView{ app: app, Flex: tview.NewFlex(), @@ -24,6 +24,27 @@ func NewMediaView(app *App) *MediaView { InputField: &MediaInput{app: app, View: tview.NewInputField()}, Focus: MediaFocusOverview, } + m.Flex.SetBackgroundColor(app.Config.Style.Background) + + m.FileList.SetBackgroundColor(app.Config.Style.Background) + m.FileList.SetMainTextColor(app.Config.Style.Text) + m.FileList.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground) + m.FileList.SetSelectedTextColor(app.Config.Style.ListSelectedText) + m.FileList.ShowSecondaryText(false) + m.FileList.SetHighlightFullLine(true) + + m.TextTop.SetBackgroundColor(app.Config.Style.Background) + m.TextTop.SetTextColor(app.Config.Style.Text) + + m.TextBottom.SetBackgroundColor(app.Config.Style.Background) + m.TextBottom.SetTextColor(app.Config.Style.Text) + + m.InputField.View.SetBackgroundColor(app.Config.Style.Background) + m.InputField.View.SetFieldBackgroundColor(app.Config.Style.Background) + m.InputField.View.SetFieldTextColor(app.Config.Style.Text) + + m.Flex.SetDrawFunc(app.Config.ClearContent) + m.Draw() return m } @@ -61,9 +82,9 @@ func (m *MediaView) SetFocus(f MediaFocus) { switch f { case MediaFocusOverview: m.InputField.View.SetText("") - m.app.App.SetFocus(m.FileList) + m.app.UI.Root.SetFocus(m.FileList) case MediaFocusAdd: - m.app.App.SetFocus(m.InputField.View) + m.app.UI.Root.SetFocus(m.InputField.View) pwd, err := os.Getwd() if err != nil { home, err := os.UserHomeDir() diff --git a/messagebox.go b/messagebox.go index 1a5b83a..465277d 100644 --- a/messagebox.go +++ b/messagebox.go @@ -21,19 +21,31 @@ type msgToot struct { ScheduledAt *time.Time } -func NewMessageBox(app *App, view *tview.TextView, controls *Controls) *MessageBox { - return &MessageBox{ +func NewMessageBox(app *App) *MessageBox { + m := &MessageBox{ app: app, - View: view, + Flex: tview.NewFlex(), + View: tview.NewTextView(), Index: 0, - Controls: controls, + Controls: tview.NewTextView(), } + + m.View.SetBackgroundColor(app.Config.Style.Background) + m.View.SetTextColor(app.Config.Style.Text) + m.View.SetDynamicColors(true) + m.Controls.SetDynamicColors(true) + m.Controls.SetBackgroundColor(app.Config.Style.Background) + m.Controls.SetTextColor(app.Config.Style.Text) + m.Flex.SetDrawFunc(app.Config.ClearContent) + + return m } type MessageBox struct { app *App + Flex *tview.Flex View *tview.TextView - Controls *Controls + Controls *tview.TextView Index int maxIndex int currentToot msgToot @@ -111,7 +123,7 @@ func (m *MessageBox) Post() { func (m *MessageBox) Draw() { info := "\n[P]ost [E]dit text, [T]oggle CW, [C]ontent warning text [M]edia attachment" status := tview.Escape(info) - m.Controls.View.SetText(status) + m.Controls.SetText(status) var outputHead string var output string @@ -165,7 +177,7 @@ func (m *MessageBox) EditText() { } t = strings.Join(users, " ") } - text, err := openEditor(m.app.App, t) + text, err := openEditor(m.app.UI.Root, t) if err != nil { log.Fatalln(err) } @@ -174,7 +186,7 @@ func (m *MessageBox) EditText() { } func (m *MessageBox) EditSpoiler() { - text, err := openEditor(m.app.App, m.currentToot.SpoilerText) + text, err := openEditor(m.app.UI.Root, m.currentToot.SpoilerText) if err != nil { log.Fatalln(err) } diff --git a/status.go b/status.go new file mode 100644 index 0000000..887e44c --- /dev/null +++ b/status.go @@ -0,0 +1,22 @@ +package main + +import "github.com/rivo/tview" + +func NewStatusBar(app *App) *StatusBar { + s := &StatusBar{ + Text: tview.NewTextView(), + } + + s.Text.SetBackgroundColor(app.Config.Style.StatusBarBackground) + s.Text.SetTextColor(app.Config.Style.StatusBarText) + + return s +} + +type StatusBar struct { + Text *tview.TextView +} + +func (s *StatusBar) SetText(t string) { + s.Text.SetText(t) +} diff --git a/tootlist.go b/tootlist.go index 717019c..ac16b5e 100644 --- a/tootlist.go +++ b/tootlist.go @@ -9,11 +9,11 @@ import ( "github.com/rivo/tview" ) -type tootListFocus int +type TootListFocus int const ( - feedFocus tootListFocus = iota - threadFocus + TootListFeedFocus TootListFocus = iota + TootListThreadFocus ) type TootList struct { @@ -22,30 +22,43 @@ type TootList struct { Statuses []*mastodon.Status Thread []*mastodon.Status ThreadIndex int - View *tview.List - focus tootListFocus + List *tview.List + Focus TootListFocus loadingFeedOld bool loadingFeedNew bool } -func NewTootList(app *App, viewList *tview.List) *TootList { - return &TootList{ +func NewTootList(app *App) *TootList { + t := &TootList{ app: app, Index: 0, - focus: feedFocus, - View: viewList, + Focus: TootListFeedFocus, + List: tview.NewList(), } + t.List.SetBackgroundColor(app.Config.Style.Background) + t.List.SetSelectedTextColor(app.Config.Style.ListSelectedText) + t.List.SetSelectedBackgroundColor(app.Config.Style.ListSelectedBackground) + t.List.ShowSecondaryText(false) + t.List.SetHighlightFullLine(true) + + t.List.SetChangedFunc(func(index int, _ string, _ string, _ rune) { + if app.HaveAccount { + app.UI.TootView.ShowToot(index) + } + }) + + return t } func (t *TootList) GetStatuses() []*mastodon.Status { - if t.focus == threadFocus { + if t.Focus == TootListThreadFocus { return t.GetThread() } return t.GetFeed() } func (t *TootList) GetStatus(index int) (*mastodon.Status, error) { - if t.focus == threadFocus { + if t.Focus == TootListThreadFocus { return t.GetThreadStatus(index) } return t.GetFeedStatus(index) @@ -61,7 +74,7 @@ func (t *TootList) PrependFeedStatuses(s []*mastodon.Status) { t.SetFeedIndex( t.GetFeedIndex() + len(s), ) - t.View.SetCurrentItem(t.GetFeedIndex()) + t.List.SetCurrentItem(t.GetFeedIndex()) } func (t *TootList) AppendFeedStatuses(s []*mastodon.Status) { @@ -81,17 +94,17 @@ func (t *TootList) GetFeedStatus(index int) (*mastodon.Status, error) { } func (t *TootList) GetIndex() int { - if t.focus == threadFocus { + if t.Focus == TootListThreadFocus { return t.GetThreadIndex() } return t.GetFeedIndex() } func (t *TootList) SetIndex(index int) { - switch t.focus { - case feedFocus: + switch t.Focus { + case TootListFeedFocus: t.SetFeedIndex(index) - case threadFocus: + case TootListThreadFocus: t.SetThreadIndex(index) } } @@ -120,21 +133,21 @@ func (t *TootList) Prev() { index-- } - if index < 5 && t.focus == feedFocus { + if index < 5 && t.Focus == TootListFeedFocus { go func() { if t.loadingFeedNew { return } t.loadingFeedNew = true t.app.UI.LoadNewer(statuses[0]) - t.app.App.QueueUpdateDraw(func() { + t.app.UI.Root.QueueUpdateDraw(func() { t.Draw() t.loadingFeedNew = false }) }() } t.SetIndex(index) - t.View.SetCurrentItem(index) + t.List.SetCurrentItem(index) } func (t *TootList) Next() { @@ -145,34 +158,34 @@ func (t *TootList) Next() { index++ } - if (len(statuses)-index) < 10 && t.focus == feedFocus { + if (len(statuses)-index) < 10 && t.Focus == TootListFeedFocus { go func() { if t.loadingFeedOld || len(statuses) == 0 { return } t.loadingFeedOld = true t.app.UI.LoadOlder(statuses[len(statuses)-1]) - t.app.App.QueueUpdateDraw(func() { + t.app.UI.Root.QueueUpdateDraw(func() { t.Draw() t.loadingFeedOld = false }) }() } t.SetIndex(index) - t.View.SetCurrentItem(index) + t.List.SetCurrentItem(index) } func (t *TootList) Draw() { - t.View.Clear() + t.List.Clear() var statuses []*mastodon.Status var index int - switch t.focus { - case feedFocus: + switch t.Focus { + case TootListFeedFocus: statuses = t.GetFeed() index = t.GetFeedIndex() - case threadFocus: + case TootListThreadFocus: statuses = t.GetThread() index = t.GetThreadIndex() } @@ -191,11 +204,11 @@ func (t *TootList) Draw() { format = "15:04" } content := fmt.Sprintf("%s %s", sLocal.Format(format), s.Account.Acct) - t.View.InsertItem(currRow, content, "", 0, nil) + t.List.InsertItem(currRow, content, "", 0, nil) currRow++ } - t.View.SetCurrentItem(index) - t.app.UI.StatusText.ShowToot(index) + t.List.SetCurrentItem(index) + t.app.UI.TootView.ShowToot(index) } func (t *TootList) SetThread(s []*mastodon.Status, index int) { @@ -216,15 +229,15 @@ func (t *TootList) GetThreadStatus(index int) (*mastodon.Status, error) { } func (t *TootList) FocusFeed() { - t.focus = feedFocus + t.Focus = TootListFeedFocus } func (t *TootList) FocusThread() { - t.focus = threadFocus + t.Focus = TootListThreadFocus } func (t *TootList) GoBack() { - t.focus = feedFocus + t.Focus = TootListFeedFocus t.Draw() } diff --git a/statustext.go b/tootview.go similarity index 79% rename from statustext.go rename to tootview.go index ec08eea..c28563c 100644 --- a/statustext.go +++ b/tootview.go @@ -8,36 +8,42 @@ import ( "github.com/rivo/tview" ) -func NewStatusText(app *App, view *tview.TextView, controls *Controls, lo *LinkOverlay) *StatusText { - return &StatusText{ - app: app, - Index: 0, - View: view, - Controls: controls, - LinkOverlay: lo, +func NewTootView(app *App) *TootView { + t := &TootView{ + app: app, + Index: 0, + Text: tview.NewTextView(), + Controls: tview.NewTextView(), } + + t.Text.SetWordWrap(true).SetDynamicColors(true) + t.Text.SetBackgroundColor(app.Config.Style.Background) + t.Text.SetTextColor(app.Config.Style.Text) + t.Controls.SetDynamicColors(true) + t.Controls.SetBackgroundColor(app.Config.Style.Background) + + return t } -type StatusText struct { - app *App - Index int - View *tview.TextView - Controls *Controls - LinkOverlay *LinkOverlay +type TootView struct { + app *App + Index int + Text *tview.TextView + Controls *tview.TextView } -func (s *StatusText) ShowToot(index int) { +func (s *TootView) ShowToot(index int) { s.ShowTootOptions(index, false) } -func (s *StatusText) ShowTootOptions(index int, showSensitive bool) { +func (s *TootView) ShowTootOptions(index int, showSensitive bool) { status, err := s.app.UI.TootList.GetStatus(index) if err != nil { log.Fatalln(err) } var line string - _, _, width, _ := s.View.GetInnerRect() + _, _, width, _ := s.Text.GetInnerRect() for i := 0; i < width; i++ { line += "-" } @@ -64,7 +70,7 @@ func (s *StatusText) ShowTootOptions(index int, showSensitive bool) { stripped = sens + "\n\n" + stripped } } - s.LinkOverlay.SetURLs(urls) + s.app.UI.LinkOverlay.SetURLs(urls) subtleColor := fmt.Sprintf("[#%x]", s.app.Config.Style.Subtle.Hex()) special1 := fmt.Sprintf("[#%x]", s.app.Config.Style.TextSpecial1.Hex()) @@ -132,8 +138,8 @@ func (s *StatusText) ShowTootOptions(index int, showSensitive bool) { output += poll + media + card } - s.View.SetText(output) - s.View.ScrollToBeginning() + s.Text.SetText(output) + s.Text.ScrollToBeginning() var info []string if status.Favourited == true { info = append(info, "Un[F]avorite") @@ -157,5 +163,5 @@ func (s *StatusText) ShowTootOptions(index int, showSensitive bool) { info = append(info, "[D]elete") } - s.Controls.View.SetText(tview.Escape(strings.Join(info, " "))) + s.Controls.SetText(tview.Escape(strings.Join(info, " "))) } diff --git a/top.go b/top.go new file mode 100644 index 0000000..15d30dd --- /dev/null +++ b/top.go @@ -0,0 +1,18 @@ +package main + +import "github.com/rivo/tview" + +func NewTop(app *App) *Top { + t := &Top{ + Text: tview.NewTextView(), + } + + t.Text.SetBackgroundColor(app.Config.Style.TopBarBackground) + t.Text.SetTextColor(app.Config.Style.TopBarText) + + return t +} + +type Top struct { + Text *tview.TextView +} diff --git a/ui.go b/ui.go index 7d21de2..23effc5 100644 --- a/ui.go +++ b/ui.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/gdamore/tcell" "github.com/mattn/go-mastodon" "github.com/rivo/tview" ) @@ -21,16 +22,111 @@ const ( AuthOverlayFocus ) +func NewUI(app *App) *UI { + ui := &UI{ + app: app, + Root: tview.NewApplication(), + Top: NewTop(app), + Pages: tview.NewPages(), + Timeline: TimelineHome, + TootList: NewTootList(app), + TootView: NewTootView(app), + CmdBar: NewCmdBar(app), + StatusBar: NewStatusBar(app), + MessageBox: NewMessageBox(app), + LinkOverlay: NewLinkOverlay(app), + AuthOverlay: NewAuthOverlay(app), + MediaOverlay: NewMediaOverlay(app), + } + + verticalLine := tview.NewBox().SetBackgroundColor(app.Config.Style.Background) + verticalLine.SetDrawFunc(func(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) { + for cy := y; cy < y+height; cy++ { + screen.SetContent(x, cy, tview.BoxDrawingsLightVertical, nil, tcell.StyleDefault.Foreground(app.Config.Style.Subtle)) + } + return 0, 0, 0, 0 + }) + + ui.Pages.SetBackgroundColor(app.Config.Style.Background) + + ui.Pages.AddPage("main", + tview.NewFlex(). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.Top.Text, 1, 0, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(ui.TootList.List, 0, 2, false). + AddItem(verticalLine, 1, 0, false). + AddItem(tview.NewBox().SetBackgroundColor(app.Config.Style.Background), 1, 0, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.TootView.Text, 0, 9, false). + AddItem(ui.TootView.Controls, 1, 0, false), + 0, 4, false), + 0, 1, false). + AddItem(ui.StatusBar.Text, 1, 1, false). + AddItem(ui.CmdBar.Input, 1, 0, false), 0, 1, false), true, true) + + ui.Pages.AddPage("toot", tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(ui.MessageBox.Flex.SetDirection(tview.FlexRow). + AddItem(ui.MessageBox.View, 0, 9, true). + AddItem(ui.MessageBox.Controls, 2, 1, false), 0, 8, false). + AddItem(nil, 0, 1, false), 0, 8, true). + AddItem(nil, 0, 1, false), true, false) + + ui.Pages.AddPage("links", tview.NewFlex().AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(ui.LinkOverlay.Flex.SetDirection(tview.FlexRow). + AddItem(ui.LinkOverlay.List, 0, 10, true). + AddItem(ui.LinkOverlay.TextBottom, 1, 1, true), 0, 8, false). + AddItem(nil, 0, 1, false), 0, 8, true). + AddItem(nil, 0, 1, false), true, false) + + ui.Pages.AddPage("login", + tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(ui.AuthOverlay.Flex.SetDirection(tview.FlexRow). + AddItem(ui.AuthOverlay.Text, 4, 1, false). + AddItem(ui.AuthOverlay.Input, 0, 9, true), 0, 9, true). + AddItem(nil, 0, 1, false), 0, 6, true). + AddItem(nil, 0, 1, false), + true, false) + + ui.Pages.AddPage("media", tview.NewFlex().AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(ui.MediaOverlay.Flex.SetDirection(tview.FlexRow). + AddItem(ui.MediaOverlay.TextTop, 1, 1, true). + AddItem(ui.MediaOverlay.FileList, 0, 10, true). + AddItem(ui.MediaOverlay.TextBottom, 1, 1, true). + AddItem(ui.MediaOverlay.InputField.View, 2, 1, false), 0, 8, false). + AddItem(nil, 0, 1, false), 0, 8, true). + AddItem(nil, 0, 1, false), true, false) + + ui.Root.SetBeforeDrawFunc(func(screen tcell.Screen) bool { + screen.Clear() + return false + }) + + return ui +} + type UI struct { app *App + Root *tview.Application Focus FocusAt - Top *tview.TextView - StatusText *StatusText + Top *Top + TootView *TootView TootList *TootList MessageBox *MessageBox CmdBar *CmdBar - Status *tview.TextView + StatusBar *StatusBar Pages *tview.Pages + LinkOverlay *LinkOverlay AuthOverlay *AuthOverlay MediaOverlay *MediaView Timeline TimelineType @@ -40,29 +136,29 @@ func (ui *UI) SetFocus(f FocusAt) { ui.Focus = f switch f { case RightPaneFocus: - ui.Status.SetText("-- VIEW --") - ui.app.App.SetFocus(ui.StatusText.View) + ui.StatusBar.SetText("-- VIEW --") + ui.Root.SetFocus(ui.TootView.Text) case CmdBarFocus: - ui.Status.SetText("-- CMD --") - ui.app.App.SetFocus(ui.CmdBar.View) + ui.StatusBar.SetText("-- CMD --") + ui.Root.SetFocus(ui.CmdBar.Input) case MessageFocus: - ui.Status.SetText("-- TOOT --") + ui.StatusBar.SetText("-- TOOT --") ui.Pages.ShowPage("toot") ui.Pages.HidePage("media") - ui.app.App.SetFocus(ui.MessageBox.View) + ui.Root.SetFocus(ui.MessageBox.View) case MessageAttachmentFocus: ui.Pages.ShowPage("media") case LinkOverlayFocus: - ui.Status.SetText("-- LINK --") + ui.StatusBar.SetText("-- LINK --") ui.Pages.ShowPage("links") - ui.app.App.SetFocus(ui.StatusText.LinkOverlay.List) + ui.Root.SetFocus(ui.LinkOverlay.List) case AuthOverlayFocus: - ui.Status.SetText("-- LOGIN --") + ui.StatusBar.SetText("-- LOGIN --") ui.Pages.ShowPage("login") - ui.app.App.SetFocus(ui.StatusText.app.UI.AuthOverlay.View) + ui.Root.SetFocus(ui.AuthOverlay.Input) default: - ui.Status.SetText("-- LIST --") - ui.app.App.SetFocus(ui.Pages) + ui.StatusBar.SetText("-- LIST --") + ui.Root.SetFocus(ui.Pages) ui.Pages.HidePage("toot") ui.Pages.HidePage("media") ui.Pages.HidePage("links") @@ -85,6 +181,10 @@ func (ui *UI) ShowThread() { log.Fatalln(err) } + if status.Reblog != nil { + status = status.Reblog + } + thread, index, err := ui.app.API.GetThread(status) if err != nil { log.Fatalln(err) @@ -97,12 +197,12 @@ func (ui *UI) ShowThread() { } func (ui *UI) ShowSensetive() { - ui.StatusText.ShowTootOptions(ui.TootList.GetIndex(), true) + ui.TootView.ShowTootOptions(ui.TootList.GetIndex(), true) } func (ui *UI) NewToot() { - ui.app.App.SetFocus(ui.MessageBox.View) - ui.app.UI.MediaOverlay.Reset() + ui.Root.SetFocus(ui.MessageBox.View) + ui.MediaOverlay.Reset() ui.MessageBox.NewToot() ui.MessageBox.Draw() ui.SetFocus(MessageFocus) @@ -116,7 +216,7 @@ func (ui *UI) Reply() { if status.Reblog != nil { status = status.Reblog } - ui.app.UI.MediaOverlay.Reset() + ui.MediaOverlay.Reset() ui.MessageBox.Reply(status) ui.MessageBox.Draw() ui.SetFocus(MessageFocus) @@ -160,7 +260,7 @@ func (ui *UI) OpenMedia() { func (ui *UI) LoggedIn() { ui.SetFocus(LeftPaneFocus) - fmt.Fprint(ui.Top, "tut\n") + fmt.Fprint(ui.Top.Text, "tut\n") me, err := ui.app.API.Client.GetAccountCurrentUser(context.Background()) if err != nil { @@ -176,7 +276,7 @@ func (ui *UI) LoadNewer(status *mastodon.Status) int { if err != nil { log.Fatalln(err) } - ui.app.UI.TootList.PrependFeedStatuses(statuses) + ui.TootList.PrependFeedStatuses(statuses) return len(statuses) } @@ -185,7 +285,7 @@ func (ui *UI) LoadOlder(status *mastodon.Status) int { if err != nil { log.Fatalln(err) } - ui.app.UI.TootList.AppendFeedStatuses(statuses) + ui.TootList.AppendFeedStatuses(statuses) return len(statuses) } @@ -226,3 +326,26 @@ func (ui *UI) DeleteStatus() { log.Fatalln(err) } } + +func (conf *Config) ClearContent(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) { + for cx := x; cx < width+x; cx++ { + for cy := y; cy < height+y; cy++ { + screen.SetContent(cx, cy, ' ', nil, tcell.StyleDefault.Background(conf.Style.Background)) + } + } + y2 := y + height + for cx := x + 1; cx < width+x; cx++ { + screen.SetContent(cx, y, tview.BoxDrawingsLightHorizontal, nil, tcell.StyleDefault.Foreground(conf.Style.Subtle)) + screen.SetContent(cx, y2, tview.BoxDrawingsLightHorizontal, nil, tcell.StyleDefault.Foreground(conf.Style.Subtle)) + } + x2 := x + width + for cy := y + 1; cy < height+y; cy++ { + screen.SetContent(x, cy, tview.BoxDrawingsLightVertical, nil, tcell.StyleDefault.Foreground(conf.Style.Subtle)) + screen.SetContent(x2, cy, tview.BoxDrawingsLightVertical, nil, tcell.StyleDefault.Foreground(conf.Style.Subtle)) + } + screen.SetContent(x, y, tview.BoxDrawingsLightDownAndRight, nil, tcell.StyleDefault.Foreground(conf.Style.Subtle)) + screen.SetContent(x, y+height, tview.BoxDrawingsLightUpAndRight, nil, tcell.StyleDefault.Foreground(conf.Style.Subtle)) + screen.SetContent(x+width, y, tview.BoxDrawingsLightDownAndLeft, nil, tcell.StyleDefault.Foreground(conf.Style.Subtle)) + screen.SetContent(x+width, y+height, tview.BoxDrawingsLightUpAndLeft, nil, tcell.StyleDefault.Foreground(conf.Style.Subtle)) + return x + 1, y + 1, width - 1, height - 1 +}