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 ._loop import loop_last
|
||||||
|
from ._segment_tools import line_crop
|
||||||
from ._types import Lines
|
from ._types import Lines
|
||||||
from .widget import Widget
|
from .widget import Widget
|
||||||
|
|
||||||
@@ -412,7 +413,6 @@ class Compositor:
|
|||||||
else:
|
else:
|
||||||
widget_regions = []
|
widget_regions = []
|
||||||
|
|
||||||
divide = Segment.divide
|
|
||||||
intersection = Region.intersection
|
intersection = Region.intersection
|
||||||
overlaps = Region.overlaps
|
overlaps = Region.overlaps
|
||||||
|
|
||||||
@@ -425,9 +425,9 @@ class Compositor:
|
|||||||
new_x, new_y, new_width, new_height = intersection(region, clip)
|
new_x, new_y, new_width, new_height = intersection(region, clip)
|
||||||
delta_x = new_x - region.x
|
delta_x = new_x - region.x
|
||||||
delta_y = new_y - region.y
|
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 = 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
|
yield region, clip, lines
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -509,13 +509,11 @@ class Compositor:
|
|||||||
crop_x, crop_y, crop_x2, crop_y2 = crop_region.corners
|
crop_x, crop_y, crop_x2, crop_y2 = crop_region.corners
|
||||||
render_lines = self._assemble_chops(chops[crop_y:crop_y2])
|
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):
|
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)
|
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