Skip to content

Commit

Permalink
fix for spatial map and layout
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Nov 18, 2024
1 parent 845840e commit 4c805bf
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 35 deletions.
37 changes: 22 additions & 15 deletions src/textual/_compositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,15 +580,9 @@ def add_widget(
add_new_widget(widget)
else:
add_new_invisible_widget(widget)
styles_offset = styles.offset
layout_offset = (
styles_offset.resolve(region.size, clip.size)
if styles_offset
else ORIGIN
)

# Container region is minus border
container_region = region.shrink(styles.gutter).translate(layout_offset)
container_region = region.shrink(styles.gutter)
container_size = container_region.size

# Widgets with scrollbars (containers or scroll view) require additional processing
Expand All @@ -613,9 +607,12 @@ def add_widget(
sub_clip = clip.intersection(child_region)

if visible_only:
placements = arrange_result.get_visible_placements(
widget_window = (
sub_clip - child_region.offset + widget.scroll_offset
)
placements = arrange_result.get_visible_placements(
widget_window
)
else:
placements = arrange_result.placements
total_region = total_region.union(arrange_result.total_region)
Expand Down Expand Up @@ -643,15 +640,25 @@ def add_widget(
)

# Add all the widgets
for sub_region, _, _, sub_widget, z, fixed, overlay in reversed(
placements
):
for (
sub_region,
sub_region_offset,
_,
sub_widget,
z,
fixed,
overlay,
) in reversed(placements):
layer_index = get_layer_index(sub_widget.layer, 0)
# Combine regions with children to calculate the "virtual size"
if fixed:
widget_region = sub_region + placement_offset
widget_region = (
sub_region + sub_region_offset + placement_offset
)
else:
widget_region = sub_region + placement_scroll_offset
widget_region = (
sub_region + sub_region_offset + placement_scroll_offset
)

widget_order = order + ((layer_index, z, layer_order),)

Expand Down Expand Up @@ -699,7 +706,7 @@ def add_widget(
)

map[widget] = _MapGeometry(
region + layout_offset,
region,
order,
clip,
total_region.size,
Expand All @@ -711,7 +718,7 @@ def add_widget(
elif visible:
# Add the widget to the map

widget_region = region + layout_offset
widget_region = region

if widget.absolute_offset is not None:
margin = styles.margin
Expand Down
10 changes: 5 additions & 5 deletions src/textual/_spatial_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from typing_extensions import TypeAlias

from textual.geometry import Region
from textual.geometry import Offset, Region

ValueType = TypeVar("ValueType")
GridCoordinate: TypeAlias = "tuple[int, int]"
Expand Down Expand Up @@ -57,27 +57,27 @@ def _region_to_grid_coordinates(self, region: Region) -> Iterable[GridCoordinate
)

def insert(
self, regions_and_values: Iterable[tuple[Region, bool, bool, ValueType]]
self, regions_and_values: Iterable[tuple[Region, Offset, bool, bool, ValueType]]
) -> None:
"""Insert values into the Spatial map.
Values are associated with their region in Euclidean space, and a boolean that
indicates fixed regions. Fixed regions don't scroll and are always visible.
Args:
regions_and_values: An iterable of (REGION, FIXED, OVERLAY, VALUE).
regions_and_values: An iterable of (REGION, OFFSET, FIXED, OVERLAY, VALUE).
"""
append_fixed = self._fixed.append
get_grid_list = self._map.__getitem__
_region_to_grid = self._region_to_grid_coordinates
total_region = self.total_region
for region, fixed, overlay, value in regions_and_values:
for region, offset, fixed, overlay, value in regions_and_values:
if fixed:
append_fixed(value)
else:
if not overlay:
total_region = total_region.union(region)
for grid in _region_to_grid(region):
for grid in _region_to_grid(region + offset):
get_grid_list(grid).append(value)
self.total_region = total_region

Expand Down
3 changes: 2 additions & 1 deletion src/textual/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def spatial_map(self) -> SpatialMap[WidgetPlacement]:
self._spatial_map.insert(
(
placement.region.grow(placement.margin),
placement.offset,
placement.fixed,
placement.overlay,
placement,
Expand Down Expand Up @@ -75,7 +76,7 @@ def get_visible_placements(self, region: Region) -> list[WidgetPlacement]:
culled_placements = [
placement
for placement in visible_placements
if placement.fixed or overlaps(placement.region)
if placement.fixed or overlaps(placement.region + placement.offset)
]
return culled_placements

Expand Down
12 changes: 10 additions & 2 deletions src/textual/layouts/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,15 +284,23 @@ def apply_height_limits(widget: Widget, height: int) -> int:
if (height > cell_size.height)
else Fraction(cell_size.height)
)

region = (
Region(x, y, int(width + margin.width), int(height + margin.height))
.crop_size(cell_size)
.shrink(margin)
)

offset = (
styles.offset.resolve(cell_size, viewport)
if styles.has_rule("offset")
else NULL_OFFSET
)

add_placement(
WidgetPlacement(
region + offset,
NULL_OFFSET,
region,
offset,
(
margin
if gutter_spacing is None
Expand Down
17 changes: 14 additions & 3 deletions src/textual/layouts/horizontal.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def arrange(
) -> ArrangeResult:
placements: list[WidgetPlacement] = []
add_placement = placements.append
viewport = parent.app.size

child_styles = [child.styles for child in children]
box_margins: list[Spacing] = [
Expand Down Expand Up @@ -52,7 +53,7 @@ def arrange(
[styles.width for styles in child_styles],
children,
size,
parent.app.size,
viewport,
resolve_margin,
resolve_dimension="width",
)
Expand All @@ -75,10 +76,20 @@ def arrange(

_Region = Region
_WidgetPlacement = WidgetPlacement
_Size = Size
for widget, (content_width, content_height, box_margin), margin in zip(
children, box_models, margins
):
overlay = widget.styles.overlay == "screen"
styles = widget.styles
overlay = styles.overlay == "screen"
offset = (
styles.offset.resolve(
_Size(content_width.__floor__(), content_height.__floor__()),
viewport,
)
if styles.has_rule("offset")
else NULL_OFFSET
)
offset_y = box_margin.top
next_x = x + content_width
add_placement(
Expand All @@ -89,7 +100,7 @@ def arrange(
(next_x - x.__floor__()).__floor__(),
content_height.__floor__(),
),
NULL_OFFSET,
offset,
box_margin,
widget,
0,
Expand Down
15 changes: 13 additions & 2 deletions src/textual/layouts/vertical.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def arrange(
) -> ArrangeResult:
placements: list[WidgetPlacement] = []
add_placement = placements.append
viewport = parent.app.size

child_styles = [child.styles for child in children]
box_margins: list[Spacing] = [
Expand Down Expand Up @@ -80,11 +81,21 @@ def arrange(

_Region = Region
_WidgetPlacement = WidgetPlacement
_Size = Size
for widget, (content_width, content_height, box_margin), margin in zip(
children, box_models, margins
):
overlay = widget.styles.overlay == "screen"
styles = widget.styles
overlay = styles.overlay == "screen"
next_y = y + content_height
offset = (
styles.offset.resolve(
_Size(content_width.__floor__(), content_height.__floor__()),
viewport,
)
if styles.has_rule("offset")
else NULL_OFFSET
)
add_placement(
_WidgetPlacement(
_Region(
Expand All @@ -93,7 +104,7 @@ def arrange(
content_width.__floor__(),
next_y.__floor__() - y.__floor__(),
),
NULL_OFFSET,
offset,
box_margin,
widget,
0,
Expand Down
6 changes: 3 additions & 3 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,9 +437,9 @@ def __init__(
self._content_width_cache: tuple[object, int] = (None, 0)
self._content_height_cache: tuple[object, int] = (None, 0)

self._arrangement_cache: FIFOCache[tuple[Size, int], DockArrangeResult] = (
FIFOCache(4)
)
self._arrangement_cache: FIFOCache[
tuple[Size, int, Widget], DockArrangeResult
] = FIFOCache(4)

self._styles_cache = StylesCache()
self._rich_style_cache: dict[tuple[str, ...], tuple[Style, Style]] = {}
Expand Down
8 changes: 4 additions & 4 deletions tests/test_spatial_map.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from textual._spatial_map import SpatialMap
from textual.geometry import Region
from textual.geometry import Offset, Region


@pytest.mark.parametrize(
Expand Down Expand Up @@ -44,9 +44,9 @@ def test_get_values_in_region() -> None:

spatial_map.insert(
[
(Region(10, 5, 5, 5), False, False, "foo"),
(Region(5, 20, 5, 5), False, False, "bar"),
(Region(0, 0, 40, 1), True, False, "title"),
(Region(10, 5, 5, 5), Offset(), False, False, "foo"),
(Region(5, 20, 5, 5), Offset(), False, False, "bar"),
(Region(0, 0, 40, 1), Offset(), True, False, "title"),
]
)

Expand Down

0 comments on commit 4c805bf

Please sign in to comment.