summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2022-02-25 17:53:33 +0100
committerRobin Jarry <robin@jarry.cc>2022-03-03 21:11:05 +0100
commit515a8b56f6e9b4e6efaf6a6a29c851dadf4b4a56 (patch)
tree773eb7af099ea985087d76db41c40d36f81cc153
parentbd65ce1010a78eec38ba9c70d9fc23f85cd087a1 (diff)
downloadaerc-515a8b56f6e9b4e6efaf6a6a29c851dadf4b4a56.zip
scrollable: extract scrolling behavior for reuse
Extract the vertical scrolling ability into its own Scrollable struct that can be embedded and reused across any ui element that relies on scrolling. Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r--widgets/dirlist.go60
-rw-r--r--widgets/dirtree.go45
-rw-r--r--widgets/msglist.go67
-rw-r--r--widgets/scrollable.go68
4 files changed, 101 insertions, 139 deletions
diff --git a/widgets/dirlist.go b/widgets/dirlist.go
index 19b45d4..cb22a61 100644
--- a/widgets/dirlist.go
+++ b/widgets/dirlist.go
@@ -41,6 +41,7 @@ type DirectoryLister interface {
type DirectoryList struct {
ui.Invalidatable
+ Scrollable
aercConf *config.AercConfig
acctConf *config.AccountConfig
store *lib.DirStore
@@ -48,7 +49,6 @@ type DirectoryList struct {
logger *log.Logger
selecting string
selected string
- scroll int
spinner *Spinner
worker *types.Worker
skipSelect chan bool
@@ -261,16 +261,11 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
return
}
- dirlist.ensureScroll(ctx.Height())
-
- needScrollbar := true
- percentVisible := float64(ctx.Height()) / float64(len(dirlist.dirs))
- if percentVisible >= 1.0 {
- needScrollbar = false
- }
+ dirlist.UpdateScroller(ctx.Height(), len(dirlist.dirs))
+ dirlist.EnsureScroll(findString(dirlist.dirs, dirlist.selecting))
textWidth := ctx.Width()
- if needScrollbar {
+ if dirlist.NeedScrollbar() {
textWidth -= 1
}
if textWidth < 0 {
@@ -278,10 +273,10 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
}
for i, name := range dirlist.dirs {
- if i < dirlist.scroll {
+ if i < dirlist.Scroll() {
continue
}
- row := i - dirlist.scroll
+ row := i - dirlist.Scroll()
if row >= ctx.Height() {
break
}
@@ -299,13 +294,13 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
ctx.Printf(0, row, style, dirString)
}
- if needScrollbar {
+ if dirlist.NeedScrollbar() {
scrollBarCtx := ctx.Subcontext(ctx.Width()-1, 0, 1, ctx.Height())
- dirlist.drawScrollbar(scrollBarCtx, percentVisible)
+ dirlist.drawScrollbar(scrollBarCtx)
}
}
-func (dirlist *DirectoryList) drawScrollbar(ctx *ui.Context, percentVisible float64) {
+func (dirlist *DirectoryList) drawScrollbar(ctx *ui.Context) {
gutterStyle := tcell.StyleDefault
pillStyle := tcell.StyleDefault.Reverse(true)
@@ -313,44 +308,11 @@ func (dirlist *DirectoryList) drawScrollbar(ctx *ui.Context, percentVisible floa
ctx.Fill(0, 0, 1, ctx.Height(), ' ', gutterStyle)
// pill
- pillSize := int(math.Ceil(float64(ctx.Height()) * percentVisible))
- percentScrolled := float64(dirlist.scroll) / float64(len(dirlist.dirs))
- pillOffset := int(math.Floor(float64(ctx.Height()) * percentScrolled))
+ pillSize := int(math.Ceil(float64(ctx.Height()) * dirlist.PercentVisible()))
+ pillOffset := int(math.Floor(float64(ctx.Height()) * dirlist.PercentScrolled()))
ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
}
-func (dirlist *DirectoryList) ensureScroll(h int) {
- selectingIdx := findString(dirlist.dirs, dirlist.selecting)
- if selectingIdx < 0 {
- // dir not found, meaning we are currently adding / removing a dir.
- // we can simply ignore this until we get redrawn with the new
- // dirlist.dir content
- return
- }
-
- maxScroll := len(dirlist.dirs) - h
- if maxScroll < 0 {
- maxScroll = 0
- }
-
- if selectingIdx >= dirlist.scroll && selectingIdx < dirlist.scroll+h {
- if dirlist.scroll > maxScroll {
- dirlist.scroll = maxScroll
- }
- return
- }
-
- if selectingIdx >= dirlist.scroll+h {
- dirlist.scroll = selectingIdx - h + 1
- } else if selectingIdx < dirlist.scroll {
- dirlist.scroll = selectingIdx
- }
-
- if dirlist.scroll > maxScroll {
- dirlist.scroll = maxScroll
- }
-}
-
func (dirlist *DirectoryList) MouseEvent(localX int, localY int, event tcell.Event) {
switch event := event.(type) {
case *tcell.EventMouse:
diff --git a/widgets/dirtree.go b/widgets/dirtree.go
index 52195d8..cf4575f 100644
--- a/widgets/dirtree.go
+++ b/widgets/dirtree.go
@@ -40,7 +40,7 @@ func (dt *DirectoryTree) UpdateList(done func([]string)) {
dt.buildTree()
dt.listIdx = findString(dt.dirs, dt.selecting)
dt.Select(dt.selecting)
- dt.scroll = 0
+ dt.Scrollable = Scrollable{}
})
}
@@ -60,7 +60,8 @@ func (dt *DirectoryTree) Draw(ctx *ui.Context) {
return
}
- dt.ensureScroll(ctx.Height())
+ dt.UpdateScroller(ctx.Height(), n)
+ dt.EnsureScroll(dt.countVisible(dt.list[:dt.listIdx]))
needScrollbar := true
percentVisible := float64(ctx.Height()) / float64(n)
@@ -78,10 +79,10 @@ func (dt *DirectoryTree) Draw(ctx *ui.Context) {
rowNr := 0
for i, node := range dt.list {
- if i < dt.scroll || !isVisible(node) {
+ if i < dt.Scroll() || !isVisible(node) {
continue
}
- row := rowNr - dt.scroll
+ row := rowNr - dt.Scroll()
if row >= ctx.Height() {
break
}
@@ -105,41 +106,9 @@ func (dt *DirectoryTree) Draw(ctx *ui.Context) {
ctx.Printf(0, row, style, dirString)
}
- if needScrollbar {
+ if dt.NeedScrollbar() {
scrollBarCtx := ctx.Subcontext(ctx.Width()-1, 0, 1, ctx.Height())
- dt.drawScrollbar(scrollBarCtx, percentVisible)
- }
-}
-
-func (dt *DirectoryTree) ensureScroll(h int) {
- selectingIdx := dt.countVisible(dt.list[:dt.listIdx])
- if selectingIdx < 0 {
- // dir not found, meaning we are currently adding / removing a dir.
- // we can simply ignore this until we get redrawn with the new
- // dirlist.dir content
- return
- }
-
- maxScroll := dt.countVisible(dt.list) - h
- if maxScroll < 0 {
- maxScroll = 0
- }
-
- if selectingIdx >= dt.scroll && selectingIdx < dt.scroll+h {
- if dt.scroll > maxScroll {
- dt.scroll = maxScroll
- }
- return
- }
-
- if selectingIdx >= dt.scroll+h {
- dt.scroll = selectingIdx - h + 1
- } else if selectingIdx < dt.scroll {
- dt.scroll = selectingIdx
- }
-
- if dt.scroll > maxScroll {
- dt.scroll = maxScroll
+ dt.drawScrollbar(scrollBarCtx)
}
}
diff --git a/widgets/msglist.go b/widgets/msglist.go
index ae0d211..f4cea70 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -20,10 +20,10 @@ import (
type MessageList struct {
ui.Invalidatable
+ Scrollable
conf *config.AercConfig
logger *log.Logger
height int
- scroll int
nmsgs int
spinner *Spinner
store *lib.MessageStore
@@ -70,16 +70,13 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
}
}
- ml.ensureScroll()
-
- needScrollbar := true
- percentVisible := float64(ctx.Height()) / float64(len(store.Uids()))
- if percentVisible >= 1.0 {
- needScrollbar = false
+ ml.UpdateScroller(ml.height, len(store.Uids()))
+ if store := ml.Store(); store != nil && len(store.Uids()) > 0 {
+ ml.EnsureScroll(store.SelectedIndex())
}
textWidth := ctx.Width()
- if needScrollbar {
+ if ml.NeedScrollbar() {
textWidth -= 1
}
if textWidth < 0 {
@@ -105,7 +102,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
return nil
}
counter--
- if counter > len(store.Uids())-1-ml.scroll {
+ if counter > len(store.Uids())-1-ml.Scroll() {
//skip messages which are higher than the viewport
return nil
}
@@ -142,7 +139,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
}
} else {
uids := store.Uids()
- for i := len(uids) - 1 - ml.scroll; i >= 0; i-- {
+ for i := len(uids) - 1 - ml.Scroll(); i >= 0; i-- {
uid := uids[i]
msg := store.Messages[uid]
fmtCtx := format.Ctx{
@@ -159,9 +156,9 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
}
}
- if needScrollbar {
+ if ml.NeedScrollbar() {
scrollbarCtx := ctx.Subcontext(ctx.Width()-1, 0, 1, ctx.Height())
- ml.drawScrollbar(scrollbarCtx, percentVisible)
+ ml.drawScrollbar(scrollbarCtx)
}
if len(store.Uids()) == 0 {
@@ -241,7 +238,7 @@ func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row i
var style tcell.Style
// current row
- if row == ml.store.SelectedIndex()-ml.scroll {
+ if row == ml.store.SelectedIndex()-ml.Scroll() {
style = uiConfig.GetComposedStyleSelected(config.STYLE_MSGLIST_DEFAULT, msg_styles)
} else {
style = uiConfig.GetComposedStyle(config.STYLE_MSGLIST_DEFAULT, msg_styles)
@@ -265,7 +262,7 @@ func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row i
return false
}
-func (ml *MessageList) drawScrollbar(ctx *ui.Context, percentVisible float64) {
+func (ml *MessageList) drawScrollbar(ctx *ui.Context) {
gutterStyle := tcell.StyleDefault
pillStyle := tcell.StyleDefault.Reverse(true)
@@ -273,9 +270,8 @@ func (ml *MessageList) drawScrollbar(ctx *ui.Context, percentVisible float64) {
ctx.Fill(0, 0, 1, ctx.Height(), ' ', gutterStyle)
// pill
- pillSize := int(math.Ceil(float64(ctx.Height()) * percentVisible))
- percentScrolled := float64(ml.scroll) / float64(len(ml.Store().Uids()))
- pillOffset := int(math.Floor(float64(ctx.Height()) * percentScrolled))
+ pillSize := int(math.Ceil(float64(ctx.Height()) * ml.PercentVisible()))
+ pillOffset := int(math.Floor(float64(ctx.Height()) * ml.PercentScrolled()))
ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
}
@@ -328,7 +324,7 @@ func (ml *MessageList) Clicked(x, y int) (int, bool) {
if store == nil || ml.nmsgs == 0 || y >= ml.nmsgs {
return 0, false
}
- return y + ml.scroll, true
+ return y + ml.Scroll(), true
}
func (ml *MessageList) Height() int {
@@ -364,7 +360,7 @@ func (ml *MessageList) storeUpdate(store *lib.MessageStore) {
func (ml *MessageList) SetStore(store *lib.MessageStore) {
if ml.Store() != store {
- ml.scroll = 0
+ ml.Scrollable = Scrollable{}
}
ml.store = store
if store != nil {
@@ -402,39 +398,6 @@ func (ml *MessageList) Select(index int) {
ml.Invalidate()
}
-func (ml *MessageList) ensureScroll() {
- store := ml.Store()
- if store == nil || len(store.Uids()) == 0 {
- return
- }
-
- h := ml.Height()
-
- maxScroll := len(store.Uids()) - h
- if maxScroll < 0 {
- maxScroll = 0
- }
-
- selectedIndex := store.SelectedIndex()
-
- if selectedIndex >= ml.scroll && selectedIndex < ml.scroll+h {
- if ml.scroll > maxScroll {
- ml.scroll = maxScroll
- }
- return
- }
-
- if selectedIndex >= ml.scroll+h {
- ml.scroll = selectedIndex - h + 1
- } else if selectedIndex < ml.scroll {
- ml.scroll = selectedIndex
- }
-
- if ml.scroll > maxScroll {
- ml.scroll = maxScroll
- }
-}
-
func (ml *MessageList) drawEmptyMessage(ctx *ui.Context) {
uiConfig := ml.aerc.SelectedAccountUiConfig()
msg := uiConfig.EmptyMessage
diff --git a/widgets/scrollable.go b/widgets/scrollable.go
new file mode 100644
index 0000000..0e8bb5a
--- /dev/null
+++ b/widgets/scrollable.go
@@ -0,0 +1,68 @@
+package widgets
+
+// Scrollable implements vertical scrolling
+type Scrollable struct {
+ scroll int
+ height int
+ elems int
+}
+
+func (s *Scrollable) Scroll() int {
+ return s.scroll
+}
+
+func (s *Scrollable) PercentVisible() float64 {
+ if s.elems <= 0 {
+ return 1.0
+ }
+ return float64(s.height) / float64(s.elems)
+}
+
+func (s *Scrollable) PercentScrolled() float64 {
+ if s.elems <= 0 {
+ return 1.0
+ }
+ return float64(s.scroll) / float64(s.elems)
+}
+
+func (s *Scrollable) NeedScrollbar() bool {
+ needScrollbar := true
+ if s.PercentVisible() >= 1.0 {
+ needScrollbar = false
+ }
+ return needScrollbar
+}
+
+func (s *Scrollable) UpdateScroller(height, elems int) {
+ s.height = height
+ s.elems = elems
+}
+
+func (s *Scrollable) EnsureScroll(selectingIdx int) {
+ if selectingIdx < 0 {
+ return
+ }
+
+ maxScroll := s.elems - s.height
+ if maxScroll < 0 {
+ maxScroll = 0
+ }
+
+ if selectingIdx >= s.scroll && selectingIdx < s.scroll+s.height {
+ if s.scroll > maxScroll {
+ s.scroll = maxScroll
+ }
+ return
+ }
+
+ if selectingIdx >= s.scroll+s.height {
+ s.scroll = selectingIdx - s.height + 1
+ } else if selectingIdx < s.scroll {
+ s.scroll = selectingIdx
+ }
+
+ if s.scroll > maxScroll {
+ s.scroll = maxScroll
+ }
+
+}