Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animations #31

Closed
hecrj opened this issue Oct 23, 2019 · 6 comments · Fixed by #1647
Closed

Animations #31

hecrj opened this issue Oct 23, 2019 · 6 comments · Fixed by #1647
Labels
animation feature New feature or request help wanted Extra attention is needed
Milestone

Comments

@hecrj
Copy link
Member

hecrj commented Oct 23, 2019

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.

@hecrj hecrj added the feature New feature or request label Oct 23, 2019
@hecrj hecrj added this to the 1.0.0 milestone Oct 23, 2019
@hecrj hecrj added the help wanted Extra attention is needed label Oct 26, 2019
@michael-hart
Copy link

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?

@hecrj hecrj mentioned this issue Feb 22, 2020
@MichaelMcDonnell
Copy link

MichaelMcDonnell commented Apr 17, 2020

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 async-rs/async-std#220

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

@ron-wolf
Copy link

Hey @hecrj! I’m 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?

@zyansheep
Copy link

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
}

@twitchyliquid64
Copy link

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. [WIP] Add damage tracking, fixes #367 #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.

@dhardy
Copy link

dhardy commented Oct 4, 2020

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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
animation feature New feature or request help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants