diff --git a/cheuph/render/element.py b/cheuph/render/element.py index d55fd85..acaccdd 100644 --- a/cheuph/render/element.py +++ b/cheuph/render/element.py @@ -40,9 +40,10 @@ class Element(abc.ABC): @abc.abstractmethod def render(self, + width: int, depth: int, - highlighted: bool, - folded: bool, + highlighted: bool = False, + folded: bool = False, ) -> RenderedElement: pass @@ -153,6 +154,10 @@ class RenderedElement: def element(self) -> Element: return self._element + @property + def lines(self) -> List[AttributedText]: + return self._lines + @property def height(self) -> int: return len(self._lines) diff --git a/cheuph/render/tree_display.py b/cheuph/render/tree_display.py index 4cce15b..e452f4d 100644 --- a/cheuph/render/tree_display.py +++ b/cheuph/render/tree_display.py @@ -274,8 +274,27 @@ class TreeDisplay: It should be called just before drawing the display lines to the curses window. """ + filler_line = AttributedText(" " * self.width) - pass # TODO + if self._rendered is None: + self._display_lines = [filler_line] * self.height + return + + lines = [] + + if self._rendered.upper_offset > 0: + # Fill the screen with empty lines until we hit the actual messages + lines.extend([filler_line] * self._rendered.upper_offset) + + rendered_lines = self._rendered.to_lines(start=0, stop=self.height-1) + lines.extend(line for line, rendered in rendered_lines) + + if self._rendered.lower_offset < self.height - 1: + # Fill the rest of the screen with empty lines + lines_left = self.height - 1 - self._rendered.lower_offset + lines.extend([filler_line] * lines_left) + + self._display_lines = lines # SCROLLING diff --git a/cheuph/render/tree_list.py b/cheuph/render/tree_list.py index ed9f06f..0c501cb 100644 --- a/cheuph/render/tree_list.py +++ b/cheuph/render/tree_list.py @@ -1,25 +1,36 @@ import collections -from typing import Deque, List +from typing import Deque, List, Optional, Tuple from .element import Id, RenderedElement +from .markup import AttributedText __all__ = ["TreeList"] class TreeList: + """ + This class is the stage between tree-like Element structures and lines of + text like the TreeDisplay's DisplayLines. + + It keeps track of the results of rendering Element trees, and also the top + and bottom tree's ids, so the TreeList can be expanded easily by appending + trees to the top and bottom. + + Despite its name, the "trees" it stores are just flat lists, and they're + stored in a flat deque one message at a time. Its name comes from how i is + used with rendered Element trees. + """ + def __init__(self, tree: List[RenderedElement], anchor_id: Id, ) -> None: - self._deque: Deque = collections.deque() + self._deque: Deque[RenderedElement] = collections.deque() # The offsets can be thought of as the index of a line relative to the # anchor's first line. # # The upper offset is the index of the uppermost message's first line. - # upper_offset <= 0. - # # The lower offset is the index of the lowermost message's LAST line. - # lower_offset >= 0. self._upper_offset: int self._lower_offset: int @@ -47,9 +58,14 @@ class TreeList: def lower_tree_id(self) -> Id: return self._lower_tree_id - def offset_by(self, offset: int) -> None: - self._upper_offset += offset - self._lower_offset += offset + def offset_by(self, delta: int) -> None: + """ + Change all the TreeList's offsets by a delta (which is added to each + offset). + """ + + self._upper_offset += delta + self._lower_offset += delta def _add_first_tree(self, tree: List[RenderedElement], @@ -81,6 +97,10 @@ class TreeList: self._lower_offset = offset - 1 def add_above(self, tree: List[RenderedElement]) -> None: + """ + Add a rendered tree above all current trees. + """ + if len(tree) == 0: raise ValueError("The tree must contain at least one element") @@ -96,6 +116,10 @@ class TreeList: #self._deque.extendLeft(reversed(tree)) def add_below(self, tree: List[RenderedElement]) -> None: + """ + Add a rendered tree below all current trees. + """ + if len(tree) == 0: raise ValueError("The tree must contain at least one element") @@ -109,3 +133,32 @@ class TreeList: #delta = sum(map(lambda r: r.height, tree)) #self._lower_offset += delta #self._deque.extend(tree) + + def to_lines(self, + start: Optional[int] = None, + stop: Optional[int] = None, + ) -> List[Tuple[AttributedText, RenderedElement]]: + + offset = self.upper_offset + lines: List[Tuple[AttributedText, RenderedElement]] = [] + + # I'm creating this generator instead of using two nested for loops + # below, because I want to be able to break out of the for loop without + # the code getting too ugly, and because it's fun :) + all_lines = ((line, rendered) + for rendered in self._deque + for line in rendered.lines) + + for line, rendered in all_lines: + after_start = start is not None and offset >= start + before_stop = stop is not None and offset <= stop + + if after_start and before_stop: + lines.append((line, rendered)) + + if not before_stop: + break + + offset += 1 + + return lines