Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix scroll placements #5253

Merged
merged 13 commits into from
Nov 18, 2024
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

## [0.86.2] - 2024-11-18

### Fixed

- Fixed visibility glitch for widgets with an offset https://github.com/Textualize/textual/pull/5253
- Fixed theme variables being unavailable in code until refresh_css was called https://github.com/Textualize/textual/pull/5254


## [0.86.1] - 2024-11-16

### Fixed
Expand Down Expand Up @@ -2549,6 +2552,9 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
- New handler system for messages that doesn't require inheritance
- Improved traceback handling


[0.86.2]: https://github.com/Textualize/textual/compare/v0.86.1...v0.86.2
[0.86.1]: https://github.com/Textualize/textual/compare/v0.86.0...v0.86.1
[0.86.0]: https://github.com/Textualize/textual/compare/v0.85.2...v0.86.0
[0.85.2]: https://github.com/Textualize/textual/compare/v0.85.1...v0.85.2
[0.85.1]: https://github.com/Textualize/textual/compare/v0.85.0...v0.85.1
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "0.86.1"
version = "0.86.2"
homepage = "https://github.com/Textualize/textual"
repository = "https://github.com/Textualize/textual"
documentation = "https://textual.textualize.io/"
Expand Down
13 changes: 9 additions & 4 deletions src/textual/_arrange.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import TYPE_CHECKING, Iterable, Mapping, Sequence

from textual._partition import partition
from textual.geometry import Region, Size, Spacing
from textual.geometry import NULL_OFFSET, NULL_SPACING, Region, Size, Spacing
from textual.layout import DockArrangeResult, WidgetPlacement

if TYPE_CHECKING:
Expand Down Expand Up @@ -133,7 +133,8 @@ def _arrange_dock_widgets(
region_offset = region.offset
size = region.size
width, height = size
null_spacing = Spacing()
null_spacing = NULL_SPACING
null_offset = NULL_OFFSET

top = right = bottom = left = 0

Expand Down Expand Up @@ -173,6 +174,7 @@ def _arrange_dock_widgets(
append_placement(
_WidgetPlacement(
dock_region.translate(region_offset),
null_offset,
null_spacing,
dock_widget,
top_z,
Expand Down Expand Up @@ -202,7 +204,8 @@ def _arrange_split_widgets(
placements: list[WidgetPlacement] = []
append_placement = placements.append
view_region = size.region
null_spacing = Spacing()
null_spacing = NULL_SPACING
null_offset = NULL_OFFSET

for split_widget in split_widgets:
split = split_widget.styles.split
Expand All @@ -226,7 +229,9 @@ def _arrange_split_widgets(
raise AssertionError("invalid value for split edge") # pragma: no-cover

append_placement(
_WidgetPlacement(split_region, null_spacing, split_widget, 1, True)
_WidgetPlacement(
split_region, null_offset, null_spacing, split_widget, 1, True
)
)

return placements, view_region
48 changes: 23 additions & 25 deletions src/textual/_compositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from textual._cells import cell_len
from textual._context import visible_screen_stack
from textual._loop import loop_last
from textual.geometry import NULL_OFFSET, NULL_SPACING, Offset, Region, Size, Spacing
from textual.geometry import NULL_SPACING, Offset, Region, Size, Spacing
from textual.map_geometry import MapGeometry
from textual.strip import Strip, StripRenderable

Expand Down Expand Up @@ -535,8 +535,6 @@ def _arrange_root(
Compositor map and set of widgets.
"""

ORIGIN = NULL_OFFSET

map: CompositorMap = {}
widgets: set[Widget] = set()
add_new_widget = widgets.add
Expand Down Expand Up @@ -580,15 +578,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 Down Expand Up @@ -643,15 +635,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 +701,7 @@ def add_widget(
)

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

widget_region = region + layout_offset

if widget.absolute_offset is not None:
margin = styles.margin
widget_region = widget_region.at_offset(
widget.absolute_offset + margin.top_left
)
widget_region = widget_region.translate(
styles.offset.resolve(widget_region.grow(margin).size, size)
region = region.at_offset(widget.absolute_offset + margin.top_left)
region = region.translate(
styles.offset.resolve(region.grow(margin).size, size)
)
has_rule = styles.has_rule
if has_rule("constrain_x") or has_rule("constrain_y"):
widget_region = widget_region.constrain(
region = region.constrain(
styles.constrain_x,
styles.constrain_y,
styles.margin,
size.region,
)

map[widget._render_widget] = _MapGeometry(
widget_region,
region,
order,
clip,
region.size,
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
20 changes: 15 additions & 5 deletions 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 All @@ -84,6 +85,7 @@ class WidgetPlacement(NamedTuple):
"""The position, size, and relative order of a widget within its parent."""

region: Region
offset: Offset
margin: Spacing
widget: Widget
order: int = 0
Expand All @@ -92,7 +94,7 @@ class WidgetPlacement(NamedTuple):

@classmethod
def translate(
cls, placements: list[WidgetPlacement], offset: Offset
cls, placements: list[WidgetPlacement], translate_offset: Offset
) -> list[WidgetPlacement]:
"""Move all placements by a given offset.

Expand All @@ -103,10 +105,18 @@ def translate(
Returns:
Placements with adjusted region, or same instance if offset is null.
"""
if offset:
if translate_offset:
return [
cls(region + offset, margin, layout_widget, order, fixed, overlay)
for region, margin, layout_widget, order, fixed, overlay in placements
cls(
region + translate_offset,
offset,
margin,
layout_widget,
order,
fixed,
overlay,
)
for region, offset, margin, layout_widget, order, fixed, overlay in placements
]
return placements

Expand Down
27 changes: 18 additions & 9 deletions src/textual/layouts/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from textual._resolve import resolve
from textual.css.scalar import Scalar
from textual.geometry import Region, Size, Spacing
from textual.geometry import NULL_OFFSET, Region, Size, Spacing
from textual.layout import ArrangeResult, Layout, WidgetPlacement

if TYPE_CHECKING:
Expand Down Expand Up @@ -258,6 +258,7 @@ def apply_height_limits(widget: Widget, height: int) -> int:
rows = resolve(row_scalars, size.height, gutter_horizontal, size, viewport)

placements: list[WidgetPlacement] = []
_WidgetPlacement = WidgetPlacement
add_placement = placements.append
widgets: list[Widget] = []
add_widget = widgets.append
Expand All @@ -272,26 +273,34 @@ def apply_height_limits(widget: Widget, height: int) -> int:
x2, cell_width = columns[min(max_column, column + column_span)]
y2, cell_height = rows[min(max_row, row + row_span)]
cell_size = Size(cell_width + x2 - x, cell_height + y2 - y)
width, height, margin = widget._get_box_model(
box_width, box_height, margin = widget._get_box_model(
cell_size,
viewport,
Fraction(cell_size.width),
Fraction(cell_size.height),
)
if self.stretch_height and len(children) > 1:
height = (
height
if (height > cell_size.height)
else Fraction(cell_size.height)
)
if box_height <= cell_size.height:
box_height = Fraction(cell_size.height)

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

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

add_placement(
WidgetPlacement(
_WidgetPlacement(
region + offset,
placement_offset,
(
margin
if gutter_spacing is None
Expand Down
Loading
Loading