// This file is part of beagles.
//
// Copyright © 2020 Chris Palmer <chris@red-oxide.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"strings"
"github.com/gdamore/tcell"
tui "gitlab.com/tslocum/cview"
)
type statusType int
const (
listStatus statusType = iota
contentStatus
commandStatus
helpStatus
)
type ui struct {
InCmdMode bool
Status statusType
App *app
CommandLine *commandLine
List *list
Content *content
StatusLine *statusLine
DB *Storage
Pages *tui.Pages
}
func initUI(db *Storage, theme themeType) *ui {
i := &ui{
DB: db,
Content: newContent(&theme),
List: newList(&theme),
StatusLine: newStatusLine(&theme),
CommandLine: newCommandLine(&theme),
App: newApp(),
Pages: tui.NewPages(),
}
i.App.processInput(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEsc:
if i.InCmdMode {
i.App.draw(func() {
i.CommandLine.clear()
})
i.setStatus(i.Status)
}
return event
case tcell.KeyLeft:
return i.moveLeft(event)
case tcell.KeyDown:
return i.moveDown(event)
case tcell.KeyUp:
return i.moveUp(event)
case tcell.KeyRight:
return i.moveRight(event)
case tcell.KeyRune:
switch event.Rune() {
case ':':
if i.InCmdMode {
return event
}
i.setStatus(commandStatus)
return nil
case 'h':
return i.moveLeft(event)
case 'j':
return i.moveDown(event)
case 'k':
return i.moveUp(event)
case 'l':
return i.moveRight(event)
case 'm':
return i.markRead(event)
case 'n':
return i.openLink(event)
}
}
return event
})
i.CommandLine.onChange(func(text string) {
if text == "" {
i.setStatus(listStatus)
}
})
i.CommandLine.onEnter(func(key tcell.Key) {
input := i.CommandLine.getText()
args := strings.Split(input, " ")
switch args[0] {
case ":quit", ":q":
i.App.exit()
case ":help", ":h", ":?":
i.setStatus(helpStatus)
case ":list":
i.setStatus(listStatus)
case ":add":
i.addFeed(args)
case ":remove", ":rm":
i.deleteFeed(args)
case ":update", ":up":
i.updateFeed(args)
}
i.InCmdMode = false
})
i.createHelpPage(&theme)
i.createListPage(&theme)
i.setStatus(listStatus)
return i
}
func (i *ui) updateFeed(args []string) {
i.App.draw(func() {
if err := i.DB.Update(); err != nil {
i.CommandLine.setError(err.Error())
i.setStatus(listStatus)
return
}
i.Content.clear()
i.List.clear()
for x, item := range i.DB.Queue {
if x == 0 {
i.Content.setText(item.Item.Link, item.Item.Content)
}
i.List.add(item.Item)
}
i.CommandLine.clear()
})
}
func (i *ui) addFeed(args []string) {
i.App.draw(func() {
if err := i.DB.CreateFeed(args[1]); err != nil {
i.CommandLine.setError(err.Error())
i.setStatus(listStatus)
return
}
i.Content.clear()
i.List.clear()
for x, item := range i.DB.Queue {
if x == 0 {
i.Content.setText(item.Item.Link, item.Item.Content)
}
i.List.add(item.Item)
}
i.CommandLine.clear()
})
}
func (i *ui) deleteFeed(args []string) {
i.App.draw(func() {
if err := i.DB.DeleteFeed(args[1]); err != nil {
i.CommandLine.setError(err.Error())
i.setStatus(listStatus)
return
}
i.Content.clear()
i.List.clear()
for x, item := range i.DB.Queue {
if x == 0 {
i.Content.setText(item.Item.Link, item.Item.Content)
}
i.List.add(item.Item)
}
i.CommandLine.clear()
})
}
func (i *ui) openLink(event *tcell.EventKey) *tcell.EventKey {
switch i.Status {
case listStatus, contentStatus:
if i.InCmdMode {
return event
}
if i.List.size() != 0 {
index := i.List.index()
item := i.DB.Queue[index].Item
go openBrowser(item.Link)
}
return nil
}
return event
}
func (i *ui) markRead(event *tcell.EventKey) *tcell.EventKey {
switch i.Status {
case listStatus, contentStatus:
if i.InCmdMode || i.List.size() == 0 {
return event
}
i.App.draw(func() {
index := i.List.remove()
if err := i.DB.MarkRead(i.DB.Queue[index]); err != nil {
i.CommandLine.setError(err.Error())
i.setStatus(listStatus)
return
}
size := i.List.size()
if size > 0 && index != -1 {
switch index {
case size:
index--
i.List.setIndex(index)
case 0:
i.List.setIndex(0)
default:
i.List.setIndex(index)
}
item := i.DB.Queue[index].Item
i.Content.setText(item.Link, item.Content)
}
if size == 0 {
i.Content.setText("", "")
}
})
return nil
}
return event
}
func (i *ui) moveLeft(event *tcell.EventKey) *tcell.EventKey {
switch i.Status {
case contentStatus:
if i.InCmdMode {
return event
}
i.setStatus(listStatus)
return nil
}
return event
}
func (i *ui) moveRight(event *tcell.EventKey) *tcell.EventKey {
switch i.Status {
case listStatus:
if i.InCmdMode {
return event
}
i.setStatus(contentStatus)
return nil
}
return event
}
func (i *ui) moveUp(event *tcell.EventKey) *tcell.EventKey {
switch i.Status {
case listStatus:
if i.InCmdMode {
return event
}
index := i.List.decrement()
if index != -1 {
item := i.DB.Queue[index].Item
i.Content.setText(item.Link, item.Content)
}
return nil
}
return event
}
func (i *ui) moveDown(event *tcell.EventKey) *tcell.EventKey {
switch i.Status {
case listStatus:
if i.InCmdMode {
return event
}
index := i.List.increment()
if index != -1 {
item := i.DB.Queue[index].Item
i.Content.setText(item.Link, item.Content)
}
return nil
}
return event
}
func (i *ui) createListPage(theme *themeType) {
qc := tui.NewFlex()
qc.SetDirection(tui.FlexColumn)
qc.AddItem(i.List.Widget, 0, 3, false)
qc.AddItem(newSeparator(theme, true), 1, 0, false)
qc.AddItem(i.Content.Widget, 0, 6, false)
cv := tui.NewFlex()
cv.SetDirection(tui.FlexRow)
cv.AddItem(newTitleLine(theme), 1, 0, false)
cv.AddItem(qc, 0, 1, false)
cv.AddItem(i.StatusLine.Widget, 1, 1, false)
cv.AddItem(i.CommandLine.Widget, 1, 0, false)
view := tui.NewFlex()
view.AddItem(cv, 0, 1, false)
resize := true
visable := true
i.Pages.AddPage("list", view, resize, visable)
}
func (i *ui) createHelpPage(theme *themeType) {
text := `beagles v%s is available under the terms of the GPL-3.0 license.
Copyright © 2020 Chris Palmer <chris@red-oxide.org>
https://git.sr.ht/~chrisppy/beagles
KEYS:
h,🠔
move to the left section (if applicable)
j,🠗
navigate down in the current section (if applicable)
k,🠕
navigate up in the current section (if applicable)
l,🠖
move the the right section (if applicable)
m
mark current post as read when in --LIST-- or --CONTENT--
n
open post url when in --LIST-- or --CONTENT--
:
enter command mode
COMMANDS:
add [url]
add a feed
remove,rm [url]
remove the feed
update,up
update all feeds
help,h,?
display help page
list
display list page
quit,q
exit the application
SEE ALSO:
Additional information can be found in the manual pages:
beagles(1)
beagles-config(5)
`
h := tui.NewTextView()
h.SetText(fmt.Sprintf(text, Version))
h.SetTextAlign(tui.AlignLeft)
bgColor := theme.BackgroundColor
fgColor := theme.ForegroundColor
if theme.Help != nil {
bgColor = theme.Help.BackgroundColor
fgColor = theme.Help.ForegroundColor
}
if bgColor == "transparent" {
h.SetBackgroundColor(tcell.ColorDefault)
} else {
h.SetBackgroundColor(tcell.GetColor(bgColor))
}
h.SetTextColor(tcell.GetColor(fgColor))
cv := tui.NewFlex()
cv.SetDirection(tui.FlexRow)
cv.AddItem(newTitleLine(theme), 1, 0, false)
cv.AddItem(h, 0, 1, false)
cv.AddItem(i.StatusLine.Widget, 1, 1, false)
cv.AddItem(i.CommandLine.Widget, 1, 0, false)
view := tui.NewFlex()
view.AddItem(cv, 0, 1, false)
resize := true
visable := true
i.Pages.AddPage("help", view, resize, visable)
}
func (i *ui) setStatus(s statusType) {
switch s {
case commandStatus:
i.InCmdMode = true
i.CommandLine.setText(":")
i.StatusLine.setText("-- COMMAND --")
i.App.switchTo(i.CommandLine.Widget)
return
case contentStatus:
i.InCmdMode = false
i.StatusLine.setText("-- CONTENT --")
i.App.switchTo(i.Content.Widget)
case helpStatus:
i.InCmdMode = false
i.StatusLine.setText("-- HELP --")
i.Pages.SwitchToPage("help")
default:
i.InCmdMode = false
i.StatusLine.setText("-- LIST --")
i.Pages.SwitchToPage("list")
}
i.Status = s
}