more tests of animation

This commit is contained in:
Will McGugan
2022-02-21 16:26:46 +00:00
parent 73a1b5377c
commit 8fa6749fdd
2 changed files with 121 additions and 30 deletions

View File

@@ -98,7 +98,7 @@ class BoundAnimator:
easing: EasingFunction | str = DEFAULT_EASING,
) -> None:
easing_function = EASING[easing] if isinstance(easing, str) else easing
self._animator.animate(
return self._animator.animate(
self._obj,
attribute=attribute,
value=value,
@@ -122,6 +122,10 @@ class Animator:
pause=True,
)
def get_time(self) -> float:
"""Get the current wall clock time."""
return time()
async def start(self) -> None:
"""Start the animator task."""
@@ -163,7 +167,7 @@ class Animator:
if final_value is ...:
final_value = value
start_time = time()
start_time = self.get_time()
animation_key = (id(obj), attribute)
if animation_key in self._animations:
@@ -207,15 +211,18 @@ class Animator:
self._animations[animation_key] = animation
self._timer.resume()
async def __call__(self) -> None:
def __call__(self) -> None:
if not self._animations:
self._timer.pause()
else:
animation_time = time()
animation_time = self.get_time()
animation_keys = list(self._animations.keys())
for animation_key in animation_keys:
animation = self._animations[animation_key]
if animation(animation_time):
del self._animations[animation_key]
# TODO: We should be able to do animation without refreshing everything
self.target.view.refresh(True, True)
self.on_animation_frame()
def on_animation_frame(self) -> None:
# TODO: We should be able to do animation without refreshing everything
self.target.view.refresh(True, True)

View File

@@ -7,7 +7,8 @@ from unittest.mock import Mock
import pytest
from textual._animator import SimpleAnimation
from textual._animator import Animator, SimpleAnimation
from textual._easing import EASING, DEFAULT_EASING
class Animatable:
@@ -24,7 +25,7 @@ class Animatable:
class AnimateTest:
"""An object with animatable properties."""
foo: float | None = 0 # Plain float that may be set to None on final_value
foo: float | None = 0.0 # Plain float that may be set to None on final_value
bar: Animatable = Animatable(0) # A mock object supporting the animatable protocol
@@ -32,14 +33,14 @@ def test_simple_animation():
"""Test an animation from one float to another."""
# Thing that may be animated
animatable = AnimateTest()
animate_test = AnimateTest()
# Fake wall-clock time
time = 100.0
# Object that does the animation
animation = SimpleAnimation(
animatable,
animate_test,
"foo",
time,
3.0,
@@ -49,25 +50,25 @@ def test_simple_animation():
easing=lambda x: x,
)
assert animatable.foo == 0.0
assert animate_test.foo == 0.0
assert animation(time) is False
assert animatable.foo == 20.0
assert animate_test.foo == 20.0
assert animation(time + 1.0) is False
assert animatable.foo == 30.0
assert animate_test.foo == 30.0
assert animation(time + 2.0) is False
assert animatable.foo == 40.0
assert animate_test.foo == 40.0
assert animation(time + 2.9) is False # Not quite final value
assert pytest.approx(animatable.foo, 49.0)
assert pytest.approx(animate_test.foo, 49.0)
assert animation(time + 3.0) is True # True to indicate animation is complete
assert animatable.foo is None # This is final_value
assert animate_test.foo is None # This is final_value
assert animation(time + 3.0) is True
assert animatable.foo is None
assert animate_test.foo is None
def test_simple_animation_duration_zero():
@@ -102,14 +103,14 @@ def test_simple_animation_reverse():
"""Test an animation from one float to another, where the end value is less than the start."""
# Thing that may be animated
animatable = AnimateTest()
animate_Test = AnimateTest()
# Fake wall-clock time
time = 100.0
# Object that does the animation
animation = SimpleAnimation(
animatable,
animate_Test,
"foo",
time,
3.0,
@@ -120,29 +121,29 @@ def test_simple_animation_reverse():
)
assert animation(time) is False
assert animatable.foo == 50.0
assert animate_Test.foo == 50.0
assert animation(time + 1.0) is False
assert animatable.foo == 40.0
assert animate_Test.foo == 40.0
assert animation(time + 2.0) is False
assert animatable.foo == 30.0
assert animate_Test.foo == 30.0
assert animation(time + 3.0) is True
assert animatable.foo == 20.0
assert animate_Test.foo == 20.0
def test_animatable():
"""Test SimpleAnimation works with the Animatable protocol"""
animatable = AnimateTest()
animate_test = AnimateTest()
# Fake wall-clock time
time = 100.0
# Object that does the animation
animation = SimpleAnimation(
animatable,
animate_test,
"bar",
time,
3.0,
@@ -153,16 +154,99 @@ def test_animatable():
)
assert animation(time) is False
assert animatable.bar.value == 20.0
assert animate_test.bar.value == 20.0
assert animation(time + 1.0) is False
assert animatable.bar.value == 30.0
assert animate_test.bar.value == 30.0
assert animation(time + 2.0) is False
assert animatable.bar.value == 40.0
assert animate_test.bar.value == 40.0
assert animation(time + 2.9) is False
assert pytest.approx(animatable.bar.value, 49.0)
assert pytest.approx(animate_test.bar.value, 49.0)
assert animation(time + 3.0) is True # True to indicate animation is complete
assert animatable.bar.value == 50.0
assert animate_test.bar.value == 50.0
class TestAnimator(Animator):
"""A mock animator."""
def __init__(self, *args) -> None:
super().__init__(*args)
self._time = 0.0
def get_time(self):
return self._time
def on_animation_frame(self):
self._on_animation_frame_called = True
def test_animator():
target = Mock()
animator = TestAnimator(target)
animate_test = AnimateTest()
# Animate attribute "foo" on animate_test to 100.0 in 10 seconds
animator.animate(animate_test, "foo", 100.0, duration=10.0)
expected = SimpleAnimation(
animate_test,
"foo",
0.0,
duration=10.0,
start_value=0.0,
end_value=100.0,
final_value=100.0,
easing=EASING[DEFAULT_EASING],
)
assert animator._animations[(id(animate_test), "foo")] == expected
animator()
assert animate_test.foo == 0
animator._time = 5
animator()
assert animate_test.foo == 50
# New animation in the middle of an existing one
animator.animate(animate_test, "foo", 200, duration=1)
assert animate_test.foo == 50
animator._time = 6
animator()
assert animate_test.foo == 200
def test_bound_animator():
target = Mock()
animator = TestAnimator(target)
animate_test = AnimateTest()
# Bind an animator so it animates attributes on the given object
bound_animator = animator.bind(animate_test)
# Animate attribute "foo" on animate_test to 100.0 in 10 seconds
bound_animator("foo", 100.0, duration=10)
expected = SimpleAnimation(
animate_test,
"foo",
0,
duration=10,
start_value=0,
end_value=100,
final_value=100,
easing=EASING[DEFAULT_EASING],
)
assert animator._animations[(id(animate_test), "foo")] == expected
def test_animator_get_time():
target = Mock()
animator = Animator(target)
assert isinstance(animator.get_time(), float)
assert animator.get_time() <= animator.get_time()