mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
optimized line_crop
This commit is contained in:
@@ -28,6 +28,7 @@ from .geometry import Region, Offset, Size
|
||||
|
||||
|
||||
from ._loop import loop_last
|
||||
from ._segment_tools import line_crop
|
||||
from ._types import Lines
|
||||
from .widget import Widget
|
||||
|
||||
@@ -412,7 +413,6 @@ class Compositor:
|
||||
else:
|
||||
widget_regions = []
|
||||
|
||||
divide = Segment.divide
|
||||
intersection = Region.intersection
|
||||
overlaps = Region.overlaps
|
||||
|
||||
@@ -425,9 +425,9 @@ class Compositor:
|
||||
new_x, new_y, new_width, new_height = intersection(region, clip)
|
||||
delta_x = new_x - region.x
|
||||
delta_y = new_y - region.y
|
||||
splits = [delta_x, delta_x + new_width]
|
||||
crop_x = delta_x + new_width
|
||||
lines = widget.get_render_lines(delta_y, delta_y + new_height)
|
||||
lines = [list(divide(line, splits))[1] for line in lines]
|
||||
lines = [line_crop(line, delta_x, crop_x) for line in lines]
|
||||
yield region, clip, lines
|
||||
|
||||
@classmethod
|
||||
@@ -509,13 +509,11 @@ class Compositor:
|
||||
crop_x, crop_y, crop_x2, crop_y2 = crop_region.corners
|
||||
render_lines = self._assemble_chops(chops[crop_y:crop_y2])
|
||||
|
||||
def width_view(line: list[Segment]) -> list[Segment]:
|
||||
div_lines = list(divide(line, [crop_x, crop_x2]))
|
||||
line = div_lines[1] if len(div_lines) > 1 else div_lines[0]
|
||||
return line
|
||||
|
||||
if crop is not None and (crop_x, crop_x2) != (0, width):
|
||||
render_lines = [width_view(line) if line else line for line in render_lines]
|
||||
render_lines = [
|
||||
line_crop(line, crop_x, crop_x2) if line else line
|
||||
for line in render_lines
|
||||
]
|
||||
|
||||
return SegmentLines(render_lines, new_lines=True)
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.segment import Segment
|
||||
|
||||
from .geometry import Region
|
||||
from ._types import Lines
|
||||
|
||||
|
||||
def crop_lines(lines: Lines, clip: Region) -> Lines:
|
||||
lines = lines[clip.y : clip.y + clip.height]
|
||||
|
||||
def width_view(line: list[Segment]) -> list[Segment]:
|
||||
_, line = Segment.divide(line, [clip.x, clip.x + clip.width])
|
||||
return line
|
||||
|
||||
cropped_lines = [width_view(line) for line in lines]
|
||||
return cropped_lines
|
||||
49
src/textual/_segment_tools.py
Normal file
49
src/textual/_segment_tools.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Tools for processing Segments, or lists of Segments.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.segment import Segment
|
||||
|
||||
|
||||
def line_crop(segments: list[Segment], start: int, end: int) -> list[Segment]:
|
||||
"""Crops a list of segments between two cell offsets.
|
||||
|
||||
Args:
|
||||
segments (list[Segment]): A list of Segments for a line.
|
||||
start (int): Start offset
|
||||
end (int): End offset
|
||||
|
||||
Returns:
|
||||
list[Segment]: A new shorter list of segments
|
||||
"""
|
||||
# This is essentially a specialized version of Segment.divide
|
||||
# The following line has equivalent functionality (but a little slower)
|
||||
# return list(Segment.divide(segments, [start, end]))[1]
|
||||
pos = 0
|
||||
output_segments: list[Segment] = []
|
||||
add_segment = output_segments.append
|
||||
iter_segments = iter(segments)
|
||||
segment: Segment | None = None
|
||||
for segment in iter_segments:
|
||||
end_pos = pos + segment.cell_length
|
||||
if end_pos > start:
|
||||
segment = segment.split_cells(start - pos)[-1]
|
||||
break
|
||||
pos = end_pos
|
||||
else:
|
||||
return []
|
||||
|
||||
pos = start
|
||||
while segment is not None:
|
||||
end_pos = pos + segment.cell_length
|
||||
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 output_segments
|
||||
55
tests/test_segment_tools.py
Normal file
55
tests/test_segment_tools.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
|
||||
from textual._segment_tools import line_crop
|
||||
|
||||
|
||||
def test_line_crop():
|
||||
bold = Style(bold=True)
|
||||
italic = Style(italic=True)
|
||||
segments = [
|
||||
Segment("Hello", bold),
|
||||
Segment(" World!", italic),
|
||||
]
|
||||
|
||||
assert line_crop(segments, 1, 2) == [Segment("e", bold)]
|
||||
assert line_crop(segments, 4, 20) == [
|
||||
Segment("o", bold),
|
||||
Segment(" World!", italic),
|
||||
]
|
||||
|
||||
|
||||
def test_line_crop_emoji():
|
||||
bold = Style(bold=True)
|
||||
italic = Style(italic=True)
|
||||
segments = [
|
||||
Segment("Hello", bold),
|
||||
Segment("💩💩💩", italic),
|
||||
]
|
||||
assert line_crop(segments, 8, 11) == [Segment(" 💩", italic)]
|
||||
assert line_crop(segments, 9, 11) == [Segment("💩", italic)]
|
||||
|
||||
|
||||
def test_line_crop_edge():
|
||||
segments = [Segment("foo"), Segment("bar"), Segment("baz")]
|
||||
assert line_crop(segments, 2, 9) == [Segment("o"), Segment("bar"), Segment("baz")]
|
||||
assert line_crop(segments, 3, 9) == [Segment("bar"), Segment("baz")]
|
||||
assert line_crop(segments, 4, 9) == [Segment("ar"), Segment("baz")]
|
||||
assert line_crop(segments, 4, 8) == [Segment("ar"), Segment("ba")]
|
||||
|
||||
|
||||
def test_line_crop_edge_2():
|
||||
segments = [
|
||||
Segment("╭─"),
|
||||
Segment(
|
||||
"────── Placeholder ───────",
|
||||
),
|
||||
Segment(
|
||||
"─╮",
|
||||
),
|
||||
]
|
||||
result = line_crop(segments, 30, 60)
|
||||
expected = []
|
||||
print(repr(result))
|
||||
assert result == expected
|
||||
Reference in New Issue
Block a user