diff options
author | Koni Marti <koni.marti@gmail.com> | 2023-10-02 16:29:15 +0200 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2023-10-13 00:31:57 +0200 |
commit | 14baf17f4bd47377403c5d965c1fe294d2ec6ef0 (patch) | |
tree | 57a56dd2ce7e0e67093af0ec3912e74aed0cd3f8 | |
parent | 3b1bc60f4e8f11c3244097c714db15bbf34d806e (diff) | |
download | aerc-14baf17f4bd47377403c5d965c1fe294d2ec6ef0.tar.gz |
switcher: add scrollbar
Add scrollbar to part switcher. Add config value "max-mime-height" to
the [Viewer] section to set the maximum height before a scrollbar is
drawn. The part switcher height is restricted to half of the context
height. Update docs.
Fixes: https://todo.sr.ht/~rjarry/aerc/194
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Inwit <inwit@sindominio.net>
Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r-- | app/msgviewer.go | 3 | ||||
-rw-r--r-- | app/partswitcher.go | 171 | ||||
-rw-r--r-- | config/aerc.conf | 8 | ||||
-rw-r--r-- | config/viewer.go | 1 | ||||
-rw-r--r-- | doc/aerc-config.5.scd | 8 |
5 files changed, 118 insertions, 73 deletions
diff --git a/app/msgviewer.go b/app/msgviewer.go index 2189779a..bb63b28d 100644 --- a/app/msgviewer.go +++ b/app/msgviewer.go @@ -139,7 +139,7 @@ func NewMessageViewer( switcher: switcher, uiConfig: acct.UiConfig(), } - switcher.mv = mv + switcher.uiConfig = mv.uiConfig return mv } @@ -214,7 +214,6 @@ func createSwitcher( ) error { var err error switcher.selected = -1 - switcher.alwaysShowMime = config.Viewer.AlwaysShowMime if msg.MessageInfo().Error != nil { return fmt.Errorf("could not view message: %w", msg.MessageInfo().Error) diff --git a/app/partswitcher.go b/app/partswitcher.go index fa2f56b2..3f070c44 100644 --- a/app/partswitcher.go +++ b/app/partswitcher.go @@ -1,6 +1,8 @@ package app import ( + "math" + "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/lib/ui" "github.com/gdamore/tcell/v2" @@ -8,12 +10,14 @@ import ( ) type PartSwitcher struct { - parts []*PartViewer - selected int - alwaysShowMime bool + Scrollable + parts []*PartViewer + selected int height int - mv *MessageViewer + offset int + + uiConfig *config.UIConfig } func (ps *PartSwitcher) PreviousPart() { @@ -80,94 +84,119 @@ func (ps *PartSwitcher) Event(event tcell.Event) bool { } func (ps *PartSwitcher) Draw(ctx *ui.Context) { - height := len(ps.parts) - if height == 1 && !config.Viewer.AlwaysShowMime { + uiConfig := ps.uiConfig + n := len(ps.parts) + if n == 1 && !config.Viewer.AlwaysShowMime { ps.parts[ps.selected].Draw(ctx) return } + ps.height = config.Viewer.MaxMimeHeight + if ps.height <= 0 || n < ps.height { + ps.height = n + } + if ps.height > ctx.Height()/2 { + ps.height = ctx.Height() / 2 + } + + ps.UpdateScroller(ps.height, n) + ps.EnsureScroll(ps.selected) + var styleSwitcher, styleFile, styleMime tcell.Style - // TODO: cap height and add scrolling for messages with many parts - ps.height = ctx.Height() - y := ctx.Height() - height - for i, part := range ps.parts { + scrollbarWidth := 0 + if ps.NeedScrollbar() { + scrollbarWidth = 1 + } + + ps.offset = ctx.Height() - ps.height + y := ps.offset + row := ps.offset + ctx.Fill(0, y, ctx.Width(), ps.height, ' ', uiConfig.GetStyle(config.STYLE_PART_SWITCHER)) + for i := ps.Scroll(); i < n; i++ { + part := ps.parts[i] if ps.selected == i { - styleSwitcher = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_SWITCHER) - styleFile = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_FILENAME) - styleMime = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_MIMETYPE) + styleSwitcher = uiConfig.GetStyleSelected(config.STYLE_PART_SWITCHER) + styleFile = uiConfig.GetStyleSelected(config.STYLE_PART_FILENAME) + styleMime = uiConfig.GetStyleSelected(config.STYLE_PART_MIMETYPE) } else { - styleSwitcher = ps.mv.uiConfig.GetStyle(config.STYLE_PART_SWITCHER) - styleFile = ps.mv.uiConfig.GetStyle(config.STYLE_PART_FILENAME) - styleMime = ps.mv.uiConfig.GetStyle(config.STYLE_PART_MIMETYPE) + styleSwitcher = uiConfig.GetStyle(config.STYLE_PART_SWITCHER) + styleFile = uiConfig.GetStyle(config.STYLE_PART_FILENAME) + styleMime = uiConfig.GetStyle(config.STYLE_PART_MIMETYPE) } - ctx.Fill(0, y+i, ctx.Width(), 1, ' ', styleSwitcher) + ctx.Fill(0, row, ctx.Width(), 1, ' ', styleSwitcher) left := len(part.index) * 2 if part.part.FileName() != "" { name := runewidth.Truncate(part.part.FileName(), ctx.Width()-left-1, "…") - left += ctx.Printf(left, y+i, styleFile, "%s ", name) + left += ctx.Printf(left, row, styleFile, "%s ", name) } t := "(" + part.part.FullMIMEType() + ")" - t = runewidth.Truncate(t, ctx.Width()-left, "…") - ctx.Printf(left, y+i, styleMime, "%s", t) + t = runewidth.Truncate(t, ctx.Width()-left-scrollbarWidth, "…") + ctx.Printf(left, row, styleMime, "%s", t) + row++ + + if (i - ps.Scroll()) >= ps.height { + break + } + } + if ps.NeedScrollbar() { + ps.drawScrollbar(ctx.Subcontext(ctx.Width()-1, y, 1, ps.height)) } ps.parts[ps.selected].Draw(ctx.Subcontext( - 0, 0, ctx.Width(), ctx.Height()-height)) + 0, 0, ctx.Width(), ctx.Height()-ps.height)) +} + +func (ps *PartSwitcher) drawScrollbar(ctx *ui.Context) { + uiConfig := ps.uiConfig + gutterStyle := uiConfig.GetStyle(config.STYLE_MSGLIST_GUTTER) + pillStyle := uiConfig.GetStyle(config.STYLE_MSGLIST_PILL) + + // gutter + ctx.Fill(0, 0, 1, ctx.Height(), ' ', gutterStyle) + + // pill + pillSize := int(math.Ceil(float64(ctx.Height()) * ps.PercentVisible())) + pillOffset := int(math.Floor(float64(ctx.Height()) * ps.PercentScrolled())) + ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle) } func (ps *PartSwitcher) MouseEvent(localX int, localY int, event tcell.Event) { - if event, ok := event.(*tcell.EventMouse); ok { - switch event.Buttons() { - case tcell.Button1: - height := len(ps.parts) - y := ps.height - height - if localY < y && ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.MouseEvent(localX, localY, event) - } - for i := range ps.parts { - if localY != y+i { - continue - } - if ps.parts[i].part.MIMEType == "multipart" { - continue - } - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(false) - } - ps.selected = i - ps.Invalidate() - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(true) - } - } - case tcell.WheelDown: - height := len(ps.parts) - y := ps.height - height - if localY < y && ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.MouseEvent(localX, localY, event) - } - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(false) - } - ps.mv.NextPart() - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(true) - } - case tcell.WheelUp: - height := len(ps.parts) - y := ps.height - height - if localY < y && ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.MouseEvent(localX, localY, event) - } - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(false) - } - ps.mv.PreviousPart() - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(true) - } + if localY < ps.offset && ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.MouseEvent(localX, localY, event) + return + } + + e, ok := event.(*tcell.EventMouse) + if !ok { + return + } + + if ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.Focus(false) + } + + switch e.Buttons() { + case tcell.Button1: + i := localY - ps.offset + ps.Scroll() + if i < 0 || i >= len(ps.parts) { + break } + if ps.parts[i].part.MIMEType == "multipart" { + break + } + ps.selected = i + ps.Invalidate() + case tcell.WheelDown: + ps.NextPart() + ps.Invalidate() + case tcell.WheelUp: + ps.PreviousPart() + ps.Invalidate() + } + + if ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.Focus(true) } } diff --git a/config/aerc.conf b/config/aerc.conf index 4b4e7cb6..18c6decc 100644 --- a/config/aerc.conf +++ b/config/aerc.conf @@ -377,6 +377,14 @@ # Default: false #always-show-mime=false +# Define the maximum height of the mimetype switcher before a scrollbar is used. +# The height of the mimetype switcher is restricted to half of the display +# height. If the provided value for the height is zero, the number of parts will +# be used as the height of the type switcher. +# +# Default: 0 +#max-mime-height = 0 + # Parses and extracts http links when viewing a message. Links can then be # accessed with the open-link command. # diff --git a/config/viewer.go b/config/viewer.go index 8ebe82a5..6d8a9957 100644 --- a/config/viewer.go +++ b/config/viewer.go @@ -10,6 +10,7 @@ type ViewerConfig struct { Alternatives []string `ini:"alternatives" default:"text/plain,text/html" delim:","` ShowHeaders bool `ini:"show-headers"` AlwaysShowMime bool `ini:"always-show-mime"` + MaxMimeHeight int `ini:"max-mime-height" default:"0"` ParseHttpLinks bool `ini:"parse-http-links" default:"true"` HeaderLayout [][]string `ini:"header-layout" parse:"ParseLayout" default:"From|To,Cc|Bcc,Date,Subject"` KeyPassthrough bool diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index b6855c7a..0e86e56b 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -561,6 +561,14 @@ These options are configured in the *[viewer]* section of _aerc.conf_. Default: _false_ +*max-mime-height* = _height_ + Define the maximum height of the mimetype switcher before a scrollbar is + used. The height of the mimetype switcher is restricted to half of the display height. + If the provided value for the height is zero, the number of parts will + be used as the height of the type switcher. + + Default: 0 + *parse-http-links* = _true_|_false_ Parses and extracts http links when viewing a message. Links can then be accessed with the *open-link* command. |