Iced: Animations

Created on 23 Oct 2019  路  6Comments  路  Source: hecrj/iced

Allow widgets to request a redraw at a specific time.

This is a necessary feature to render loading spinners, a blinking text cursor, GIF images, etc.

[winit] allows controlling the event loop in a flexible way. We may be able to use ControlFlow::WaitUntil for this purpose.

feature help wanted

Most helpful comment

Following on from a conversation on the zulip chat, @hecrj mentioned a good start would be enumerating use-cases, and think through how animations/draw state will interact with other features and API design decisions (of particular note, async rendering, and layout/widget state).

In the interest of getting started, he's my attempt at rounding up the use-cases:

  1. Self-animating widgets - We want to support widgets which need to change their appearance _as a consequence of the nominal passage of time_. Examples of this are spinners (loading animations), blinking text cursors, and animated GIFs.

    • We can probably divide this into two categories: those that want to re-draw as fast as possible (ie: every frame, to give the appearance of smooth motion), or those that want to re-draw at roughly some point in the future(ie: a blinking cursor, roughly every 500ms).

    • For the latter case, its common for the animation requirement to be temporary. For example, the cursor only needs to blink for a text input that is currently in focus.

  2. Transitionary animations - We want to support widgets which temporarily animate a change in state. An example of this could be an image which fades in once the bitmap data is loaded, or a text field which highlights itself (and then fades back to normal) when its content changes.

Non-case: Updating a widget based on external/non-UI state. I believe iced_native::subscription::Recipe should be used for this purpose (but feel free to disagree with me).

Thoughts:

  1. Incremental layout/draw - The current implementation caches the state of the _layout_ (see native/src/user_interface.rs) through a Cache object, however has no such equivalent for caching drawable primitives for each widget (instead, draw() is called recursively throughout the entire widget tree whenever drawing is performed). This is nice and simple, but for a complex graph which elements which are animating every frame, this would result in a lot of draw() calls to widgets which don't change, which would be wasteful.

    I imagine a future implementation might try and keep track of the draw/message/damage state of widgets, and avoid performing unnecessary computation here if theres no need. Alternatively, damage information for the entire visible surface could be tracked, and combined with a draw() walk could be used to cull draw() invocations across the tree. #377 is exploring the beginnings of damage tracking.

  2. Widget methods - The widget tree is composed of types which implement layout(), on_event(), and draw(), which is an awesomely simple & understandable API. However, this doesn't really leave a good place for animation-related state.

Prior art / Implementation ideas

  1. Flutter

    • Layout and display list information is cached for each widget. All widgets keep track of whether they need to be re-drawn or have layout done in the next pass - update logic calls methods on a provided context like markNeedsPaint().

    • Invocations to widget methods during animation are configured through an AnimationController, which under-the-hood registers a callback on frame updates or a clock (see TickerProvider).

  2. Druid

    • Similarly to other toolkits, Druid tracks whether a draw operation is needed: update logic calls request_paint() to request a redraw when one is required.

    • To support animations that need to happen every frame, widgets can call request_anim_frame() to mark itself and all parents as needing an animation specific redraw.

    • To support periodic animations, widgets can call request_timer() to setup a timer event which fires periodically.

All 6 comments

I'm not sure why a redraw at a specific time is necessary. Is this so that a component can render at e.g. a specific framerate? It seems like a timer or a separate scheduler to call update functions might be a way to go about this, followed by requesting a window redraw?

I need this too. I'm writing a program for the web that does custom animation. I looked at the solar_system and stopwatch example but they both use async_std to drive the animation with a timer. The async_std crate does not support WASM yet. See https://github.com/async-rs/async-std/issues/220

For a "normal" web program I would just use the Window.requestAnimationFrame method.

Hey @hecrj! I鈥檓 a noob but would love to take a shot at helping out with this. Are there any blockers for this issue?; and if not, which place(s) in the codebase would the change be made?

I have an idea for a simple but versatile Animation widget where different functional methods could be used to modify a child widget.
Perhaps something like this:

