Skip to content

Commit

Permalink
Merge pull request #1627 from Textualize/fix-1616
Browse files Browse the repository at this point in the history
Changing overflow programmatically updates layout
  • Loading branch information
willmcgugan authored Jan 25, 2023
2 parents 82e2739 + b9b53b8 commit 5e0996d
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Fixed stuck screen https://github.com/Textualize/textual/issues/1632
- Fixed relative units in `grid-rows` and `grid-columns` being computed with respect to the wrong dimension https://github.com/Textualize/textual/issues/1406
- Programmatically setting `overflow_x`/`overflow_y` refreshes the layout correctly https://github.com/Textualize/textual/issues/1616
- Fixed double-paste into `Input` https://github.com/Textualize/textual/issues/1657

## [0.10.1] - 2023-01-20
Expand Down
15 changes: 15 additions & 0 deletions src/textual/css/_style_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,9 @@ def __get__(self, obj: StylesBase, objtype: type[StylesBase] | None = None) -> s
"""
return obj.get_rule(self.name, self._default)

def _before_refresh(self, obj: StylesBase, value: str | None) -> None:
"""Do any housekeeping before asking for a layout refresh after a value change."""

def __set__(self, obj: StylesBase, value: str | None = None):
"""Set the string property and ensure it is in the set of allowed values.
Expand All @@ -717,6 +720,7 @@ def __set__(self, obj: StylesBase, value: str | None = None):
_rich_traceback_omit = True
if value is None:
if obj.clear_rule(self.name):
self._before_refresh(obj, value)
obj.refresh(layout=self._layout)
else:
if value not in self._valid_values:
Expand All @@ -729,9 +733,20 @@ def __set__(self, obj: StylesBase, value: str | None = None):
),
)
if obj.set_rule(self.name, value):
self._before_refresh(obj, value)
obj.refresh(layout=self._layout)


class OverflowProperty(StringEnumProperty):
"""Descriptor for overflow styles that forces widgets to refresh scrollbars."""

def _before_refresh(self, obj: StylesBase, value: str | None) -> None:
from ..widget import Widget # Avoid circular import

if isinstance(obj.node, Widget):
obj.node._refresh_scrollbars()


class NameProperty:
"""Descriptor for getting and setting name properties."""

Expand Down
5 changes: 3 additions & 2 deletions src/textual/css/styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
NameListProperty,
NameProperty,
OffsetProperty,
OverflowProperty,
ScalarListProperty,
ScalarProperty,
SpacingProperty,
Expand Down Expand Up @@ -246,8 +247,8 @@ class StylesBase(ABC):

dock = DockProperty()

overflow_x = StringEnumProperty(VALID_OVERFLOW, "hidden")
overflow_y = StringEnumProperty(VALID_OVERFLOW, "hidden")
overflow_x = OverflowProperty(VALID_OVERFLOW, "hidden")
overflow_y = OverflowProperty(VALID_OVERFLOW, "hidden")

layer = NameProperty()
layers = NameListProperty()
Expand Down
12 changes: 6 additions & 6 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from asyncio import Lock, wait
from asyncio import Lock, create_task, wait
from collections import Counter
from fractions import Fraction
from itertools import islice
Expand Down Expand Up @@ -922,7 +921,7 @@ def _refresh_scrollbars(self) -> None:
show_horizontal = self.show_horizontal_scrollbar
if overflow_x == "hidden":
show_horizontal = False
if overflow_x == "scroll":
elif overflow_x == "scroll":
show_horizontal = True
elif overflow_x == "auto":
show_horizontal = self.virtual_size.width > width
Expand Down Expand Up @@ -1974,13 +1973,14 @@ def _get_scrollable_region(self, region: Region) -> Region:
"""
show_vertical_scrollbar, show_horizontal_scrollbar = self.scrollbars_enabled

scrollbar_size_horizontal = self.styles.scrollbar_size_horizontal
scrollbar_size_vertical = self.styles.scrollbar_size_vertical
styles = self.styles
scrollbar_size_horizontal = styles.scrollbar_size_horizontal
scrollbar_size_vertical = styles.scrollbar_size_vertical

if self.styles.scrollbar_gutter == "stable":
if styles.scrollbar_gutter == "stable":
# Let's _always_ reserve some space, whether the scrollbar is actually displayed or not:
show_vertical_scrollbar = True
scrollbar_size_vertical = self.styles.scrollbar_size_vertical
scrollbar_size_vertical = styles.scrollbar_size_vertical

if show_horizontal_scrollbar and show_vertical_scrollbar:
(region, _, _, _) = region.split(
Expand Down
37 changes: 37 additions & 0 deletions tests/test_overflow_change.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Regression test for #1616 https://github.com/Textualize/textual/issues/1616"""
import pytest


from textual.app import App
from textual.containers import Vertical


async def test_overflow_change_updates_virtual_size_appropriately():
class MyApp(App):
def compose(self):
yield Vertical()

app = MyApp()

async with app.run_test() as pilot:
vertical = app.query_one(Vertical)

height = vertical.virtual_size.height

vertical.styles.overflow_x = "scroll"
await pilot.pause() # Let changes propagate.
assert vertical.virtual_size.height < height

vertical.styles.overflow_x = "hidden"
await pilot.pause()
assert vertical.virtual_size.height == height

width = vertical.virtual_size.width

vertical.styles.overflow_y = "scroll"
await pilot.pause()
assert vertical.virtual_size.width < width

vertical.styles.overflow_y = "hidden"
await pilot.pause()
assert vertical.virtual_size.width == width

0 comments on commit 5e0996d

Please sign in to comment.