mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
styles renderer update
This commit is contained in:
@@ -115,7 +115,7 @@ class BasicApp(App, css_path="basic.css"):
|
||||
Static(Syntax(CODE, "python"), classes="code"),
|
||||
classes="scrollable",
|
||||
),
|
||||
table,
|
||||
# table,
|
||||
Error(),
|
||||
Tweet(TweetBody(), classes="scrollbar-size-custom"),
|
||||
Warning(),
|
||||
|
||||
@@ -35,7 +35,7 @@ def line_crop(
|
||||
for segment in iter_segments:
|
||||
end_pos = pos + _cell_len(segment.text)
|
||||
if end_pos > start:
|
||||
segment = segment.split_cells(start - pos)[-1]
|
||||
segment = segment.split_cells(start - pos)[1]
|
||||
break
|
||||
pos = end_pos
|
||||
else:
|
||||
@@ -86,5 +86,5 @@ def line_trim(segments: list[Segment], start: bool, end: bool) -> list[Segment]:
|
||||
if last_segment.text:
|
||||
segments[-1] = last_segment
|
||||
else:
|
||||
segments.pop(-1)
|
||||
segments.pop()
|
||||
return segments
|
||||
|
||||
@@ -11,11 +11,11 @@ from .css.types import EdgeType
|
||||
from ._segment_tools import line_crop
|
||||
from ._types import Lines
|
||||
from .geometry import Region, Size
|
||||
from .widget import Widget
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .css.styles import RenderStyles
|
||||
from .widget import Widget
|
||||
|
||||
|
||||
NORMALIZE_BORDER: dict[EdgeType, EdgeType] = {"none": "", "hidden": ""}
|
||||
@@ -27,8 +27,14 @@ class StylesRenderer:
|
||||
self._cache: dict[int, list[Segment]] = {}
|
||||
self._dirty_lines: set[int] = set()
|
||||
|
||||
def invalidate(self, region: Region) -> None:
|
||||
self._dirty_lines.update(region.y_range)
|
||||
def set_dirty(self, *regions: Region) -> None:
|
||||
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:
|
||||
|
||||
@@ -50,12 +56,14 @@ class StylesRenderer:
|
||||
width, height = size
|
||||
lines: Lines = []
|
||||
add_line = lines.append
|
||||
simplify = Segment.simplify
|
||||
|
||||
is_dirty = self._dirty_lines.__contains__
|
||||
render_line = self.render_line
|
||||
for y in region.y_range:
|
||||
if is_dirty(y) or y not in self._cache:
|
||||
line = render_line(styles, y, size, base_background, background)
|
||||
line = list(simplify(line))
|
||||
self._cache[y] = line
|
||||
else:
|
||||
line = self._cache[y]
|
||||
@@ -64,13 +72,13 @@ class StylesRenderer:
|
||||
|
||||
if region.x_extents != (0, width):
|
||||
_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]
|
||||
|
||||
return lines
|
||||
|
||||
def render_content_line(self, y: int, width: int) -> list[Segment]:
|
||||
return [Segment((str(y) * width)[:width])]
|
||||
def render_content_line(self, y: int) -> list[Segment]:
|
||||
return self._widget.render_line(y)
|
||||
|
||||
def render_line(
|
||||
self,
|
||||
@@ -149,11 +157,9 @@ class StylesRenderer:
|
||||
return [Segment(" " * width, background_style)]
|
||||
|
||||
# Apply background style
|
||||
line = list(
|
||||
Segment.apply_style(
|
||||
self.render_content_line(y - gutter.top, content_width), inner_style
|
||||
)
|
||||
)
|
||||
line = self.render_content_line(y - gutter.top)
|
||||
if inner_style:
|
||||
line = list(Segment.apply_style(line, inner_style))
|
||||
|
||||
# Add padding
|
||||
if pad_left and pad_right:
|
||||
@@ -184,7 +190,7 @@ class StylesRenderer:
|
||||
from_color(border_left_color.rich_color),
|
||||
)
|
||||
_, (_, _, right), _ = get_box(
|
||||
border_left,
|
||||
border_right,
|
||||
inner_style,
|
||||
outer_style,
|
||||
from_color(border_right_color.rich_color),
|
||||
@@ -195,7 +201,7 @@ class StylesRenderer:
|
||||
elif border_left:
|
||||
return [left, *line]
|
||||
|
||||
return [right, *line]
|
||||
return [*line, right]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -139,6 +139,10 @@ class Size(NamedTuple):
|
||||
width, height = self
|
||||
return Region(0, 0, width, height)
|
||||
|
||||
@property
|
||||
def lines(self) -> list[int]:
|
||||
return list(range(self.height))
|
||||
|
||||
def __add__(self, other: object) -> Size:
|
||||
if isinstance(other, tuple):
|
||||
width, height = self
|
||||
|
||||
@@ -17,6 +17,7 @@ from rich.align import Align
|
||||
from rich.console import Console, RenderableType
|
||||
from rich.measure import Measurement
|
||||
from rich.padding import Padding
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from . import errors
|
||||
@@ -25,6 +26,7 @@ from ._animator import BoundAnimator
|
||||
from ._border import Border
|
||||
from .box_model import BoxModel, get_box_model
|
||||
from ._context import active_app
|
||||
from ._styles_render import StylesRenderer
|
||||
from ._types import Lines
|
||||
from .dom import DOMNode
|
||||
from ._layout import ArrangeResult
|
||||
@@ -118,6 +120,8 @@ class Widget(DOMNode):
|
||||
self._arrangement: ArrangeResult | None = None
|
||||
self._arrangement_cache_key: tuple[int, Size] = (-1, Size())
|
||||
|
||||
self._styles_renderer = StylesRenderer(self)
|
||||
|
||||
super().__init__(name=name, id=id, classes=classes)
|
||||
self.add_children(*children)
|
||||
|
||||
@@ -833,9 +837,18 @@ class Widget(DOMNode):
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
@property
|
||||
def content_size(self) -> Size:
|
||||
return self._size - self.styles.gutter.totals
|
||||
|
||||
@property
|
||||
def size(self) -> Size:
|
||||
return self._size
|
||||
@@ -955,13 +968,13 @@ class Widget(DOMNode):
|
||||
|
||||
def _render_lines(self) -> None:
|
||||
"""Render all lines."""
|
||||
width, height = self.size
|
||||
width, height = self.content_size
|
||||
renderable = self.render_styled()
|
||||
options = self.console.options.update_dimensions(width, height).update(
|
||||
highlight=False
|
||||
)
|
||||
lines = self.console.render_lines(renderable, options)
|
||||
self._render_cache = RenderCache(self.size, lines)
|
||||
lines = self.console.render_lines(renderable, options, style=self.rich_style)
|
||||
self._render_cache = RenderCache(self.content_size, lines)
|
||||
self._dirty_regions.clear()
|
||||
|
||||
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]
|
||||
return lines
|
||||
|
||||
def render_line(self, y) -> list[Segment]:
|
||||
line = self._render_cache.lines[y]
|
||||
return line
|
||||
|
||||
def render_lines(self, crop: Region) -> Lines:
|
||||
"""Render the widget in to lines.
|
||||
|
||||
@@ -981,8 +998,12 @@ class Widget(DOMNode):
|
||||
Lines: A list of list of segments
|
||||
"""
|
||||
if self._dirty_regions:
|
||||
self._styles_renderer.set_dirty(*self._dirty_regions)
|
||||
self._render_lines()
|
||||
|
||||
lines = self._styles_renderer.render(crop)
|
||||
return lines
|
||||
|
||||
x1, y1, x2, y2 = crop.corners
|
||||
lines = self._render_cache.lines[y1:y2]
|
||||
lines = self._crop_lines(lines, x1, x2)
|
||||
|
||||
Reference in New Issue
Block a user