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: scalar animation with percentages #3004

7 changes: 4 additions & 3 deletions src/textual/css/scalar_animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ def __init__(
self.easing = easing
self.on_complete = on_complete

size = widget.outer_size
container_size = widget.parent.size
viewport = widget.app.size

self.start = getattr(styles, attribute).resolve(size, viewport)
self.destination = value.resolve(size, viewport)
# Resolve scalar units to a start and destination size in cells
self.start = getattr(styles, attribute).resolve(container_size, viewport)
self.destination = value.resolve(container_size, viewport)

if speed is not None:
distance = self.start.get_distance_to(self.destination)
Expand Down
50 changes: 50 additions & 0 deletions tests/test_animation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from time import perf_counter

from textual.app import App, ComposeResult
from textual.containers import Container
from textual.reactive import var
from textual.widgets import Static

Expand Down Expand Up @@ -160,6 +161,55 @@ async def test_schedule_reverse_animations() -> None:
assert styles.background.rgb == (0, 0, 0)


async def test_scalar_animation_with_percentages() -> None:
"""Regression test for #2940: https://github.com/Textualize/textual/issues/2940

Previously the scalar animation `start` and `destination` values were
resolved based on the size of the widget itself, rather than the size of the
parent container.

Ideally this test would check the widget size at certain intervals during
the animation, but this has proven to be very flaky in CI. Instead this
simply tests that during the animation the size is within the correct range,
which fails before the patch in PR #3004.
"""

class ScalarPercentAnimApp(App):
CSS = """
Container {
width: 10;
}

Static {
width: 20%;
}
"""

def compose(self) -> ComposeResult:
with Container():
yield Static()

app = ScalarPercentAnimApp()

async with app.run_test() as pilot:
static = app.query_one(Static)
# Sanity check
assert static.size.width == 2
assert static.styles.width.value == 20

static.styles.animate("width", "80%", duration=0.6, easing="linear")
start = perf_counter()

await pilot.pause(0.3)
assert static.size.width in range(3, 8)

await pilot.wait_for_animation()
elapsed = perf_counter() - start
assert elapsed >= 0.6
assert static.size.width == 8
assert static.styles.width.value == 80


class CancelAnimWidget(Static):
counter: var[float] = var(23)

Expand Down
Loading