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"),
|
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(),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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__":
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user