aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2023-10-02 16:29:15 +0200
committerRobin Jarry <robin@jarry.cc>2023-10-13 00:31:57 +0200
commit14baf17f4bd47377403c5d965c1fe294d2ec6ef0 (patch)
tree57a56dd2ce7e0e67093af0ec3912e74aed0cd3f8
parent3b1bc60f4e8f11c3244097c714db15bbf34d806e (diff)
downloadaerc-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.go3
-rw-r--r--app/partswitcher.go171
-rw-r--r--config/aerc.conf8
-rw-r--r--config/viewer.go1
-rw-r--r--doc/aerc-config.5.scd8
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.