styles renderer update

This commit is contained in:
Will McGugan
2022-06-30 10:54:37 +01:00
parent b2f0dbb8a2
commit 410fc91a0e
5 changed files with 51 additions and 20 deletions

View File

@@ -115,7 +115,7 @@ class BasicApp(App, css_path="basic.css"):
Static(Syntax(CODE, "python"), classes="code"), Static(Syntax(CODE, "python"), classes="code"),
classes="scrollable", classes="scrollable",
), ),
table, # table,
Error(), Error(),
Tweet(TweetBody(), classes="scrollbar-size-custom"), Tweet(TweetBody(), classes="scrollbar-size-custom"),
Warning(), Warning(),

View File

@@ -35,7 +35,7 @@ def line_crop(
for segment in iter_segments: for segment in iter_segments:
end_pos = pos + _cell_len(segment.text) end_pos = pos + _cell_len(segment.text)
if end_pos > start: if end_pos > start:
segment = segment.split_cells(start - pos)[-1] segment = segment.split_cells(start - pos)[1]
break break
pos = end_pos pos = end_pos
else: else:
@@ -86,5 +86,5 @@ def line_trim(segments: list[Segment], start: bool, end: bool) -> list[Segment]:
if last_segment.text: if last_segment.text:
segments[-1] = last_segment segments[-1] = last_segment
else: else:
segments.pop(-1) segments.pop()
return segments return segments

View File

@@ -11,11 +11,11 @@ from .css.types import EdgeType
from ._segment_tools import line_crop from ._segment_tools import line_crop
from ._types import Lines from ._types import Lines
from .geometry import Region, Size from .geometry import Region, Size
from .widget import Widget
if TYPE_CHECKING: if TYPE_CHECKING:
from .css.styles import RenderStyles from .css.styles import RenderStyles
from .widget import Widget
NORMALIZE_BORDER: dict[EdgeType, EdgeType] = {"none": "", "hidden": ""} NORMALIZE_BORDER: dict[EdgeType, EdgeType] = {"none": "", "hidden": ""}
@@ -27,8 +27,14 @@ class StylesRenderer:
self._cache: dict[int, list[Segment]] = {} self._cache: dict[int, list[Segment]] = {}
self._dirty_lines: set[int] = set() self._dirty_lines: set[int] = set()
def invalidate(self, region: Region) -> None: def set_dirty(self, *regions: Region) -> None:
self._dirty_lines.update(region.y_range) if regions:
for region in regions:
self._dirty_lines.update(region.y_range)
else:
self._dirty_lines.clear()
for y in self._widget.size.lines:
self._dirty_lines.add(y)
def render(self, region: Region) -> Lines: def render(self, region: Region) -> Lines:
@@ -50,12 +56,14 @@ class StylesRenderer:
width, height = size width, height = size
lines: Lines = [] lines: Lines = []
add_line = lines.append add_line = lines.append
simplify = Segment.simplify
is_dirty = self._dirty_lines.__contains__ is_dirty = self._dirty_lines.__contains__
render_line = self.render_line render_line = self.render_line
for y in region.y_range: for y in region.y_range:
if is_dirty(y) or y not in self._cache: if is_dirty(y) or y not in self._cache:
line = render_line(styles, y, size, base_background, background) line = render_line(styles, y, size, base_background, background)
line = list(simplify(line))
self._cache[y] = line self._cache[y] = line
else: else:
line = self._cache[y] line = self._cache[y]
@@ -64,13 +72,13 @@ class StylesRenderer:
if region.x_extents != (0, width): if region.x_extents != (0, width):
_line_crop = line_crop _line_crop = line_crop
x1, x2 = region.x_range x1, x2 = region.x_extents
lines = [_line_crop(line, x1, x2, width) for line in lines] lines = [_line_crop(line, x1, x2, width) for line in lines]
return lines return lines
def render_content_line(self, y: int, width: int) -> list[Segment]: def render_content_line(self, y: int) -> list[Segment]:
return [Segment((str(y) * width)[:width])] return self._widget.render_line(y)
def render_line( def render_line(
self, self,
@@ -149,11 +157,9 @@ class StylesRenderer:
return [Segment(" " * width, background_style)] return [Segment(" " * width, background_style)]
# Apply background style # Apply background style
line = list( line = self.render_content_line(y - gutter.top)
Segment.apply_style( if inner_style:
self.render_content_line(y - gutter.top, content_width), inner_style line = list(Segment.apply_style(line, inner_style))
)
)
# Add padding # Add padding
if pad_left and pad_right: if pad_left and pad_right:
@@ -184,7 +190,7 @@ class StylesRenderer:
from_color(border_left_color.rich_color), from_color(border_left_color.rich_color),
) )
_, (_, _, right), _ = get_box( _, (_, _, right), _ = get_box(
border_left, border_right,
inner_style, inner_style,
outer_style, outer_style,
from_color(border_right_color.rich_color), from_color(border_right_color.rich_color),
@@ -195,7 +201,7 @@ class StylesRenderer:
elif border_left: elif border_left:
return [left, *line] return [left, *line]
return [right, *line] return [*line, right]
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -139,6 +139,10 @@ class Size(NamedTuple):
width, height = self width, height = self
return Region(0, 0, width, height) return Region(0, 0, width, height)
@property
def lines(self) -> list[int]:
return list(range(self.height))
def __add__(self, other: object) -> Size: def __add__(self, other: object) -> Size:
if isinstance(other, tuple): if isinstance(other, tuple):
width, height = self width, height = self

View File

@@ -17,6 +17,7 @@ from rich.align import Align
from rich.console import Console, RenderableType from rich.console import Console, RenderableType
from rich.measure import Measurement from rich.measure import Measurement
from rich.padding import Padding from rich.padding import Padding
from rich.segment import Segment
from rich.style import Style from rich.style import Style
from . import errors from . import errors
@@ -25,6 +26,7 @@ from ._animator import BoundAnimator
from ._border import Border from ._border import Border
from .box_model import BoxModel, get_box_model from .box_model import BoxModel, get_box_model
from ._context import active_app from ._context import active_app
from ._styles_render import StylesRenderer
from ._types import Lines from ._types import Lines
from .dom import DOMNode from .dom import DOMNode
from ._layout import ArrangeResult from ._layout import ArrangeResult
@@ -118,6 +120,8 @@ class Widget(DOMNode):
self._arrangement: ArrangeResult | None = None self._arrangement: ArrangeResult | None = None
self._arrangement_cache_key: tuple[int, Size] = (-1, Size()) self._arrangement_cache_key: tuple[int, Size] = (-1, Size())
self._styles_renderer = StylesRenderer(self)
super().__init__(name=name, id=id, classes=classes) super().__init__(name=name, id=id, classes=classes)
self.add_children(*children) self.add_children(*children)
@@ -833,9 +837,18 @@ class Widget(DOMNode):
""" """
renderable = self.render() renderable = self.render()
renderable = self._style_renderable(renderable) styles = self.styles
content_align = (styles.content_align_horizontal, styles.content_align_vertical)
if content_align != ("left", "top"):
horizontal, vertical = content_align
renderable = Align(renderable, horizontal, vertical=vertical)
return renderable return renderable
@property
def content_size(self) -> Size:
return self._size - self.styles.gutter.totals
@property @property
def size(self) -> Size: def size(self) -> Size:
return self._size return self._size
@@ -955,13 +968,13 @@ class Widget(DOMNode):
def _render_lines(self) -> None: def _render_lines(self) -> None:
"""Render all lines.""" """Render all lines."""
width, height = self.size width, height = self.content_size
renderable = self.render_styled() renderable = self.render_styled()
options = self.console.options.update_dimensions(width, height).update( options = self.console.options.update_dimensions(width, height).update(
highlight=False highlight=False
) )
lines = self.console.render_lines(renderable, options) lines = self.console.render_lines(renderable, options, style=self.rich_style)
self._render_cache = RenderCache(self.size, lines) self._render_cache = RenderCache(self.content_size, lines)
self._dirty_regions.clear() self._dirty_regions.clear()
def _crop_lines(self, lines: Lines, x1, x2) -> Lines: def _crop_lines(self, lines: Lines, x1, x2) -> Lines:
@@ -971,6 +984,10 @@ class Widget(DOMNode):
lines = [_line_crop(line, x1, x2, width) for line in lines] lines = [_line_crop(line, x1, x2, width) for line in lines]
return lines return lines
def render_line(self, y) -> list[Segment]:
line = self._render_cache.lines[y]
return line
def render_lines(self, crop: Region) -> Lines: def render_lines(self, crop: Region) -> Lines:
"""Render the widget in to lines. """Render the widget in to lines.
@@ -981,8 +998,12 @@ class Widget(DOMNode):
Lines: A list of list of segments Lines: A list of list of segments
""" """
if self._dirty_regions: if self._dirty_regions:
self._styles_renderer.set_dirty(*self._dirty_regions)
self._render_lines() self._render_lines()
lines = self._styles_renderer.render(crop)
return lines
x1, y1, x2, y2 = crop.corners x1, y1, x2, y2 = crop.corners
lines = self._render_cache.lines[y1:y2] lines = self._render_cache.lines[y1:y2]
lines = self._crop_lines(lines, x1, x2) lines = self._crop_lines(lines, x1, x2)