From f84313dac7c649405f83e6f15d974c1271735f91 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 23 Nov 2022 22:12:44 +0800 Subject: [PATCH] integrated spatial map --- src/textual/_compositor.py | 7 ++++- src/textual/_layout.py | 3 +- .../{spatial_map.py => _spatial_map.py} | 31 ++++++++++--------- src/textual/widget.py | 10 ++++-- 4 files changed, 31 insertions(+), 20 deletions(-) rename src/textual/{spatial_map.py => _spatial_map.py} (80%) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index ab9d69eca..47305047e 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -381,9 +381,14 @@ class Compositor: if widget.is_container: # Arrange the layout - placements, arranged_widgets, spacing = widget._arrange( + spatial_map, arranged_widgets, spacing = widget._arrange( child_region.size ) + + placements = spatial_map.get_placements( + child_region.reset_offset.translate(widget.scroll_offset) + ) + widgets.update(arranged_widgets) # An offset added to all placements diff --git a/src/textual/_layout.py b/src/textual/_layout.py index fc9f21173..25d2d27c7 100644 --- a/src/textual/_layout.py +++ b/src/textual/_layout.py @@ -8,9 +8,10 @@ from ._typing import TypeAlias if TYPE_CHECKING: from .widget import Widget + from ._spatial_map import SpatialMap ArrangeResult: TypeAlias = "tuple[list[WidgetPlacement], set[Widget]]" -DockArrangeResult: TypeAlias = "tuple[list[WidgetPlacement], set[Widget], Spacing]" +DockArrangeResult: TypeAlias = "SpacialMap, set[Widget], Spacing]" class WidgetPlacement(NamedTuple): diff --git a/src/textual/spatial_map.py b/src/textual/_spatial_map.py similarity index 80% rename from src/textual/spatial_map.py rename to src/textual/_spatial_map.py index b8e9e8884..c55caa9a6 100644 --- a/src/textual/spatial_map.py +++ b/src/textual/_spatial_map.py @@ -2,10 +2,12 @@ from __future__ import annotations from collections import defaultdict from itertools import product -from typing import Iterable, Mapping +from operator import attrgetter +from typing import Iterable, Mapping, Sequence from ._layout import WidgetPlacement from .geometry import Region +from ._partition import partition class SpatialMap: @@ -23,26 +25,23 @@ class SpatialMap: def __init__( self, - placements: Iterable[WidgetPlacement], + placements: Sequence[WidgetPlacement], block_width: int = 80, block_height: int = 80, ) -> None: self._placements = placements self._block_width = block_width self._block_height = block_height + self._fixed: list[WidgetPlacement] = [] 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. + self.placement_map = self._build_placements(placements) - Returns: - Mapping[tuple[int, int], list[WidgetPlacement]]: Mapping of coord to list of placements. - """ - if self._map is None: - self._map = self._build_placements(self._placements) - return self._map - return self._map + def __iter__(self) -> Iterable[WidgetPlacement]: + yield from self._placements + + def __reversed__(self) -> Iterable[WidgetPlacement]: + yield from reversed(self._placements) def _build_placements( self, placements: Iterable[WidgetPlacement] @@ -58,6 +57,8 @@ class SpatialMap: block_width = self._block_width block_height = self._block_height + placements, self._fixed = partition(attrgetter("fixed"), placements) + for placement in placements: x1, y1, width, height = placement.region x2 = x1 + width @@ -69,7 +70,7 @@ class SpatialMap: get_bucket(coord).append(placement) return map - def get_placements(self, screen_region: Region) -> Iterable[WidgetPlacement]: + def get_placements(self, screen_region: Region) -> list[WidgetPlacement]: """Get placements that may overlap a given region. There may be false positives, but no false negatives. @@ -85,7 +86,7 @@ class SpatialMap: block_width = self._block_width block_height = self._block_height - placements: set[WidgetPlacement] = set() + placements: set[WidgetPlacement] = set(self._fixed) extend_placements = placements.update map = self.placement_map map_get = map.get @@ -98,4 +99,4 @@ class SpatialMap: if block_placements is not None: extend_placements(block_placements) - return placements + return list(placements) diff --git a/src/textual/widget.py b/src/textual/widget.py index 3dc821e95..292b670ba 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -1,8 +1,8 @@ from __future__ import annotations -from collections import Counter from asyncio import Event as AsyncEvent from asyncio import Lock, create_task, wait +from collections import Counter from fractions import Fraction from itertools import islice from operator import attrgetter @@ -39,6 +39,7 @@ from ._context import active_app from ._easing import DEFAULT_SCROLL_EASING from ._layout import Layout from ._segment_tools import align_lines +from ._spatial_map import SpatialMap from ._styles_cache import StylesCache from ._types import Lines from .await_remove import AwaitRemove @@ -53,7 +54,6 @@ from .message import Message from .messages import CallbackType from .reactive import Reactive from .render import measure -from .await_remove import AwaitRemove from .walk import walk_depth_first if TYPE_CHECKING: @@ -426,7 +426,11 @@ class Widget(DOMNode): return self._arrangement self._arrangement_cache_key = arrange_cache_key - self._arrangement = arrange(self, self.children, size, self.screen.size) + placements, widgets, spacing = arrange( + self, self.children, size, self.screen.size + ) + arrange_result = SpatialMap(placements), widgets, spacing + self._arrangement = arrange_result return self._arrangement def _clear_arrangement_cache(self) -> None: