amfora/display/tab.go
makeworld 543d15abfc 🔀 Refactor to use tab struct
Squashed commit of the following:

commit 72f36afc9e
Author: makeworld <colecmac@protonmail.com>
Date:   Tue Jul 7 16:15:45 2020 -0400

    🚧 Scroll is applied correctly when navigating around

commit 4b8982723f
Author: makeworld <colecmac@protonmail.com>
Date:   Tue Jul 7 15:34:45 2020 -0400

    🚧 Fix bottomBar code

    Make sure it always resets to a selected link if one was selected before

commit be09ffcf91
Author: makeworld <colecmac@protonmail.com>
Date:   Mon Jul 6 20:30:54 2020 -0400

    🚧 Switch to using tab pointers instead of ints

    Almost finished overall work.

commit ef8ab3da39
Author: makeworld <colecmac@protonmail.com>
Date:   Mon Jul 6 12:10:50 2020 -0400

    🚧 Fixed some bugs, major ones remain

commit d3d47a344d
Author: makeworld <colecmac@protonmail.com>
Date:   Sat Jul 4 20:58:46 2020 -0400

    🚧 Everything uses tab struct, no compile errors, untested

commit 44bf54c12f
Author: makeworld <colecmac@protonmail.com>
Date:   Sat Jul 4 13:24:49 2020 -0400

    🚧 Initial work on tab struct
2020-07-07 21:13:45 -04:00

231 lines
6.5 KiB
Go

