Skip to content

Commit 8d99130

Browse files
authored
Merge pull request #5253 from Textualize/fix-scroll-placements
Fix scroll placements
2 parents 8b616e1 + f917421 commit 8d99130

File tree

13 files changed

+191
-74
lines changed

13 files changed

+191
-74
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8-
## [Unreleased]
8+
9+
## [0.86.2] - 2024-11-18
910

1011
### Fixed
1112

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

16+
1417
## [0.86.1] - 2024-11-16
1518

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

2555+
2556+
[0.86.2]: https://github.com/Textualize/textual/compare/v0.86.1...v0.86.2
2557+
[0.86.1]: https://github.com/Textualize/textual/compare/v0.86.0...v0.86.1
25522558
[0.86.0]: https://github.com/Textualize/textual/compare/v0.85.2...v0.86.0
25532559
[0.85.2]: https://github.com/Textualize/textual/compare/v0.85.1...v0.85.2
25542560
[0.85.1]: https://github.com/Textualize/textual/compare/v0.85.0...v0.85.1

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "textual"
3-
version = "0.86.1"
3+
version = "0.86.2"
44
homepage = "https://github.com/Textualize/textual"
55
repository = "https://github.com/Textualize/textual"
66
documentation = "https://textual.textualize.io/"

src/textual/_arrange.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import TYPE_CHECKING, Iterable, Mapping, Sequence
77

88
from textual._partition import partition
9-
from textual.geometry import Region, Size, Spacing
9+
from textual.geometry import NULL_OFFSET, NULL_SPACING, Region, Size, Spacing
1010
from textual.layout import DockArrangeResult, WidgetPlacement
1111

