mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Animator docs, wait syntax in app.run keys
This commit is contained in:
19
docs/examples/guide/animator/animation01.py
Normal file
19
docs/examples/guide/animator/animation01.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
|
class AnimationApp(App):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
self.box = Static("Hello, World!")
|
||||||
|
self.box.styles.background = "red"
|
||||||
|
self.box.styles.color = "black"
|
||||||
|
self.box.styles.padding = (1, 2)
|
||||||
|
yield self.box
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.box.styles.animate("opacity", value=0.0, duration=2.0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = AnimationApp()
|
||||||
|
app.run()
|
||||||
16
docs/examples/guide/animator/animation01_static.py
Normal file
16
docs/examples/guide/animator/animation01_static.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
|
class AnimationApp(App):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
self.box = Static("Hello, World!")
|
||||||
|
self.box.styles.background = "red"
|
||||||
|
self.box.styles.color = "black"
|
||||||
|
self.box.styles.padding = (1, 2)
|
||||||
|
yield self.box
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = AnimationApp()
|
||||||
|
app.run()
|
||||||
19
docs/examples/guide/animator/animation02.py
Normal file
19
docs/examples/guide/animator/animation02.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
|
class AnimationApp(App):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
self.box = Static("Hello, World!")
|
||||||
|
self.box.styles.background = "red"
|
||||||
|
self.box.styles.color = "black"
|
||||||
|
self.box.styles.padding = (1, 2)
|
||||||
|
yield self.box
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.box.styles.animate("opacity", value=0.0, duration=2.0, easing="linear")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = AnimationApp()
|
||||||
|
app.run()
|
||||||
21
docs/examples/guide/animator/animation03.py
Normal file
21
docs/examples/guide/animator/animation03.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
|
class AnimationApp(App):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
self.box = Static("Hello, World!")
|
||||||
|
self.box.styles.background = "red"
|
||||||
|
self.box.styles.color = "black"
|
||||||
|
self.box.styles.padding = (1, 2)
|
||||||
|
yield self.box
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.box.styles.animate(
|
||||||
|
"opacity", value=0.0, duration=2.0, on_complete=self.bell
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = AnimationApp()
|
||||||
|
app.run()
|
||||||
@@ -1,3 +1,100 @@
|
|||||||
# Animator
|
# Animator
|
||||||
|
|
||||||
TODO: Animator docs
|
Textual ships with an easy-to-use system which lets you add animation to your application.
|
||||||
|
To get a feel for what animation looks like in Textual and try out different easing functions, run `textual easing` in your terminal.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
The easing preview requires the `dev` extras (using `pip install textual[dev]`).
|
||||||
|
|
||||||
|
## Animating styles
|
||||||
|
|
||||||
|
The animator allows you to easily animate the attributes of a widget, including the `styles`.
|
||||||
|
This means you can animate attributes such as `offset` to move widgets around,
|
||||||
|
and `opacity` to create "fading" effects.
|
||||||
|
|
||||||
|
To animate something, you need a reference to an "animator".
|
||||||
|
Conveniently, you can obtain an animator via the `animate` property on `App`, `Widget` and `RenderStyles` (the type of `widget.styles`).
|
||||||
|
|
||||||
|
Let's look at an example of how we can animate the opacity of a widget to make it fade out.
|
||||||
|
The app below contains a single `Static` widget which is immediately animated to an opacity of `0.0` over a duration of two seconds.
|
||||||
|
|
||||||
|
```python hl_lines="14"
|
||||||
|
--8<-- "docs/examples/guide/animator/animation01.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
Internally, the animator deals with updating the value of the `opacity` attribute
|
||||||
|
on the `styles` object.
|
||||||
|
In a single line, we've achieved a fading animation:
|
||||||
|
|
||||||
|
|
||||||
|
=== "After 0s"
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/guide/animator/animation01_static.py"}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "After 1s"
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/guide/animator/animation01.py" press="wait:1000"}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "After 2s"
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/guide/animator/animation01.py" press="wait:2100"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember, when the value of a property on the `styles` object gets updated, Textual automatically updates the display.
|
||||||
|
This means there's no additional code required to trigger a display update.
|
||||||
|
|
||||||
|
In the example above we specified a `duration` of two seconds, but you can alternatively pass in a `speed` value.
|
||||||
|
|
||||||
|
## Animating other attributes
|
||||||
|
|
||||||
|
You can animate non-style attributes on widgets too.
|
||||||
|
This could be used to drive more complex animations involving styles, or to keep animations in sync with each other.
|
||||||
|
Again, the animation system will take care of updating the attribute on the widget as time progresses.
|
||||||
|
|
||||||
|
If the attribute being animated is [reactive](./reactivity.md), Textual can handle the refreshing of the display each time the animator updates the value.
|
||||||
|
|
||||||
|
## Animating arbitrary values
|
||||||
|
|
||||||
|
Sometimes, you'll want to animate a value that isn't directly accessible as an attribute on a widget.
|
||||||
|
For example, perhaps the value to be animated is nested inside some object structure, and you don't want to restructure your code to make it a top-level attribute.
|
||||||
|
|
||||||
|
In these cases, you can make use of an "unbound" animator.
|
||||||
|
These are animators which aren't pre-emptively associated with an object.
|
||||||
|
They let you pass in an object, _and_ the name of the attribute you wish to animate on it.
|
||||||
|
This is unlike the animators discussed above, which are already _bound_ to the object they were retrieved from.
|
||||||
|
|
||||||
|
## Easing functions
|
||||||
|
|
||||||
|
Easing functions control the "look and feel" of an animation.
|
||||||
|
The easing function determines the journey a value takes on its way to the target value.
|
||||||
|
Perhaps the value will be transformed linearly, moving towards the target at a constant rate.
|
||||||
|
Or maybe it'll start off slow, then accelerate towards the final value as the animation progresses.
|
||||||
|
|
||||||
|
Textual supports the easing functions listed on this [very helpful page](https://easings.net/).
|
||||||
|
In order to use them, you'll need to write them as `snake_case` and remove the `ease` at the start.
|
||||||
|
To use `easeInOutSine`, for example, you'll write `in_out_sine`.
|
||||||
|
|
||||||
|
The example below shows how we can use the `linear` easing function to ensure our box fades out at a constant rate.
|
||||||
|
|
||||||
|
```python hl_lines="14"
|
||||||
|
--8<-- "docs/examples/guide/animator/animation02.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the only change we had to make was to pass `easing="linear"` into the `animate` method.
|
||||||
|
|
||||||
|
You can preview the different easing functions by running `textual easing`, and clicking the buttons on the left of the window.
|
||||||
|
|
||||||
|
## Completion callbacks
|
||||||
|
|
||||||
|
To run some code when the animation completes, you can pass a callable object as the `on_complete` argument to the `animate` method.
|
||||||
|
Here's how we might extend the example above to ring the terminal bell when the animation ends:
|
||||||
|
|
||||||
|
```python hl_lines="14"
|
||||||
|
--8<-- "docs/examples/guide/animator/animation03.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
Awaitable callbacks are also supported.
|
||||||
|
If the callback passed to `on_complete` is awaitable, then Textual will await it for you.
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ We'll also add a slight tint using `tint: magenta 40%;` to draw attention to it.
|
|||||||
|
|
||||||
=== "grid_layout5_col_span.py"
|
=== "grid_layout5_col_span.py"
|
||||||
|
|
||||||
```python
|
```python
|
||||||
--8<-- "docs/examples/guide/layout/grid_layout5_col_span.py"
|
--8<-- "docs/examples/guide/layout/grid_layout5_col_span.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -544,7 +544,7 @@ The example below shows how an advanced layout can be built by combining the var
|
|||||||
|
|
||||||
=== "combining_layouts.css"
|
=== "combining_layouts.css"
|
||||||
|
|
||||||
```sass
|
```sass
|
||||||
--8<-- "docs/examples/guide/layout/combining_layouts.css"
|
--8<-- "docs/examples/guide/layout/combining_layouts.css"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -663,8 +663,12 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
await asyncio.sleep(0.01)
|
await asyncio.sleep(0.01)
|
||||||
for key in press:
|
for key in press:
|
||||||
if key == "_":
|
if key == "_":
|
||||||
print("(pause)")
|
print("(pause 50ms)")
|
||||||
await asyncio.sleep(0.05)
|
await asyncio.sleep(0.05)
|
||||||
|
elif key.startswith("wait:"):
|
||||||
|
_, wait_ms = key.split(":")
|
||||||
|
print(f"(pause {wait_ms}ms)")
|
||||||
|
await asyncio.sleep(float(wait_ms) / 1000)
|
||||||
else:
|
else:
|
||||||
print(f"press {key!r}")
|
print(f"press {key!r}")
|
||||||
driver.send_event(
|
driver.send_event(
|
||||||
|
|||||||
Reference in New Issue
Block a user