package display
import (
"strconv"
"strings"
"sync"
"github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/structs"
"gitlab.com/tslocum/cview"
)
type tabMode int
const (
tabModeDone tabMode = iota
tabModeLoading
)
type tabHistory struct {
urls []string
pos int // Position: where in the list of URLs we are
}
// tab hold the information needed for each browser tab.
type tab struct {
page *structs.Page
view *cview.TextView
history *tabHistory
mode tabMode
reformatMut *sync.Mutex // Mutex for reformatting, so there's only one reformat job at once
barLabel string // The bottomBar label for the tab
barText string // The bottomBar text for the tab
}
// makeNewTab initializes an tab struct with no content.
func makeNewTab() *tab {
t := tab{
page: &structs.Page{Mode: structs.ModeOff},
view: cview.NewTextView().
SetDynamicColors(true).
SetRegions(true).
SetScrollable(true).
SetWrap(false).
SetChangedFunc(func() {
App.Draw()
}),
history: &tabHistory{},
reformatMut: &sync.Mutex{},
mode: tabModeDone,
}
t.view.SetDoneFunc(func(key tcell.Key) {
// Altered from: https://gitlab.com/tslocum/cview/-/blob/master/demos/textview/main.go
// Handles being able to select and "click" links with the enter and tab keys
tab := curTab // Don't let it change in the middle of the code
if key == tcell.KeyEsc && tabs[tab].mode == tabModeDone {
// Stop highlighting
bottomBar.SetLabel("")
bottomBar.SetText(tabs[tab].page.Url)
tabs[tab].clearSelected()
tabs[tab].saveBottomBar()
return
}
if len(tabs[tab].page.Links) <= 0 {
// No links on page
return
}
currentSelection := tabs[tab].view.GetHighlights()
numSelections := len(tabs[tab].page.Links)
if key == tcell.KeyEnter {
if len(currentSelection) > 0 {
// A link was selected, "click" it and load the page it's for
bottomBar.SetLabel("")
linkN, _ := strconv.Atoi(currentSelection[0])
tabs[tab].page.Selected = tabs[tab].page.Links[linkN]
tabs[tab].page.SelectedID = currentSelection[0]
followLink(tabs[tab], tabs[tab].page.Url, tabs[tab].page.Links[linkN])
return
} else {
// They've started link highlighting
tabs[tab].page.Mode = structs.ModeLinkSelect
tabs[tab].view.Highlight("0").ScrollToHighlight()
// Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabs[tab].page.Links[0])
tabs[tab].saveBottomBar()
tabs[tab].page.Selected = tabs[tab].page.Links[0]
tabs[tab].page.SelectedID = "0"
}
} else if len(currentSelection) > 0 {
// There's still a selection, but a different key was pressed, not Enter
index, _ := strconv.Atoi(currentSelection[0])
if key == tcell.KeyTab {
index = (index + 1) % numSelections
} else if key == tcell.KeyBacktab {
index = (index - 1 + numSelections) % numSelections
} else {
return
}
tabs[tab].view.Highlight(strconv.Itoa(index)).ScrollToHighlight()
// Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabs[tab].page.Links[index])
tabs[tab].saveBottomBar()
tabs[tab].page.Selected = tabs[tab].page.Links[index]
tabs[tab].page.SelectedID = strconv.Itoa(index)
}
})
return &t
}
// addToHistory adds the given URL to history.
// It assumes the URL is currently being loaded and displayed on the page.
func (t *tab) addToHistory(u string) {
if t.history.pos < len(t.history.urls)-1 {
// We're somewhere in the middle of the history instead, with URLs ahead and behind.
// The URLs ahead need to be removed so this new URL is the most recent item in the history
t.history.urls = t.history.urls[:t.history.pos+1]
}
t.history.urls = append(t.history.urls, u)
t.history.pos++
}
// pageUp scrolls up 75% of the height of the terminal, like Bombadillo.
func (t *tab) pageUp() {
row, col := t.view.GetScrollOffset()
t.view.ScrollTo(row-(termH/4)*3, col)
}
// pageDown scrolls down 75% of the height of the terminal, like Bombadillo.
func (t *tab) pageDown() {
row, col := t.view.GetScrollOffset()
t.view.ScrollTo(row+(termH/4)*3, col)
}
// hasContent returns true when the tab has a page that could be displayed.
// The most likely situation where false would be returned is when the default
// new tab content is being displayed.
func (t *tab) hasContent() bool {
if t.page == nil || t.view == nil {
return false
}
if t.page.Url == "" {
return false
}
if strings.HasPrefix(t.page.Url, "about:") {
return false
}
if t.page.Content == "" {
return false
}
return true
}
// saveScroll saves where in the page the user was.
// It should be used whenever moving from one page to another.
func (t *tab) saveScroll() {
// It will also be saved in the cache because the cache uses the same pointer
row, col := t.view.GetScrollOffset()
t.page.Row = row
t.page.Column = col
}
// applyScroll applies the saved scroll values to the page and tab.
// It should only be used when going backward and forward.
func (t *tab) applyScroll() {
t.view.ScrollTo(t.page.Row, t.page.Column)
}
// saveBottomBar saves the current bottomBar values in the tab.
func (t *tab) saveBottomBar() {
t.barLabel = bottomBar.GetLabel()
t.barText = bottomBar.GetText()
}
// applyBottomBar sets the bottomBar using the stored tab values
func (t *tab) applyBottomBar() {
bottomBar.SetLabel(t.barLabel)
bottomBar.SetText(t.barText)
}
// clearSelected turns off any selection that was going on.
// It does not affect the bottomBar.
func (t *tab) clearSelected() {
t.page.Mode = structs.ModeOff
t.page.Selected = ""
t.page.SelectedID = ""
t.view.Highlight("")
}
// applySelected selects whatever is stored as the selected element in the struct,
// and sets the mode accordingly.
// It is safe to call if nothing was selected previously.
//
// applyBottomBar should be called after, as this func might set some bottomBar values.
func (t *tab) applySelected() {
if t.page.Mode == structs.ModeOff {
// Just in case
t.page.Selected = ""
t.page.SelectedID = ""
t.view.Highlight("")
return
} else if t.page.Mode == structs.ModeLinkSelect {
t.view.Highlight(t.page.SelectedID)
if t.mode == tabModeDone {
// Page is not loading so bottomBar can change
t.barLabel = "[::b]Link: [::-]"
t.barText = t.page.Selected
}
}
}
// applyAll uses applyScroll and applySelected to put a tab's TextView back the way it was.
// It also uses applyBottomBar if this is the current tab.
func (t *tab) applyAll() {
t.applySelected()
t.applyScroll()
if t == tabs[curTab] {
t.applyBottomBar()
}
}