Merge pull request #319 from Textualize/region-resolve

Regions
This commit is contained in:
Will McGugan
2022-03-09 10:01:28 +00:00
committed by GitHub
3 changed files with 93 additions and 0 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
.pytype .pytype
.DS_Store .DS_Store
.vscode .vscode
.idea
mypy_report mypy_report
docs/build docs/build
docs/source/_build docs/source/_build

View File

@@ -0,0 +1,48 @@
from __future__ import annotations
from collections import defaultdict
from operator import attrgetter
from typing import NamedTuple, Iterable
from .geometry import Region
class InlineRange(NamedTuple):
"""Represents a region on a single line."""
line_index: int
start: int
end: int
def regions_to_ranges(regions: Iterable[Region]) -> Iterable[InlineRange]:
"""Converts the regions to non-overlapping horizontal strips, where each strip
represents the region on a single line. Combining the resulting strips therefore
results in a shape identical to the combined original regions.
Args:
regions (Iterable[Region]): An iterable of Regions.
Returns:
Iterable[InlineRange]: Yields InlineRange objects representing the content on
a single line, with overlaps removed.
"""
inline_ranges: dict[int, list[InlineRange]] = defaultdict(list)
for region_x, region_y, width, height in regions:
for y in range(region_y, region_y + height):
inline_ranges[y].append(
InlineRange(line_index=y, start=region_x, end=region_x + width - 1)
)
get_start = attrgetter("start")
for line_index, ranges in inline_ranges.items():
sorted_ranges = iter(sorted(ranges, key=get_start))
_, start, end = next(sorted_ranges)
for next_line_index, next_start, next_end in sorted_ranges:
if next_start <= end + 1:
end = max(end, next_end)
else:
yield InlineRange(line_index, start, end)
start = next_start
end = next_end
yield InlineRange(line_index, start, end)

View File

@@ -0,0 +1,44 @@
from textual._region_group import regions_to_ranges, InlineRange
from textual.geometry import Region
def test_regions_to_ranges_no_regions():
assert list(regions_to_ranges([])) == []
def test_regions_to_ranges_single_region():
regions = [Region(0, 0, 3, 2)]
assert list(regions_to_ranges(regions)) == [InlineRange(0, 0, 2), InlineRange(1, 0, 2)]
def test_regions_to_ranges_partially_overlapping_regions():
regions = [Region(0, 0, 2, 2), Region(1, 1, 2, 2)]
assert list(regions_to_ranges(regions)) == [
InlineRange(0, 0, 1), InlineRange(1, 0, 2), InlineRange(2, 1, 2),
]
def test_regions_to_ranges_fully_overlapping_regions():
regions = [Region(1, 1, 3, 3), Region(2, 2, 1, 1), Region(0, 2, 3, 1)]
assert list(regions_to_ranges(regions)) == [
InlineRange(1, 1, 3), InlineRange(2, 0, 3), InlineRange(3, 1, 3)
]
def test_regions_to_ranges_disjoint_regions_different_lines():
regions = [Region(0, 0, 2, 1), Region(2, 2, 2, 1)]
assert list(regions_to_ranges(regions)) == [InlineRange(0, 0, 1), InlineRange(2, 2, 3)]
def test_regions_to_ranges_disjoint_regions_same_line():
regions = [Region(0, 0, 1, 2), Region(2, 0, 1, 1)]
assert list(regions_to_ranges(regions)) == [
InlineRange(0, 0, 0), InlineRange(0, 2, 2), InlineRange(1, 0, 0)
]
def test_regions_to_ranges_directly_adjacent_ranges_merged():
regions = [Region(0, 0, 1, 2), Region(1, 0, 1, 2)]
assert list(regions_to_ranges(regions)) == [
InlineRange(0, 0, 1), InlineRange(1, 0, 1)
]