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
@hecrj
Copy link
Member

hecrj commented Jan 28, 2025

Hey there! Thanks for taking a shot at this.

For anyone that wants to help taking widget animations to the finish line—and specially now that #2757 is landing—there are a couple of things we must do:

  • Split the code into multiple units. A different PR should be opened for each widget. This will let us have a focused discussion about what and how we should animate, as well as let animations land gradually.

  • Rebase the work on top of Animation API for application code #2757. This PR exposes a unified animation module and properly hides the lilt dependency. The API is just a very thin wrapper over lilt, so it should be a fairly straightforward rewrite. The main challenge will be addressing the new reactive rendering strategy introduced in Reactive Rendering #2662.

  • Feature-gate widget animations under a new animations feature flag. This will let users of the library opt-in or opt-out. Once all built-in widgets are properly animated, we can enable the feature by default.

@Brady-Simon
Copy link

I would be interested in helping with widget animations.

Svg should be one of the simplest widgets to animate since its style is only a single optional Color. It could serve as a reference for animating other widgets once it's done.

@hecrj How would you like the user to customize the internal animation (i.e. easing and duration)? I imagine you already have something in mind.

@hecrj
Copy link
Member

hecrj commented Jan 28, 2025

@Brady-Simon I think the svg widget should be animated from application code.

Widget animations should be implemented for those internal state changes that cannot be easily animated from application code (e.g. hover transitions, toggle transitions, focus transitions, etc.).

I would just hardcode some sane defaults for duration and easings for now. We can think about customization later.

@lazytanuki
Copy link
Author

Thanks @hecrj for tackling this! I'll have a look at it and try to reimplement my animations on top of this in separate PR's.

I'll try to implement the toggle widget first to see if the implementation design aligns with your vision, then I shall start working on the other ones.

@Brady-Simon
Copy link

@lazytanuki I've opened an initial PR for animating the radio widget. Do you have any other widgets beside toggle that you'd prefer to animate? I can tackle some of the widgets you aren't working on.

@lazytanuki
Copy link
Author

@lazytanuki I've opened an initial PR for animating the radio widget. Do you have any other widgets beside toggle that you'd prefer to animate? I can tackle some of the widgets you aren't working on.

Hi !

I still need to have a closer look at the new API but yeah ideally I'd like to rebase my implementations (mainly buttons, scrollables, toggles) as some had caveats that can be a hassle to tackle. I shall get into this tonight

@lazytanuki
Copy link
Author

I've made a PR for the toggler animation (#2779). If this one is alright with you @hecrj, I'll start porting the rest of my implementations.

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.

4 participants