Skip to content

Commit e555fc0

Browse files
committed
animator interface
1 parent 9371bd1 commit e555fc0

File tree

4 files changed

+61
-18
lines changed

4 files changed

+61
-18
lines changed

src/textual/_animator.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,28 @@ def __call__(self, obj: object, time: float) -> bool:
4343
return value == self.end_value
4444

4545

46+
class BoundAnimator:
47+
def __init__(self, animator: Animator, obj: object) -> None:
48+
self._animator = animator
49+
self._obj = obj
50+
51+
def __call__(
52+
self,
53+
attribute: str,
54+
value: float,
55+
*,
56+
duration: float = 1,
57+
easing: EasingFunction = InOutCubitEasing,
58+
) -> None:
59+
self._animator.animate(
60+
self._obj,
61+
attribute=attribute,
62+
value=value,
63+
duration=duration,
64+
easing=easing,
65+
)
66+
67+
4668
class Animator:
4769
def __init__(self, target: MessageTarget) -> None:
4870
self._animations: dict[tuple[object, str], Animation] = {}
@@ -54,11 +76,15 @@ async def start(self) -> None:
5476
async def stop(self) -> None:
5577
self._timer.stop()
5678

79+
def bind(self, obj: object) -> BoundAnimator:
80+
return BoundAnimator(self, obj)
81+
5782
def animate(
5883
self,
5984
obj: object,
6085
attribute: str,
6186
value: float,
87+
*,
6288
duration: float = 1,
6389
easing: EasingFunction = InOutCubitEasing,
6490
) -> None:
@@ -75,13 +101,16 @@ def animate(
75101
easing_function=easing,
76102
)
77103
self._animations[(obj, attribute)] = animation
104+
self._timer.resume()
78105

79106
async def __call__(self) -> None:
80-
log.debug("ANIMATION FRAME")
81-
animation_time = 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]
107+
if not self._animations:
108+
self._timer.pause()
109+
else:
110+
animation_time = time()
111+
animation_keys = list(self._animations.keys())
112+
for animation_key in animation_keys:
113+
animation = self._animations[animation_key]
114+
obj, _attribute = animation_key
115+
if animation(obj, animation_time):
116+
del self._animations[animation_key]

src/textual/_timer.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def __init__(
4040
self._callback = callback
4141
self._repeat = repeat
4242
self._stop_event = Event()
43+
self._active = Event()
4344

4445
def __rich_repr__(self) -> RichReprResult:
4546
yield self._interval
@@ -54,19 +55,21 @@ def target(self) -> MessageTarget:
5455
return target
5556

5657
def stop(self) -> None:
58+
self._active.set()
5759
self._stop_event.set()
5860

59-
async def pause(self) -> None:
60-
pass
61+
def pause(self) -> None:
62+
self._active.clear()
6163

62-
async def resume(self) -> None:
63-
pass
64+
def resume(self) -> None:
65+
self._active.set()
6466

6567
async def run(self) -> None:
6668
count = 0
6769
_repeat = self._repeat
6870
_interval = self._interval
6971
_wait = self._stop_event.wait
72+
_wait_active = self._active.wait
7073
start = monotonic()
7174
try:
7275
while _repeat is None or count <= _repeat:
@@ -84,5 +87,6 @@ async def run(self) -> None:
8487
except EventTargetGone:
8588
break
8689
count += 1
90+
await _wait_active()
8791
except CancelledError:
8892
pass

src/textual/widget.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from rich.style import Style
2121

2222
from . import events
23+
from ._animator import BoundAnimator
2324
from ._context import active_app
2425
from ._loop import loop_last
2526
from ._line_cache import LineCache
@@ -112,6 +113,7 @@ def __init__(self, name: str | None = None) -> None:
112113
self.size = Dimensions(0, 0)
113114
self.size_changed = False
114115
self._repaint_required = False
116+
self._animate: BoundAnimator | None = None
115117

116118
super().__init__()
117119
# self.disable_messages(events.MouseMove)
@@ -139,6 +141,13 @@ def console(self) -> Console:
139141
"""Get the current console."""
140142
return active_app.get().console
141143

144+
@property
145+
def animate(self) -> BoundAnimator:
146+
if self._animate is None:
147+
self._animate = self.app.animator.bind(self)
148+
assert self._animate is not None
149+
return self._animate
150+
142151
def require_repaint(self) -> None:
143152
"""Mark widget as requiring a repaint.
144153

src/textual/widgets/scroll_view.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,12 @@ async def on_mouse_scroll_down(self, event: events.MouseScrollUp) -> None:
6464
self._vertical_scrollbar.position -= 1
6565

6666
async def on_key(self, event: events.Key) -> None:
67-
if event.key == "down":
67+
key = event.key
68+
if key == "down":
6869
self.y += 1
69-
elif event.key == "up":
70+
elif key == "up":
7071
self.y -= 1
71-
elif event.key == "pagedown":
72-
self.app.animator.animate(self, "y", self.y + self.size.height, duration=1)
73-
elif event.key == "pageup":
74-
self.app.animator.animate(self, "y", self.y - self.size.height, duration=1)
72+
elif key == "pagedown":
73+
self.animate("y", self.y + self.size.height)
74+
elif key == "pageup":
75+
self.animate("y", self.y - self.size.height)

0 commit comments

Comments
 (0)