Finish up Animator docs first pass

This commit is contained in:
Darren Burns
2022-10-06 14:11:08 +01:00
parent ded9d39054
commit 533ec72029
2 changed files with 81 additions and 16 deletions

View File

@@ -0,0 +1,29 @@
from rich.console import RenderableType
from textual.app import App, ComposeResult
from textual.reactive import reactive
from textual.widget import Widget
class ValueBox(Widget):
value = reactive(0.0)
def render(self) -> RenderableType:
return str(self.value)
class AnimationApp(App):
def compose(self) -> ComposeResult:
self.box = ValueBox()
self.box.styles.background = "red"
self.box.styles.color = "black"
self.box.styles.padding = (1, 2)
yield self.box
async def on_mount(self):
self.box.animate("value", value=100.0, duration=100.0, easing="linear")
if __name__ == "__main__":
app = AnimationApp()
app.run()

View File

@@ -1,11 +1,11 @@
# Animator # Animator
Textual ships with an easy-to-use system which lets you add animation to your application. 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. To get a feel for what animation looks like in Textual, run `textual easing` from the command line.
!!! note !!! note
The easing preview requires the `dev` extras (using `pip install textual[dev]`). The `textual easing` preview requires the `dev` extras to be installed (using `pip install textual[dev]`).
## Animating styles ## Animating styles
@@ -23,9 +23,8 @@ The app below contains a single `Static` widget which is immediately animated to
--8<-- "docs/examples/guide/animator/animation01.py" --8<-- "docs/examples/guide/animator/animation01.py"
``` ```
Internally, the animator deals with updating the value of the `opacity` attribute on the `styles` object. Internally, the animator repeatedly updates the value of the `opacity` attribute on the `styles` object.
In a single line, we've achieved a fading animation: With a single line of code, we've achieved a fading animation:
=== "After 0s" === "After 0s"
@@ -43,28 +42,56 @@ In a single line, we've achieved a fading animation:
``` ```
Remember, when the value of a property on the `styles` object gets updated, Textual automatically updates the display. 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. This means there's no additional code required to trigger a display update - the animation just works.
In the example above we specified a `duration` of two seconds, but you can alternatively pass in a `speed` value. In the example above we specified a `duration` of two seconds, but you can alternatively pass in a `speed` value.
## Animating other attributes ## The `Animatable` protocol
You can animate non-style attributes on widgets too. You can animate `float` values and any type which implements the `Animatable` protocol.
This could be used to drive more complex animations involving styles, or to keep animations in sync with each other.
To implement the `Animatable` protocol, add a `def blend(self: T, destination: T, factor: float) -> T` method to the class.
The `blend` method should return a new object which represents `self` blended with `destination` by a factor of `factor`.
The animator will repeatedly call this method to retrieve the current value to display for the current.
An example of an object which implements this protocol is [Color][textual.color.Color].
It follows that you can use `animate` to animate from one `Color` to another.
## Animating widget attributes
You can animate non-`style` attributes on widgets too, assuming they implement `Animatable`.
Again, the animation system will take care of updating the attribute on the widget as time progresses. 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. If the attribute being animated is [reactive](./reactivity.md), Textual can refresh the display each time the animator updates the value.
## Animating arbitrary values The example below shows a simple incrementing timer that counts from 0 to 100 over 100 seconds.
Sometimes, you'll want to animate a value that isn't directly accessible as an attribute on a widget. === "animation04.py"
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. ```python
These are animators which aren't pre-emptively associated with an object. --8<-- "docs/examples/guide/animator/animation04.py"
They let you pass in an object, _and_ the name of the attribute you wish to animate on it. ```
=== "Output"
```{.textual path="docs/examples/guide/animator/animation04.py"}
```
Since `value` is reactive, the display is automatically updated each time the animator modifies it.
## Animating Python object attributes
Sometimes you'll want to animate a value that exists inside a plain old Python object.
In these cases, you can make use of the "unbound" animator.
An unbound animator is an animator which isn't pre-emptively associated with (bound to) an object.
Unbound animators let you pass the name of the attribute you wish to animate, _and_ the object that attribute exists on.
This is unlike the animators discussed above, which are already _bound_ to the object they were retrieved from. This is unlike the animators discussed above, which are already _bound_ to the object they were retrieved from.
You can retrieve the unbound animator from the `App` instance via `App.animator`, and call the `animate` method on it.
This method is the same as the one described earlier, except the first argument is the object containing the attribute.
## Easing functions ## Easing functions
Easing functions control the "look and feel" of an animation. Easing functions control the "look and feel" of an animation.
@@ -72,6 +99,15 @@ The easing function determines the journey a value takes on its way to the targe
Perhaps the value will be transformed linearly, moving towards the target at a constant rate. 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. Or maybe it'll start off slow, then accelerate towards the final value as the animation progresses.
Easing functions take a single input representing the time, and output a "factor".
This factor is what gets passed to the `blend` method in the `Animatable` protocol.
!!! warning
The factor output by the easing function will usually remain between 0 and 1.
However, some easing functions (such as `in_out_elastic`) will produce values slightly below 0 and slightly above 1.
Because of this, any implementation of `blend` should support values outwith the range 0 to 1.
Textual supports the easing functions listed on this [very helpful page](https://easings.net/). 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. 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`. To use `easeInOutSine`, for example, you'll write `in_out_sine`.