summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2021-05-18 13:35:59 +0200
committerAnton Khirnov <anton@khirnov.net>2021-05-18 13:35:59 +0200
commit6edfc5825c44d59a50f6e59303a68cf357b18d91 (patch)
tree660958952a3c3ce4b1d79c8a3150523e6c63a2e8
parentebae071055ec524e3bc947c49a2f843c5be68d73 (diff)
widgets/thread: improve thread node decoration rendering
Implement configurable message summary indentation, fix appearance when the message summary spans more than one line.
-rw-r--r--alot/widgets/thread.py106
1 files changed, 86 insertions, 20 deletions
diff --git a/alot/widgets/thread.py b/alot/widgets/thread.py
index 111c8569..c89f175d 100644
--- a/alot/widgets/thread.py
+++ b/alot/widgets/thread.py
@@ -7,6 +7,8 @@ Widgets specific to thread mode
import logging
import re
import urwid
+from urwid.canvas import SolidCanvas, TextCanvas
+from urwid.util import apply_target_encoding
try:
import pygments.lexers, pygments.formatters, pygments.util
@@ -758,15 +760,89 @@ class MessageWidget(urwid.WidgetWrap):
return self._attach_wgt.focus.attachment
return None
-class ThreadNode(urwid.WidgetWrap):
+class _ThreadNodeDecoration(urwid.Widget):
+ """
+ Decoration indicating thread structure in rows corresponding to a single
+ message. This is a box widget that is rendered alongside the message summary
+ widget and is sized by their common container to have a matching height.
+
+ The decoration consists of a list of blocks, where nth entry decorates the
+ structure at the nth depth level. Each block is rendered with the same
+ 'indent' width.
+ The last block is treated specially, since its first row contains the arrow
+ pointing to the message itself.
+ """
_ARROW = '➤'
_HBAR = '─'
_VBAR = '│'
_TNODE = '├'
_CORNER = '└'
- _decor_text = None
- _have_next_sibling = None
+ _sizing = frozenset(['box'])
+
+ # depth of the correponding message in the thread
+ _depth = None
+
+ # decorations for all the blocks before the last one
+ _deco_leading = None
+ # decoration for the first row of the last block
+ _deco_last_row0 = None
+ # decoration for the other rows of the last block
+ _deco_last = None
+
+ def __init__(self, have_next_sibling):
+ self._depth = len(have_next_sibling) - 1
+
+ deco_leading = []
+ for d in range(self._depth - 1):
+ deco_leading.append(self._VBAR if have_next_sibling[d + 1] else '')
+ self._deco_leading = deco_leading
+
+ if self._depth > 0:
+ if have_next_sibling[self._depth]:
+ self._deco_last_row0 = self._TNODE
+ self._deco_last = self._VBAR
+ else:
+ self._deco_last_row0 = self._CORNER
+ self._deco_last = ''
+
+ def render(self, size, focus = False):
+ cols, rows = size
+ indent = cols // self._depth
+
+ if indent < 1:
+ return SolidCanvas(' ', cols, rows)
+
+ # pad each leading block with spaces according to indent
+ blocks_leading = []
+ for d in self._deco_leading:
+ pad = max(indent - len(d), 0)
+ blocks_leading.append(d + ' ' * pad)
+
+ # build the arrow pointing to the message in the first row
+ # of the last block
+ pad_last0 = max(indent - len(self._deco_last_row0), 0)
+ block_last0 = self._deco_last_row0 + self._HBAR * (pad_last0 - 1) + self._ARROW * (pad_last0 > 0)
+ lines = [''.join(blocks_leading + [block_last0])]
+
+ pad_last = max(indent - len(self._deco_last), 0)
+ block_last = self._deco_last + ' ' * pad_last
+ lines.extend([''.join(blocks_leading + [block_last])] * (rows - 1))
+
+ text, cs = list(zip(*(apply_target_encoding(l) for l in lines)))
+
+ return TextCanvas(text, cs = cs)
+
+class ThreadNode(urwid.WidgetWrap):
+ """
+ A node in the thread tree.
+
+ It is a flow widget that consists of 2 columns:
+ - (optional) decoration indicating the thread structure
+ - message summary
+ """
+ _decor = None
+ _msg_depth = None
def __init__(self, msg, thread, pos, indent):
parity = 'odd' if pos & 1 else 'even'
@@ -778,9 +854,6 @@ class ThreadNode(urwid.WidgetWrap):
if msg.depth == 0:
wgt = msg_summary
else:
- self._decor_text = urwid.Text('')
- wgt = urwid.Columns([('pack', self._decor_text), msg_summary])
-
ancestor_chain = [msg]
for p in msg.parents():
ancestor_chain.insert(0, p)
@@ -792,25 +865,18 @@ class ThreadNode(urwid.WidgetWrap):
else:
have_sibling.append(m != ancestor_chain[d - 1].replies[-1])
- self._have_next_sibling = have_sibling
+ self._decor = _ThreadNodeDecoration(have_sibling)
+ wgt = urwid.Columns([(indent * msg.depth, self._decor), msg_summary],
+ box_columns = [0])
super().__init__(wgt)
+ self._msg_depth = msg.depth
+
self.set_indent(indent)
def set_indent(self, indent):
- if self._decor_text is None:
+ if self._decor is None:
return
- seg_text = []
- if indent > 0:
- depth = len(self._have_next_sibling) - 1
- for d in range(depth):
- if d == depth - 1:
- corner = self._TNODE if self._have_next_sibling[d + 1] else self._CORNER
- seg_text.append(corner + self._ARROW)
- else:
- bar = self._VBAR if self._have_next_sibling[d + 1] else ' '
- seg_text.append(bar + ' ')
-
- self._decor_text.set_text(''.join(seg_text))
+ self._w.contents[0] = (self._decor, ('given', self._msg_depth * indent, True))