mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
first iteration of spatial map
This commit is contained in:
101
src/textual/_spatial_map.py
Normal file
101
src/textual/_spatial_map.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from itertools import product
|
||||||
|
from typing import Iterable, Mapping
|
||||||
|
|
||||||
|
from ._layout import WidgetPlacement
|
||||||
|
from .geometry import Region
|
||||||
|
|
||||||
|
|
||||||
|
class SpatialMap:
|
||||||
|
"""An object to return WidgetPlacements within a given region.
|
||||||
|
|
||||||
|
The widget area is split in to a regular grid of buckets. Each placement is assigned to
|
||||||
|
any bucket it overlaps, which may be 1 or more buckets.
|
||||||
|
|
||||||
|
The `get_placements` function will calculate which buckets overlap the screen area, and combine
|
||||||
|
the placements from those buckets. This generally means that widgets that aren't overlapping or
|
||||||
|
near the screen area can be quickly discarded. The result will typically be a superset of visible
|
||||||
|
placements, which can then be filtered normally.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
placements: Iterable[WidgetPlacement],
|
||||||
|
block_width: int = 80,
|
||||||
|
block_height: int = 80,
|
||||||
|
) -> None:
|
||||||
|
self._placements = placements
|
||||||
|
self._block_width = block_width
|
||||||
|
self._block_height = block_height
|
||||||
|
self._map: defaultdict[tuple[int, int], list[WidgetPlacement]] | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def placement_map(self) -> Mapping[tuple[int, int], list[WidgetPlacement]]:
|
||||||
|
"""A mapping of block coordinate on to widget placement.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Mapping[tuple[int, int], list[WidgetPlacement]]: Mapping.
|
||||||
|
"""
|
||||||
|
if self._map is None:
|
||||||
|
self._map = self._build_placements(self._placements)
|
||||||
|
return self._map
|
||||||
|
return self._map
|
||||||
|
|
||||||
|
def _build_placements(
|
||||||
|
self, placements: Iterable[WidgetPlacement]
|
||||||
|
) -> defaultdict[tuple[int, int], list[WidgetPlacement]]:
|
||||||
|
"""Add placements to map.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
placements (Iterable[WidgetPlacement]): A number of placements.
|
||||||
|
"""
|
||||||
|
map: defaultdict[tuple[int, int], list[WidgetPlacement]] = defaultdict(list)
|
||||||
|
get_bucket = map.__getitem__
|
||||||
|
|
||||||
|
block_width = self._block_width
|
||||||
|
block_height = self._block_height
|
||||||
|
|
||||||
|
for placement in placements:
|
||||||
|
x1, y1, width, height = placement.region
|
||||||
|
x2 = x1 + width
|
||||||
|
y2 = y1 + height
|
||||||
|
for coord in product(
|
||||||
|
range(x1 // block_width, x2 // block_width + 1),
|
||||||
|
range(y1 // block_height, y2 // block_height + 1),
|
||||||
|
):
|
||||||
|
get_bucket(coord).append(placement)
|
||||||
|
return map
|
||||||
|
|
||||||
|
def get_placements(self, screen_region: Region) -> Iterable[WidgetPlacement]:
|
||||||
|
"""Get placements that may overlap a given region. There may be false positives,
|
||||||
|
but no false negatives.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
region (Region): Container region.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
set[WidgetPlacement]: Set of Widget placements.
|
||||||
|
"""
|
||||||
|
x1, y1, width, height = screen_region
|
||||||
|
x2 = x1 + width
|
||||||
|
y2 = y1 + height
|
||||||
|
block_width = self._block_width
|
||||||
|
block_height = self._block_height
|
||||||
|
|
||||||
|
placements: set[WidgetPlacement] = set()
|
||||||
|
extend_placements = placements.update
|
||||||
|
map = self.placement_map
|
||||||
|
map_get = map.get
|
||||||
|
|
||||||
|
for coord in product(
|
||||||
|
range(x1 // block_width, x2 // block_width + 1),
|
||||||
|
range(y1 // block_height, y2 // block_height + 1),
|
||||||
|
):
|
||||||
|
block_placements = map_get(coord)
|
||||||
|
if block_placements is not None:
|
||||||
|
extend_placements(block_placements)
|
||||||
|
|
||||||
|
return placements
|
||||||
Reference in New Issue
Block a user