fn update(&mut self, message: Message){
    match message {
        Message::TriggerAnimation => self.animation_state.forward(), // If the animation has not been run yet, run it forward
    }
}
fn view(&mut self) {
    Button::new(&mut self.increment_button, Text::new("Trigger")).on_press(Message::TriggerAnimation)
    Animation::new(&mut self.animation_state, child: Text::new("I just faded in!"))
    .fade_in(Curve::linear()) // Fade in linearly
    .translate(Curve::ease(), (-10, 0), (0,0)) // Move in from relative left
}

Following on from a conversation on the zulip chat, @hecrj mentioned a good start would be enumerating use-cases, and think through how animations/draw state will interact with other features and API design decisions (of particular note, async rendering, and layout/widget state).

In the interest of getting started, he's my attempt at rounding up the use-cases:

  1. Self-animating widgets - We want to support widgets which need to change their appearance _as a consequence of the nominal passage of time_. Examples of this are spinners (loading animations), blinking text cursors, and animated GIFs.

    • We can probably divide this into two categories: those that want to re-draw as fast as possible (ie: every frame, to give the appearance of smooth motion), or those that want to re-draw at roughly some point in the future(ie: a blinking cursor, roughly every 500ms).

    • For the latter case, its common for the animation requirement to be temporary. For example, the cursor only needs to blink for a text input that is currently in focus.

  2. Transitionary animations - We want to support widgets which temporarily animate a change in state. An example of this could be an image which fades in once the bitmap data is loaded, or a text field which highlights itself (and then fades back to normal) when its content changes.

Non-case: Updating a widget based on external/non-UI state. I believe iced_native::subscription::Recipe should be used for this purpose (but feel free to disagree with me).

Thoughts:

  1. Incremental layout/draw - The current implementation caches the state of the _layout_ (see native/src/user_interface.rs) through a Cache object, however has no such equivalent for caching drawable primitives for each widget (instead, draw() is called recursively throughout the entire widget tree whenever drawing is performed). This is nice and simple, but for a complex graph which elements which are animating every frame, this would result in a lot of draw() calls to widgets which don't change, which would be wasteful.

    I imagine a future implementation might try and keep track of the draw/message/damage state of widgets, and avoid performing unnecessary computation here if theres no need. Alternatively, damage information for the entire visible surface could be tracked, and combined with a draw() walk could be used to cull draw() invocations across the tree. #377 is exploring the beginnings of damage tracking.

  2. Widget methods - The widget tree is composed of types which implement layout(), on_event(), and draw(), which is an awesomely simple & understandable API. However, this doesn't really leave a good place for animation-related state.

Prior art / Implementation ideas

  1. Flutter

    • Layout and display list information is cached for each widget. All widgets keep track of whether they need to be re-drawn or have layout done in the next pass - update logic calls methods on a provided context like markNeedsPaint().

    • Invocations to widget methods during animation are configured through an AnimationController, which under-the-hood registers a callback on frame updates or a clock (see TickerProvider).

  2. Druid

    • Similarly to other toolkits, Druid tracks whether a draw operation is needed: update logic calls request_paint() to request a redraw when one is required.

    • To support animations that need to happen every frame, widgets can call request_anim_frame() to mark itself and all parents as needing an animation specific redraw.

    • To support periodic animations, widgets can call request_timer() to setup a timer event which fires periodically.

Note: not every "animation" is purely visual, e.g. in Qt menus, there is a short delay (maybe 500ms) between hovering over a menu item and opening/closing submenus. The reason is more functional than visual: during mouse movement the cursor may temporarily leave the menu item before entering the sub-menu's area, yet the user expects the sub-menu not to close during this brief period. (Alternative approaches are possible here, e.g. GTK does not close sub-menus when the cursor leaves the menu area, but does immediately do so if moving over a different item in the parent menu.)

KAS has a very simple solution similar to Flutter: let widgets request an update after dur: Duration, and make the widget handle redrawing (and optionally requesting another update-after-duration) in the event handler. (May have limitations but so far seems to work well.)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hecrj picture hecrj  路  3Comments

porglezomp picture porglezomp  路  3Comments

kszlim picture kszlim  路  4Comments

michael-hart picture michael-hart  路  4Comments

johannesvollmer picture johannesvollmer  路  4Comments