diff options
author | Koni Marti <koni.marti@gmail.com> | 2022-02-25 17:53:33 +0100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2022-03-03 21:11:05 +0100 |
commit | 515a8b56f6e9b4e6efaf6a6a29c851dadf4b4a56 (patch) | |
tree | 773eb7af099ea985087d76db41c40d36f81cc153 /widgets | |
parent | bd65ce1010a78eec38ba9c70d9fc23f85cd087a1 (diff) | |
download | aerc-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>
Diffstat (limited to 'widgets')
-rw-r--r-- | widgets/dirlist.go | 60 | ||||
-rw-r--r-- | widgets/dirtree.go | 45 | ||||
-rw-r--r-- | widgets/msglist.go | 67 | ||||
-rw-r--r-- | widgets/scrollable.go | 68 |
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 + } + +} |