Skip to content

fix: scalar animation with percentages #3004

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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