mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
adds Strip primitive
This commit is contained in:
@@ -26,10 +26,12 @@ from rich.style import Style
|
||||
from . import errors
|
||||
from ._cells import cell_len
|
||||
from ._loop import loop_last
|
||||
from ._types import Lines
|
||||
from .strip import Strip
|
||||
from ._types import Strips
|
||||
from ._typing import TypeAlias
|
||||
from .geometry import NULL_OFFSET, Offset, Region, Size
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .widget import Widget
|
||||
|
||||
@@ -66,8 +68,8 @@ CompositorMap: TypeAlias = "dict[Widget, MapGeometry]"
|
||||
class LayoutUpdate:
|
||||
"""A renderable containing the result of a render for a given region."""
|
||||
|
||||
def __init__(self, lines: Lines, region: Region) -> None:
|
||||
self.lines = lines
|
||||
def __init__(self, strips: Strips, region: Region) -> None:
|
||||
self.strips = strips
|
||||
self.region = region
|
||||
|
||||
def __rich_console__(
|
||||
@@ -76,7 +78,7 @@ class LayoutUpdate:
|
||||
x = self.region.x
|
||||
new_line = Segment.line()
|
||||
move_to = Control.move_to
|
||||
for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)):
|
||||
for last, (y, line) in loop_last(enumerate(self.strips, self.region.y)):
|
||||
yield move_to(x, y)
|
||||
yield from line
|
||||
if not last:
|
||||
@@ -92,7 +94,7 @@ class ChopsUpdate:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chops: list[dict[int, list[Segment] | None]],
|
||||
chops: list[dict[int, Strip | None]],
|
||||
spans: list[tuple[int, int, int]],
|
||||
chop_ends: list[list[int]],
|
||||
) -> None:
|
||||
@@ -121,9 +123,9 @@ class ChopsUpdate:
|
||||
for y, x1, x2 in self.spans:
|
||||
line = chops[y]
|
||||
ends = chop_ends[y]
|
||||
for end, (x, segments) in zip(ends, line.items()):
|
||||
for end, (x, strip) in zip(ends, line.items()):
|
||||
# TODO: crop to x extents
|
||||
if segments is None:
|
||||
if strip is None:
|
||||
continue
|
||||
|
||||
if x > x2 or end <= x1:
|
||||
@@ -131,10 +133,10 @@ class ChopsUpdate:
|
||||
|
||||
if x2 > x >= x1 and end <= x2:
|
||||
yield move_to(x, y)
|
||||
yield from segments
|
||||
yield from strip
|
||||
continue
|
||||
|
||||
iter_segments = iter(segments)
|
||||
iter_segments = iter(strip)
|
||||
if x < x1:
|
||||
for segment in iter_segments:
|
||||
next_x = x + _cell_len(segment.text)
|
||||
@@ -635,7 +637,7 @@ class Compositor:
|
||||
|
||||
def _get_renders(
|
||||
self, crop: Region | None = None
|
||||
) -> Iterable[tuple[Region, Region, Lines]]:
|
||||
) -> Iterable[tuple[Region, Region, Strips]]:
|
||||
"""Get rendered widgets (lists of segments) in the composition.
|
||||
|
||||
Returns:
|
||||
@@ -685,19 +687,21 @@ class Compositor:
|
||||
_Region(delta_x, delta_y, new_width, new_height)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _assemble_chops(
|
||||
cls, chops: list[dict[int, list[Segment] | None]]
|
||||
) -> list[list[Segment]]:
|
||||
"""Combine chops in to lines."""
|
||||
from_iterable = chain.from_iterable
|
||||
segment_lines: list[list[Segment]] = [
|
||||
list(from_iterable(line for line in bucket.values() if line is not None))
|
||||
for bucket in chops
|
||||
]
|
||||
return segment_lines
|
||||
# @classmethod
|
||||
# def _assemble_chops(cls, chops: list[dict[int, Strip | None]]) -> list[Strip]:
|
||||
# """Combine chops in to lines."""
|
||||
|
||||
def render(self, full: bool = False) -> RenderableType | None:
|
||||
# [Strip.join(strips) for strips in chops]
|
||||
|
||||
# from_iterable = chain.from_iterable
|
||||
|
||||
# segment_lines: list[list[Segment]] = [
|
||||
# list(from_iterable(strip for strip in bucket.values() if strip is not None))
|
||||
# for bucket in chops
|
||||
# ]
|
||||
# return segment_lines
|
||||
|
||||
def render(self, full: bool = False) -> RenderableType:
|
||||
"""Render a layout.
|
||||
|
||||
Returns:
|
||||
@@ -728,8 +732,6 @@ class Compositor:
|
||||
else:
|
||||
return None
|
||||
|
||||
divide = Segment.divide
|
||||
|
||||
# Maps each cut on to a list of segments
|
||||
cuts = self.cuts
|
||||
|
||||
@@ -738,19 +740,19 @@ class Compositor:
|
||||
"Callable[[list[int]], dict[int, list[Segment] | None]]", dict.fromkeys
|
||||
)
|
||||
# A mapping of cut index to a list of segments for each line
|
||||
chops: list[dict[int, list[Segment] | None]]
|
||||
chops: list[dict[int, Strip | None]]
|
||||
chops = [fromkeys(cut_set[:-1]) for cut_set in cuts]
|
||||
|
||||
cut_segments: Iterable[list[Segment]]
|
||||
cut_strips: Iterable[Strip]
|
||||
|
||||
# Go through all the renders in reverse order and fill buckets with no render
|
||||
renders = self._get_renders(crop)
|
||||
intersection = Region.intersection
|
||||
|
||||
for region, clip, lines in renders:
|
||||
for region, clip, strips in renders:
|
||||
render_region = intersection(region, clip)
|
||||
|
||||
for y, line in zip(render_region.line_range, lines):
|
||||
for y, strip in zip(render_region.line_range, strips):
|
||||
if not is_rendered_line(y):
|
||||
continue
|
||||
|
||||
@@ -763,20 +765,20 @@ class Compositor:
|
||||
]
|
||||
if len(final_cuts) <= 2:
|
||||
# Two cuts, which means the entire line
|
||||
cut_segments = [line]
|
||||
cut_strips = [strip]
|
||||
else:
|
||||
render_x = render_region.x
|
||||
relative_cuts = [cut - render_x for cut in final_cuts[1:]]
|
||||
cut_segments = divide(line, relative_cuts)
|
||||
cut_strips = list(strip.divide(relative_cuts))
|
||||
|
||||
# Since we are painting front to back, the first segments for a cut "wins"
|
||||
for cut, segments in zip(final_cuts, cut_segments):
|
||||
for cut, segments in zip(final_cuts, cut_strips):
|
||||
if chops_line[cut] is None:
|
||||
chops_line[cut] = segments
|
||||
|
||||
if full:
|
||||
render_lines = self._assemble_chops(chops)
|
||||
return LayoutUpdate(render_lines, screen_region)
|
||||
render_strips = [Strip.join(chop.values()) for chop in chops]
|
||||
return LayoutUpdate(render_strips, screen_region)
|
||||
else:
|
||||
chop_ends = [cut_set[1:] for cut_set in cuts]
|
||||
return ChopsUpdate(chops, spans, chop_ends)
|
||||
|
||||
@@ -10,7 +10,7 @@ from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from ._cells import cell_len
|
||||
from ._types import Lines
|
||||
from ._types import Strips
|
||||
from .css.types import AlignHorizontal, AlignVertical
|
||||
from .geometry import Size
|
||||
|
||||
@@ -22,8 +22,8 @@ def line_crop(
|
||||
|
||||
Args:
|
||||
segments (list[Segment]): A list of Segments for a line.
|
||||
start (int): Start offset
|
||||
end (int): End offset (exclusive)
|
||||
start (int): Start offset (cells)
|
||||
end (int): End offset (cells, exclusive)
|
||||
total (int): Total cell length of segments.
|
||||
Returns:
|
||||
list[Segment]: A new shorter list of segments
|
||||
@@ -130,7 +130,7 @@ def line_pad(
|
||||
|
||||
|
||||
def align_lines(
|
||||
lines: Lines,
|
||||
lines: Strips,
|
||||
style: Style,
|
||||
size: Size,
|
||||
horizontal: AlignHorizontal,
|
||||
@@ -153,7 +153,7 @@ def align_lines(
|
||||
width, height = size
|
||||
shape_width, shape_height = Segment.get_shape(lines)
|
||||
|
||||
def blank_lines(count: int) -> Lines:
|
||||
def blank_lines(count: int) -> Strips:
|
||||
return [[Segment(" " * width, style)]] * count
|
||||
|
||||
top_blank_lines = bottom_blank_lines = 0
|
||||
|
||||
@@ -11,12 +11,13 @@ from ._border import get_box, render_row
|
||||
from ._filter import LineFilter
|
||||
from ._opacity import _apply_opacity
|
||||
from ._segment_tools import line_crop, line_pad, line_trim
|
||||
from ._types import Lines
|
||||
from ._types import Strips
|
||||
from ._typing import TypeAlias
|
||||
from .color import Color
|
||||
from .geometry import Region, Size, Spacing
|
||||
from .renderables.text_opacity import TextOpacity
|
||||
from .renderables.tint import Tint
|
||||
from .strip import Strip
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .css.styles import StylesBase
|
||||
@@ -25,35 +26,6 @@ if TYPE_CHECKING:
|
||||
RenderLineCallback: TypeAlias = Callable[[int], List[Segment]]
|
||||
|
||||
|
||||
def style_links(
|
||||
segments: Iterable[Segment], link_id: str, link_style: Style
|
||||
) -> list[Segment]:
|
||||
"""Apply a style to the given link id.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): Segments.
|
||||
link_id (str): A link id.
|
||||
link_style (Style): Style to apply.
|
||||
|
||||
Returns:
|
||||
list[Segment]: A list of new segments.
|
||||
"""
|
||||
|
||||
_Segment = Segment
|
||||
|
||||
segments = [
|
||||
_Segment(
|
||||
text,
|
||||
(style + link_style if style is not None else None)
|
||||
if (style and not style._null and style._link_id == link_id)
|
||||
else style,
|
||||
control,
|
||||
)
|
||||
for text, style, control in segments
|
||||
]
|
||||
return segments
|
||||
|
||||
|
||||
@lru_cache(1024 * 8)
|
||||
def make_blank(width, style: Style) -> Segment:
|
||||
"""Make a blank segment.
|
||||
@@ -95,7 +67,7 @@ class StylesCache:
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._cache: dict[int, list[Segment]] = {}
|
||||
self._cache: dict[int, Strip] = {}
|
||||
self._dirty_lines: set[int] = set()
|
||||
self._width = 1
|
||||
|
||||
@@ -123,7 +95,7 @@ class StylesCache:
|
||||
self._cache.clear()
|
||||
self._dirty_lines.clear()
|
||||
|
||||
def render_widget(self, widget: Widget, crop: Region) -> Lines:
|
||||
def render_widget(self, widget: Widget, crop: Region) -> list[Strip]:
|
||||
"""Render the content for a widget.
|
||||
|
||||
Args:
|
||||
@@ -135,7 +107,7 @@ class StylesCache:
|
||||
"""
|
||||
base_background, background = widget.background_colors
|
||||
styles = widget.styles
|
||||
lines = self.render(
|
||||
strips = self.render(
|
||||
styles,
|
||||
widget.region.size,
|
||||
base_background,
|
||||
@@ -147,7 +119,6 @@ class StylesCache:
|
||||
filter=widget.app._filter,
|
||||
)
|
||||
if widget.auto_links:
|
||||
_style_links = style_links
|
||||
hover_style = widget.hover_style
|
||||
link_hover_style = widget.link_hover_style
|
||||
if (
|
||||
@@ -157,12 +128,12 @@ class StylesCache:
|
||||
and "@click" in hover_style.meta
|
||||
):
|
||||
if link_hover_style:
|
||||
lines = [
|
||||
_style_links(line, hover_style.link_id, link_hover_style)
|
||||
for line in lines
|
||||
strips = [
|
||||
strip.style_links(hover_style.link_id, link_hover_style)
|
||||
for strip in strips
|
||||
]
|
||||
|
||||
return lines
|
||||
return strips
|
||||
|
||||
def render(
|
||||
self,
|
||||
@@ -175,7 +146,7 @@ class StylesCache:
|
||||
padding: Spacing | None = None,
|
||||
crop: Region | None = None,
|
||||
filter: LineFilter | None = None,
|
||||
) -> Lines:
|
||||
) -> list[Strip]:
|
||||
"""Render a widget content plus CSS styles.
|
||||
|
||||
Args:
|
||||
@@ -202,15 +173,14 @@ class StylesCache:
|
||||
if width != self._width:
|
||||
self.clear()
|
||||
self._width = width
|
||||
lines: Lines = []
|
||||
add_line = lines.append
|
||||
simplify = Segment.simplify
|
||||
strips: list[Strip] = []
|
||||
add_strip = strips.append
|
||||
|
||||
is_dirty = self._dirty_lines.__contains__
|
||||
render_line = self.render_line
|
||||
for y in crop.line_range:
|
||||
if is_dirty(y) or y not in self._cache:
|
||||
line = render_line(
|
||||
strip = render_line(
|
||||
styles,
|
||||
y,
|
||||
size,
|
||||
@@ -220,21 +190,19 @@ class StylesCache:
|
||||
background,
|
||||
render_content_line,
|
||||
)
|
||||
line = list(simplify(line))
|
||||
self._cache[y] = line
|
||||
self._cache[y] = strip
|
||||
else:
|
||||
line = self._cache[y]
|
||||
strip = self._cache[y]
|
||||
if filter:
|
||||
line = filter.filter(line)
|
||||
add_line(line)
|
||||
strip = strip.apply_filter(filter)
|
||||
add_strip(strip)
|
||||
self._dirty_lines.difference_update(crop.line_range)
|
||||
|
||||
if crop.column_span != (0, width):
|
||||
_line_crop = line_crop
|
||||
x1, x2 = crop.column_span
|
||||
lines = [_line_crop(line, x1, x2, width) for line in lines]
|
||||
strips = [strip.crop(x1, x2) for strip in strips]
|
||||
|
||||
return lines
|
||||
return strips
|
||||
|
||||
def render_line(
|
||||
self,
|
||||
@@ -246,7 +214,7 @@ class StylesCache:
|
||||
base_background: Color,
|
||||
background: Color,
|
||||
render_content_line: RenderLineCallback,
|
||||
) -> list[Segment]:
|
||||
) -> Strip:
|
||||
"""Render a styled line.
|
||||
|
||||
Args:
|
||||
@@ -402,4 +370,5 @@ class StylesCache:
|
||||
else:
|
||||
line = [*line, right]
|
||||
|
||||
return post(line)
|
||||
strip = Strip(post(line), width)
|
||||
return strip
|
||||
|
||||
@@ -2,10 +2,12 @@ from typing import Awaitable, Callable, List, TYPE_CHECKING, Union
|
||||
|
||||
from rich.segment import Segment
|
||||
|
||||
from textual._typing import Protocol
|
||||
from ._typing import Protocol
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .message import Message
|
||||
from .strip import Strip
|
||||
|
||||
|
||||
class MessageTarget(Protocol):
|
||||
@@ -27,5 +29,5 @@ class EventTarget(Protocol):
|
||||
...
|
||||
|
||||
|
||||
Lines = List[List[Segment]]
|
||||
Strips = List["Strip"]
|
||||
CallbackType = Union[Callable[[], Awaitable[None]], Callable[[], None]]
|
||||
|
||||
@@ -30,6 +30,16 @@ class ScrollView(Widget):
|
||||
"""Not transparent, i.e. renders something."""
|
||||
return False
|
||||
|
||||
def watch_scroll_x(self, new_value: float) -> None:
|
||||
if self.show_horizontal_scrollbar:
|
||||
self.horizontal_scrollbar.position = int(new_value)
|
||||
self.refresh()
|
||||
|
||||
def watch_scroll_y(self, new_value: float) -> None:
|
||||
if self.show_vertical_scrollbar:
|
||||
self.vertical_scrollbar.position = int(new_value)
|
||||
self.refresh()
|
||||
|
||||
def on_mount(self):
|
||||
self._refresh_scrollbars()
|
||||
|
||||
@@ -68,6 +78,8 @@ class ScrollView(Widget):
|
||||
virtual_size (Size): New virtual size.
|
||||
container_size (Size): New container size.
|
||||
"""
|
||||
if self._size != size or container_size != container_size:
|
||||
self.refresh()
|
||||
if (
|
||||
self._size != size
|
||||
or virtual_size != self.virtual_size
|
||||
@@ -77,9 +89,7 @@ class ScrollView(Widget):
|
||||
virtual_size = self.virtual_size
|
||||
self._container_size = size - self.styles.gutter.totals
|
||||
self._scroll_update(virtual_size)
|
||||
|
||||
self.scroll_to(self.scroll_x, self.scroll_y, animate=False)
|
||||
self.refresh()
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
"""Render the scrollable region (if `render_lines` is not implemented).
|
||||
|
||||
181
src/textual/strip.py
Normal file
181
src/textual/strip.py
Normal file
@@ -0,0 +1,181 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import chain
|
||||
from typing import Iterator, Iterable, Sequence
|
||||
|
||||
import rich.repr
|
||||
from rich.cells import cell_len, set_cell_size
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from ._filter import LineFilter
|
||||
from ._segment_tools import line_crop
|
||||
|
||||
from ._profile import timer
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class Strip:
|
||||
__slots__ = ["_segments", "_cell_length", "_divide_cache"]
|
||||
|
||||
def __init__(
|
||||
self, segments: Iterable[Segment], cell_length: int | None = None
|
||||
) -> None:
|
||||
self._segments = list(segments)
|
||||
self._cell_length = cell_length
|
||||
self._divide_cache: dict[tuple[int], list[Strip]] = {}
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield self._segments
|
||||
yield self.cell_length
|
||||
|
||||
@property
|
||||
def cell_length(self) -> int:
|
||||
"""Get the number of cells required to render this object."""
|
||||
# Done on demand and cached, as this is an O(n) operation
|
||||
if self._cell_length is None:
|
||||
self._cell_length = Segment.get_line_length(self._segments)
|
||||
return self._cell_length
|
||||
|
||||
@classmethod
|
||||
def join(cls, strips: Iterable[Strip | None]) -> Strip:
|
||||
|
||||
segments: list[list[Segment]] = []
|
||||
add_segments = segments.append
|
||||
total_cell_length = 0
|
||||
for strip in strips:
|
||||
if strip is None:
|
||||
continue
|
||||
total_cell_length += strip.cell_length
|
||||
add_segments(strip._segments)
|
||||
strip = cls(chain.from_iterable(segments), total_cell_length)
|
||||
return strip
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self._segments)
|
||||
|
||||
def __iter__(self) -> Iterator[Segment]:
|
||||
return iter(self._segments)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._segments)
|
||||
|
||||
def __eq__(self, strip: Strip) -> bool:
|
||||
return (
|
||||
self._segments == strip._segments and self.cell_length == strip.cell_length
|
||||
)
|
||||
|
||||
def adjust_line_length(self, cell_length: int, style: Style | None) -> Strip:
|
||||
|
||||
new_line: list[Segment]
|
||||
line = self._segments
|
||||
current_cell_length = self.cell_length
|
||||
|
||||
_Segment = Segment
|
||||
|
||||
if current_cell_length < cell_length:
|
||||
new_line = line + [
|
||||
_Segment(" " * (cell_length - current_cell_length), style)
|
||||
]
|
||||
|
||||
elif current_cell_length > cell_length:
|
||||
new_line = []
|
||||
append = new_line.append
|
||||
line_length = 0
|
||||
for segment in line:
|
||||
segment_length = segment.cell_length
|
||||
if line_length + segment_length < cell_length:
|
||||
append(segment)
|
||||
line_length += segment_length
|
||||
else:
|
||||
text, segment_style, _ = segment
|
||||
text = set_cell_size(text, cell_length - line_length)
|
||||
append(_Segment(text, segment_style))
|
||||
break
|
||||
else:
|
||||
return self
|
||||
|
||||
return Strip(new_line, cell_length)
|
||||
|
||||
def simplify(self) -> Strip:
|
||||
line = Strip(
|
||||
Segment.simplify(self._segments),
|
||||
self._cell_length,
|
||||
)
|
||||
return line
|
||||
|
||||
def apply_filter(self, filter: LineFilter) -> Strip:
|
||||
return Strip(filter.filter(self._segments), self._cell_length)
|
||||
|
||||
def style_links(self, link_id: str, link_style: Style) -> Strip:
|
||||
_Segment = Segment
|
||||
if not any(
|
||||
segment.style._link_id == link_id
|
||||
for segment in self._segments
|
||||
if segment.style
|
||||
):
|
||||
return self
|
||||
segments = [
|
||||
_Segment(
|
||||
text,
|
||||
(style + link_style if style is not None else None)
|
||||
if (style and not style._null and style._link_id == link_id)
|
||||
else style,
|
||||
control,
|
||||
)
|
||||
for text, style, control in self._segments
|
||||
]
|
||||
return Strip(segments, self._cell_length)
|
||||
|
||||
def crop(self, start: int, end: int) -> Strip:
|
||||
_cell_len = cell_len
|
||||
pos = 0
|
||||
output_segments: list[Segment] = []
|
||||
add_segment = output_segments.append
|
||||
iter_segments = iter(self._segments)
|
||||
segment: Segment | None = None
|
||||
for segment in iter_segments:
|
||||
end_pos = pos + _cell_len(segment.text)
|
||||
if end_pos > start:
|
||||
segment = segment.split_cells(start - pos)[1]
|
||||
break
|
||||
pos = end_pos
|
||||
else:
|
||||
return Strip([], 0)
|
||||
|
||||
if end >= self.cell_length:
|
||||
# The end crop is the end of the segments, so we can collect all remaining segments
|
||||
if segment:
|
||||
add_segment(segment)
|
||||
output_segments.extend(iter_segments)
|
||||
return Strip(output_segments, self.cell_length - start)
|
||||
|
||||
pos = start
|
||||
while segment is not None:
|
||||
end_pos = pos + _cell_len(segment.text)
|
||||
if end_pos < end:
|
||||
add_segment(segment)
|
||||
else:
|
||||
add_segment(segment.split_cells(end - pos)[0])
|
||||
break
|
||||
pos = end_pos
|
||||
segment = next(iter_segments, None)
|
||||
return Strip(output_segments, end - start)
|
||||
|
||||
def divide(self, cuts: Iterable[int]) -> list[Strip]:
|
||||
|
||||
pos = 0
|
||||
cache_key = tuple(cuts)
|
||||
cached = self._divide_cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
strips: list[Strip] = []
|
||||
add_strip = strips.append
|
||||
for segments, cut in zip(Segment.divide(self._segments, cuts), cuts):
|
||||
add_strip(Strip(segments, cut - pos))
|
||||
pos += cut
|
||||
|
||||
self._divide_cache[cache_key] = strips
|
||||
|
||||
return strips
|
||||
@@ -43,7 +43,7 @@ from ._easing import DEFAULT_SCROLL_EASING
|
||||
from ._layout import Layout
|
||||
from ._segment_tools import align_lines
|
||||
from ._styles_cache import StylesCache
|
||||
from ._types import Lines
|
||||
from ._types import Strips
|
||||
from .actions import SkipAction
|
||||
from .await_remove import AwaitRemove
|
||||
from .binding import Binding
|
||||
@@ -57,6 +57,7 @@ from .message import Message
|
||||
from .messages import CallbackType
|
||||
from .reactive import Reactive
|
||||
from .render import measure
|
||||
from .strip import Strip
|
||||
from .walk import walk_depth_first
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -156,7 +157,7 @@ class RenderCache(NamedTuple):
|
||||
"""Stores results of a previous render."""
|
||||
|
||||
size: Size
|
||||
lines: Lines
|
||||
lines: Strips
|
||||
|
||||
|
||||
class WidgetError(Exception):
|
||||
@@ -2118,7 +2119,7 @@ class Widget(DOMNode):
|
||||
line = [Segment(" " * self.size.width, self.rich_style)]
|
||||
return line
|
||||
|
||||
def render_lines(self, crop: Region) -> Lines:
|
||||
def render_lines(self, crop: Region) -> list[Strip]:
|
||||
"""Render the widget in to lines.
|
||||
|
||||
Args:
|
||||
@@ -2127,8 +2128,8 @@ class Widget(DOMNode):
|
||||
Returns:
|
||||
Lines: A list of list of segments.
|
||||
"""
|
||||
lines = self._styles_cache.render_widget(self, crop)
|
||||
return lines
|
||||
strips = self._styles_cache.render_widget(self, crop)
|
||||
return strips
|
||||
|
||||
def get_style_at(self, x: int, y: int) -> Style:
|
||||
"""Get the Rich style in a widget at a given relative offset.
|
||||
|
||||
@@ -14,7 +14,7 @@ from rich.text import Text, TextType
|
||||
from .. import events, messages
|
||||
from .._cache import LRUCache
|
||||
from .._segment_tools import line_crop
|
||||
from .._types import Lines
|
||||
from .._types import Strips
|
||||
from ..geometry import Region, Size, Spacing, clamp
|
||||
from ..reactive import Reactive
|
||||
from ..render import measure
|
||||
@@ -207,10 +207,10 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
self.row_count = 0
|
||||
self._y_offsets: list[tuple[int, int]] = []
|
||||
self._row_render_cache: LRUCache[
|
||||
tuple[int, int, Style, int, int], tuple[Lines, Lines]
|
||||
tuple[int, int, Style, int, int], tuple[Strips, Strips]
|
||||
]
|
||||
self._row_render_cache = LRUCache(1000)
|
||||
self._cell_render_cache: LRUCache[tuple[int, int, Style, bool, bool], Lines]
|
||||
self._cell_render_cache: LRUCache[tuple[int, int, Style, bool, bool], Strips]
|
||||
self._cell_render_cache = LRUCache(10000)
|
||||
self._line_cache: LRUCache[
|
||||
tuple[int, int, int, int, int, int, Style], list[Segment]
|
||||
@@ -450,7 +450,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
width: int,
|
||||
cursor: bool = False,
|
||||
hover: bool = False,
|
||||
) -> Lines:
|
||||
) -> Strips:
|
||||
"""Render the given cell.
|
||||
|
||||
Args:
|
||||
@@ -488,7 +488,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
base_style: Style,
|
||||
cursor_column: int = -1,
|
||||
hover_column: int = -1,
|
||||
) -> tuple[Lines, Lines]:
|
||||
) -> tuple[Strips, Strips]:
|
||||
"""Render a row in to lines for each cell.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -15,7 +15,7 @@ from ..geometry import Size, Region
|
||||
from ..scroll_view import ScrollView
|
||||
from .._cache import LRUCache
|
||||
from .._segment_tools import line_crop
|
||||
from .._types import Lines
|
||||
from .._types import Strips
|
||||
|
||||
|
||||
class TextLog(ScrollView, can_focus=True):
|
||||
@@ -143,7 +143,7 @@ class TextLog(ScrollView, can_focus=True):
|
||||
line = list(Segment.apply_style(line, self.rich_style))
|
||||
return line
|
||||
|
||||
def render_lines(self, crop: Region) -> Lines:
|
||||
def render_lines(self, crop: Region) -> Strips:
|
||||
"""Render the widget in to lines.
|
||||
|
||||
Args:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,13 +4,14 @@ from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from textual._styles_cache import StylesCache
|
||||
from textual._types import Lines
|
||||
from textual._types import Strips
|
||||
from textual.color import Color
|
||||
from textual.css.styles import Styles
|
||||
from textual.geometry import Region, Size
|
||||
from textual.strip import Strip
|
||||
|
||||
|
||||
def _extract_content(lines: Lines):
|
||||
def _extract_content(lines: Strips):
|
||||
"""Extract the text content from lines."""
|
||||
content = ["".join(segment.text for segment in line) for line in lines]
|
||||
return content
|
||||
@@ -44,10 +45,11 @@ def test_no_styles():
|
||||
)
|
||||
style = Style.from_color(bgcolor=Color.parse("green").rich_color)
|
||||
expected = [
|
||||
[Segment("foo", style)],
|
||||
[Segment("bar", style)],
|
||||
[Segment("baz", style)],
|
||||
Strip([Segment("foo", style)], 3),
|
||||
Strip([Segment("bar", style)], 3),
|
||||
Strip([Segment("baz", style)], 3),
|
||||
]
|
||||
|
||||
assert lines == expected
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user