1212
if TYPE_CHECKING:
@@ -133,7 +133,8 @@ def _arrange_dock_widgets(
133133
region_offset = region.offset
134134
size = region.size
135135
width, height = size
136-
null_spacing = Spacing()
136+
null_spacing = NULL_SPACING
137+
null_offset = NULL_OFFSET
137138

138139
top = right = bottom = left = 0
139140

@@ -173,6 +174,7 @@ def _arrange_dock_widgets(
173174
append_placement(
174175
_WidgetPlacement(
175176
dock_region.translate(region_offset),
177+
null_offset,
176178
null_spacing,
177179
dock_widget,
178180
top_z,
@@ -202,7 +204,8 @@ def _arrange_split_widgets(
202204
placements: list[WidgetPlacement] = []
203205
append_placement = placements.append
204206
view_region = size.region
205-
null_spacing = Spacing()
207+
null_spacing = NULL_SPACING
208+
null_offset = NULL_OFFSET
206209

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

228231
append_placement(
229-
_WidgetPlacement(split_region, null_spacing, split_widget, 1, True)
232+
_WidgetPlacement(
233+
split_region, null_offset, null_spacing, split_widget, 1, True
234+
)
230235
)
231236

232237
return placements, view_region

src/textual/_compositor.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from textual._cells import cell_len
3434
from textual._context import visible_screen_stack
3535
from textual._loop import loop_last
36-
from textual.geometry import NULL_OFFSET, NULL_SPACING, Offset, Region, Size, Spacing
36+
from textual.geometry import NULL_SPACING, Offset, Region, Size, Spacing
3737
from textual.map_geometry import MapGeometry
3838
from textual.strip import Strip, StripRenderable
3939

@@ -535,8 +535,6 @@ def _arrange_root(
535535
Compositor map and set of widgets.
536536
"""
537537

538-
ORIGIN = NULL_OFFSET
539-
540538
map: CompositorMap = {}
541539
widgets: set[Widget] = set()
542540
add_new_widget = widgets.add
@@ -580,15 +578,9 @@ def add_widget(
580578
add_new_widget(widget)
581579
else:
582580
add_new_invisible_widget(widget)
583-
styles_offset = styles.offset
584-
layout_offset = (
585-
styles_offset.resolve(region.size, clip.size)
586-
if styles_offset
587-
else ORIGIN
588-
)
589581

590582
# Container region is minus border
591-
container_region = region.shrink(styles.gutter).translate(layout_offset)
583+
container_region = region.shrink(styles.gutter)
592584
container_size = container_region.size
593585

594586
# Widgets with scrollbars (containers or scroll view) require additional processing
@@ -643,15 +635,25 @@ def add_widget(
643635
)
644636

645637
# Add all the widgets
646-
for sub_region, _, sub_widget, z, fixed, overlay in reversed(
647-
placements
648-
):
638+
for (
639+
sub_region,
640+
sub_region_offset,
641+
_,
642+
sub_widget,
643+
z,
644+
fixed,
645+
overlay,
646+
) in reversed(placements):
649647
layer_index = get_layer_index(sub_widget.layer, 0)
650648
# Combine regions with children to calculate the "virtual size"
651649
if fixed:
652-
widget_region = sub_region + placement_offset
650+
widget_region = (
651+
sub_region + sub_region_offset + placement_offset
652+
)
653653
else:
654-
widget_region = sub_region + placement_scroll_offset
654+
widget_region = (
655+
sub_region + sub_region_offset + placement_scroll_offset
656+
)
655657

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

@@ -699,7 +701,7 @@ def add_widget(
699701
)
700702

701703
map[widget] = _MapGeometry(
702-
region + layout_offset,
704+
region,
703705
order,
704706
clip,
705707
total_region.size,
@@ -711,27 +713,23 @@ def add_widget(
711713
elif visible:
712714
# Add the widget to the map
713715

714-
widget_region = region + layout_offset
715-
716716
if widget.absolute_offset is not None:
717717
margin = styles.margin
718-
widget_region = widget_region.at_offset(
719-
widget.absolute_offset + margin.top_left
720-
)
721-
widget_region = widget_region.translate(
722-
styles.offset.resolve(widget_region.grow(margin).size, size)
718+
region = region.at_offset(widget.absolute_offset + margin.top_left)
719+
region = region.translate(
720+
styles.offset.resolve(region.grow(margin).size, size)
723721
)
724722
has_rule = styles.has_rule
725723
if has_rule("constrain_x") or has_rule("constrain_y"):
726-
widget_region = widget_region.constrain(
724+
region = region.constrain(
727725
styles.constrain_x,
728726
styles.constrain_y,
729727
styles.margin,
730728
size.region,
731729
)
732730

733731
map[widget._render_widget] = _MapGeometry(
734-
widget_region,
732+
region,
735733
order,
736734
clip,
737735
region.size,

src/textual/_spatial_map.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from typing_extensions import TypeAlias
88

9-
from textual.geometry import Region
9+
from textual.geometry import Offset, Region
1010

1111
ValueType = TypeVar("ValueType")
1212
GridCoordinate: TypeAlias = "tuple[int, int]"
@@ -57,27 +57,27 @@ def _region_to_grid_coordinates(self, region: Region) -> Iterable[GridCoordinate
5757
)
5858

5959
def insert(
60-
self, regions_and_values: Iterable[tuple[Region, bool, bool, ValueType]]
60+
self, regions_and_values: Iterable[tuple[Region, Offset, bool, bool, ValueType]]
6161
) -> None:
6262
"""Insert values into the Spatial map.
6363
6464
Values are associated with their region in Euclidean space, and a boolean that
6565
indicates fixed regions. Fixed regions don't scroll and are always visible.
6666
6767
Args:
68-
regions_and_values: An iterable of (REGION, FIXED, OVERLAY, VALUE).
68+
regions_and_values: An iterable of (REGION, OFFSET, FIXED, OVERLAY, VALUE).
6969
"""
7070
append_fixed = self._fixed.append
7171
get_grid_list = self._map.__getitem__
7272
_region_to_grid = self._region_to_grid_coordinates
7373
total_region = self.total_region
74-
for region, fixed, overlay, value in regions_and_values:
74+
for region, offset, fixed, overlay, value in regions_and_values:
7575
if fixed:
7676
append_fixed(value)
7777
else:
7878
if not overlay:
7979
total_region = total_region.union(region)
80-
for grid in _region_to_grid(region):
80+
for grid in _region_to_grid(region + offset):
8181
get_grid_list(grid).append(value)
8282
self.total_region = total_region
8383

src/textual/layout.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def spatial_map(self) -> SpatialMap[WidgetPlacement]:
3939
self._spatial_map.insert(
4040
(
4141
placement.region.grow(placement.margin),
42+
placement.offset,
4243
placement.fixed,
4344
placement.overlay,
4445
placement,
@@ -75,7 +76,7 @@ def get_visible_placements(self, region: Region) -> list[WidgetPlacement]:
7576
culled_placements = [
7677
placement
7778
for placement in visible_placements
78-
if placement.fixed or overlaps(placement.region)
79+
if placement.fixed or overlaps(placement.region + placement.offset)
7980
]
8081
return culled_placements
8182

@@ -84,6 +85,7 @@ class WidgetPlacement(NamedTuple):
8485
"""The position, size, and relative order of a widget within its parent."""
8586

8687
region: Region
88+
offset: Offset
8789
margin: Spacing
8890
widget: Widget
8991
order: int = 0
@@ -92,7 +94,7 @@ class WidgetPlacement(NamedTuple):
9294

9395
@classmethod
9496
def translate(
95-
cls, placements: list[WidgetPlacement], offset: Offset
97+
cls, placements: list[WidgetPlacement], translate_offset: Offset
9698
) -> list[WidgetPlacement]:
9799
"""Move all placements by a given offset.
98100
@@ -103,10 +105,18 @@ def translate(
103105
Returns:
104106
Placements with adjusted region, or same instance if offset is null.
105107
"""
106-
if offset:
108+
if translate_offset:
107109
return [
108-
cls(region + offset, margin, layout_widget, order, fixed, overlay)
109-
for region, margin, layout_widget, order, fixed, overlay in placements
110+
cls(
111+
region + translate_offset,
112+
offset,
113+
margin,
114+
layout_widget,
115+
order,
116+
fixed,
117+
overlay,
118+
)
119+
for region, offset, margin, layout_widget, order, fixed, overlay in placements
110120
]
111121
return placements
112122

src/textual/layouts/grid.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from textual._resolve import resolve
77
from textual.css.scalar import Scalar
8-
from textual.geometry import Region, Size, Spacing
8+
from textual.geometry import NULL_OFFSET, Region, Size, Spacing
99
from textual.layout import ArrangeResult, Layout, WidgetPlacement
1010

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

260260
placements: list[WidgetPlacement] = []
261+
_WidgetPlacement = WidgetPlacement
261262
add_placement = placements.append
262263
widgets: list[Widget] = []
263264
add_widget = widgets.append
@@ -272,26 +273,34 @@ def apply_height_limits(widget: Widget, height: int) -> int:
272273
x2, cell_width = columns[min(max_column, column + column_span)]
273274
y2, cell_height = rows[min(max_row, row + row_span)]
274275
cell_size = Size(cell_width + x2 - x, cell_height + y2 - y)
275-
width, height, margin = widget._get_box_model(
276+
box_width, box_height, margin = widget._get_box_model(
276277
cell_size,
277278
viewport,
278279
Fraction(cell_size.width),
279280
Fraction(cell_size.height),
280281
)
281282
if self.stretch_height and len(children) > 1:
282-
height = (
283-
height
284-
if (height > cell_size.height)
285-
else Fraction(cell_size.height)
286-
)
283+
if box_height <= cell_size.height:
284+
box_height = Fraction(cell_size.height)
285+
287286
region = (
288-
Region(x, y, int(width + margin.width), int(height + margin.height))
287+
Region(
288+
x, y, int(box_width + margin.width), int(box_height + margin.height)
289+
)
289290
.crop_size(cell_size)
290291
.shrink(margin)
291292
)
293+
294+
placement_offset = (
295+
styles.offset.resolve(cell_size, viewport)
296+
if styles.has_rule("offset")
297+
else NULL_OFFSET
298+
)
299+
292300
add_placement(
293-
WidgetPlacement(
301+
_WidgetPlacement(
294302
region + offset,
303+
placement_offset,
295304
(
296305
margin
297306
if gutter_spacing is None

0 commit comments

Comments
 (0)