Skip to content

Commit 9371bd1

Browse files
committed
app animator
1 parent 1feec2e commit 9371bd1

File tree

5 files changed

+43
-26
lines changed

5 files changed

+43
-26
lines changed
Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
from __future__ import annotations
22

33
import logging
4-
from .message_pump import MessagePump
5-
from ._timer import Timer
4+
5+
import asyncio
66
from time import time
77
from typing import Callable
88

99
from dataclasses import dataclass
1010

11+
from ._timer import Timer
12+
from ._types import MessageTarget
13+
from .message_pump import MessagePump
1114

1215
EasingFunction = Callable[[float], float]
1316

17+
1418
LinearEasing = lambda value: value
1519

1620

@@ -23,6 +27,7 @@ def InOutCubitEasing(x: float) -> float:
2327

2428
@dataclass
2529
class Animation:
30+
obj: object
2631
attribute: str
2732
start_time: float
2833
duration: float
@@ -39,43 +44,44 @@ def __call__(self, obj: object, time: float) -> bool:
3944

4045

4146
class Animator:
42-
def __init__(self, obj: MessagePump) -> None:
43-
self.obj = obj
44-
self._animations: dict[str, Animation] = {}
45-
self._timer: Timer | None = None
47+
def __init__(self, target: MessageTarget) -> None:
48+
self._animations: dict[tuple[object, str], Animation] = {}
49+
self._timer: Timer = Timer(target, 1 / 30, target, callback=self)
50+
51+
async def start(self) -> None:
52+
asyncio.get_event_loop().create_task(self._timer.run())
53+
54+
async def stop(self) -> None:
55+
self._timer.stop()
4656

4757
def animate(
4858
self,
59+
obj: object,
4960
attribute: str,
5061
value: float,
5162
duration: float = 1,
5263
easing: EasingFunction = InOutCubitEasing,
5364
) -> None:
54-
start_value = getattr(self.obj, attribute)
65+
start_value = getattr(obj, attribute)
5566
start_time = time()
5667

5768
animation = Animation(
69+
obj,
5870
attribute=attribute,
5971
start_time=start_time,
6072
duration=duration,
6173
start_value=start_value,
6274
end_value=value,
6375
easing_function=easing,
6476
)
65-
self._animations[attribute] = animation
66-
if self._timer is None:
67-
self._timer = self.obj.set_interval(0.02, callback=self)
77+
self._animations[(obj, attribute)] = animation
6878

6979
async def __call__(self) -> None:
7080
log.debug("ANIMATION FRAME")
7181
animation_time = time()
72-
if all(
73-
animation(self.obj, animation_time)
74-
for animation in self._animations.values()
75-
):
76-
if self._timer is not None:
77-
self._timer.stop()
78-
self._timer = None
79-
80-
for _attribute, animation in self._animations.items():
81-
animation(self.obj, animation_time)
82+
animation_keys = list(self._animations.keys())
83+
for animation_key in animation_keys:
84+
animation = self._animations[animation_key]
85+
obj, _attribute = animation_key
86+
if animation(obj, animation_time):
87+
del self._animations[animation_key]

src/textual/_timer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ def target(self) -> MessageTarget:
5656
def stop(self) -> None:
5757
self._stop_event.set()
5858

59+
async def pause(self) -> None:
60+
pass
61+
62+
async def resume(self) -> None:
63+
pass
64+
5965
async def run(self) -> None:
6066
count = 0
6167
_repeat = self._repeat

src/textual/app.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from . import events
1818
from . import actions
19+
from ._animator import Animator
1920
from ._context import active_app
2021
from .driver import Driver
2122
from ._linux_driver import LinuxDriver
@@ -75,13 +76,18 @@ def __init__(
7576
self._driver: Driver | None = None
7677

7778
self._action_targets = {"app": self, "view": self.view}
79+
self._animator = Animator(self)
7880

7981
def __rich_repr__(self) -> rich.repr.RichReprResult:
8082
yield "title", self.title
8183

8284
def __rich__(self) -> RenderableType:
8385
return self.view
8486

87+
@property
88+
def animator(self) -> Animator:
89+
return self._animator
90+
8591
@classmethod
8692
def run(
8793
cls, console: Console = None, screen: bool = True, driver: Type[Driver] = None
@@ -180,7 +186,9 @@ async def _process_messages(self) -> None:
180186
except Exception:
181187
log.exception("error starting application mode")
182188
raise
189+
await self.animator.start()
183190
await super().process_messages()
191+
await self.animator.stop()
184192

185193
await self.view.close_messages()
186194
driver.stop_application_mode()

src/textual/widget.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
ClassVar,
77
Generic,
88
Iterable,
9-
NamedTuple,
109
TypeVar,
1110
TYPE_CHECKING,
1211
)
@@ -21,7 +20,6 @@
2120
from rich.style import Style
2221

2322
from . import events
24-
from .animator import Animator
2523
from ._context import active_app
2624
from ._loop import loop_last
2725
from ._line_cache import LineCache
@@ -116,7 +114,6 @@ def __init__(self, name: str | None = None) -> None:
116114
self._repaint_required = False
117115

118116
super().__init__()
119-
self._animator = Animator(self)
120117
# self.disable_messages(events.MouseMove)
121118

122119
def __init_subclass__(

src/textual/widgets/scroll_view.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def __init__(
3838
y: Reactive[float] = Reactive(0)
3939

4040
def validate_y(self, value: float) -> int:
41-
return max(0, value)
41+
return max(0, round(value))
4242

4343
def update_y(self, old_value: float, new_value: float) -> None:
4444
self._page.y = int(new_value)
@@ -69,6 +69,6 @@ async def on_key(self, event: events.Key) -> None:
6969
elif event.key == "up":
7070
self.y -= 1
7171
elif event.key == "pagedown":
72-
self._animator.animate("y", self.y + self.size.height, duration=1)
72+
self.app.animator.animate(self, "y", self.y + self.size.height, duration=1)
7373
elif event.key == "pageup":
74-
self._animator.animate("y", self.y - self.size.height, duration=1)
74+
self.app.animator.animate(self, "y", self.y - self.size.height, duration=1)

0 commit comments

Comments
 (0)