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

feat: animations for default widgets #2483

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

lazytanuki
Copy link

@lazytanuki lazytanuki commented Jun 28, 2024

Hi!

This PR is the continuation / replacement of my previous work in #1855 which needed a heavy rebase as well as improvements for some edge cases.

This PR implements internal animations for some default widgets. The goal here is to make Iced applications appear smooth when hovering / clicking things, without breaking the API.
This heavily uses the frame subscription API as well as the brand new lilt crate (thanks @ejjonny !)

output.mp4

Motivation

In many other toolkits out there, such as GTK, app developers can make GUIs without needing to handle widget animations by themselves, which makes for a better coding experience and a coherent ecosystem.

Implementation

Animations are added to the widgets State, which use lilt::Animated<...> values that are then interpolated in the draw() function to render animated frames.
Animations are triggered by specific events, such as cursor movements.
The frame subscription API is used to request redraws until the animations are completed.

Widgets that have got animations are:

  • buttons: hover and click style transitions

    I have had to make a kind of opinionated decision here. As of now, hovering a Button with the mouse transitions its color to a lighter/darker version for dark/light base colors, and pressing the Button reverts that effect back to the base color. However, this isn't very well suited to animations, as it makes the transition go from base to hover (hover), hover to base (pressed), then base to hover (release mouse button), then back from hover to base when not hovering it. On quick movements, it looks a bit odd. What other toolkits out there have been doing is a bit more linear: on a dark base color, hovering lightens it, pressing lightens it more, and so that there isn't too much back and forth between different variations.

    Also, buttons feature an asymmetric animation. The transition from idle to hovered is quicker than the one back to idle. That way, when moving the cursor rapidly between multiple widgets, you still get the feedback of the widgets "lighting" up when the cursor flies over them, and then you get a slower, smooth transition back to their idle state. Otherwise, having long animations would prevent the hover effects from being visible on fast cursor movements, and animations fast enough for it would feel too fast for the "back to idle" transition.

  • text input cursor: cursor fades in and out when idle, but remains visible while typing

  • togglers

  • checkboxes

  • scrollables: scrolling using the mouse wheel and clicking on the scrollbar follows an eased-out trajectory, whereas scrolling by grabbing the scrollbar remains instantaneous.

    To make the code simpler and the diff shorter, I've to do a bit of refactoring there. All offsets are now stored as relative offsets, but absolute offsets are still passed in arguments when necessary, only to be converted back to relative offsets (outside draw()).

This PR is divided in separate commits for an easier reviewing.

Caveats

Due to the stateless nature of the widgets, I've encountered some edge cases where a widget would use what I call a "tainted" state in the code. For instance, in the tour example, the first checkbox of a page will share the same state as the first checkbox of the page that follows (first one of its kind in the state tree map). This can lead to the new checkbox already having an ongoing animation when being rendered for the first time. I've added checks to detect and fix "tainted" states, they are pretty straightforward, but I did not find a better way to handle this.

What's next

If and when this PR lands, some more widgets can receive animations. However, the most important step that I intend to implement is a global animation speed multiplier, so that users can set it according to their preferences in the COSMIC desktop, for instance. It could also include some other user settings, such as animation styles for different widgets and so on.

Thanks for reading this!

@lazytanuki lazytanuki force-pushed the animations branch 4 times, most recently from c373262 to 0c15d51 Compare July 9, 2024 20:58
@lazytanuki lazytanuki marked this pull request as draft July 9, 2024 21:03
@lazytanuki lazytanuki force-pushed the animations branch 6 times, most recently from 180d37e to 4e69ed5 Compare July 12, 2024 22:50
@lazytanuki lazytanuki marked this pull request as ready for review July 12, 2024 22:52
@lazytanuki lazytanuki force-pushed the animations branch 2 times, most recently from a07b781 to f35dc79 Compare July 19, 2024 10:50
@lazytanuki
Copy link
Author

Rebased on latest master

Before this commit, hovering a button "deviates" the color (it darkens
light ones and vice-versa) and pressing it reverts back to the original
color. With animations, it makes more sense to deviate the base color
one time on hover, and deviate it a second time on press, so that going
back from pressed to just active just goes from light/dark to dark/light
instead of dark/light to light/dark then back to dark/light.
…rollable::scroll_to` not to depend on `scrollable::State::last_notified`
@hecrj hecrj added this to the 1.0 milestone Sep 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants