slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
16.93k stars 568 forks source link

Reset animations [was: infinite loop animation error] #3494

Closed syf20020816 closed 11 months ago

syf20020816 commented 1 year ago

Root cause is that the animation gets stopped at some value between 0 and 360° and animates from that (random) value to 360°.

We should have a way to cleanly reset animations in use-cases like this one. -- @hunger

Original report follows:


My Goal

I want to write a loading component , when this component show the loading icon will rotate in an infinite loop

error video

The icon in the component can rotate normally during the first run, but after multiple runs, the animation shows an exception

https://github.com/slint-ui/slint/assets/92167095/ee874119-876b-4d22-a6f2-e4db73659e1a

Source Code Repo

loading.slint : https://github.com/syf20020816/SurrealismUI/tree/main/src/loading/loading.slint test : https://github.com/syf20020816/SurrealismUI/blob/main/tests/src/loading.slint

Code

import { SURPopup } from "../popup/index.slint";
import { SURText } from "../text/index.slint";
import { SURCard } from "../card/index.slint";

import {ROOT_STYLES,Themes,PaddingSize,PaddingItem,Shadows,Borders,BorderItem,IconSources} from "../../themes/index.slint";
export component Loading inherits Window {
  height: 100%;
  width: 100%;
  padding: 0;
  padding-bottom: 0;
  padding-left: 0;
  padding-right: 0;
  padding-top: 0;
  always-on-top:true;
  opacity: 1;
  visible: is-show;
  z: 1000;
  in property <image> loading-icon : IconSources.icons.Loading;
  in property <duration> duration : 1200ms;
  in property <string> content : "Loading ...";
  in-out property <bool> is-show:false;
  in property <Themes> theme : Dark;
  callback open();
  callback close();
  open => {
    self.is-show = true;
    // do animation
    loading-icon.rotation-angle = 360deg;
  }
  close => {
    self.is-show = false;
    loading-icon.rotation-angle = 0deg;
  }
  mask:=SURCard {
    z: 100;
    height: 100%;
    width: 100%;
    theme: root.theme;
    drop-shadow-blur: 0;
    drop-shadow-offset-x: 0;
    drop-shadow-offset-y: 0;
    opacity: 100%;
    border: None;
    clip: true;
    VerticalLayout {
      alignment: center;
      width: 50%;
      spacing: ROOT-STYLES.sur-font.font-size;
      Rectangle {
        height: loading-icon.height;
        width: parent.width;
        loading-icon:=Image {
          height: ROOT-STYLES.sur-font.font-size * 2;
          width:  ROOT-STYLES.sur-font.font-size * 2;
          source: root.loading-icon;
          animate rotation-angle {
            easing: ease-in-out;
            iteration-count: -1;
            duration: root.duration;
          }
        }
      }
      Rectangle {
        height: loading-txt.height;
        width: parent.width;
        loading-txt:=SURText {
          theme: root.theme;
          content: root.content;
        }
      }
    }
  }
  TouchArea {
    z: 110;
  }
}

test code

import {SURLoading,SURButton,SURCard} from "../../index.slint";

export component TestLoading inherits Window {
    height: 600px;
    width: 400px;
    SURButton {
      y: 100px;
      content: "show";
      clicked => {
        p.open();
      }
    }
    SURButton {
      y: 160px;
      content: "close";
      clicked => {
        p.close();
      }
    }
    SURCard{
      y: 260px;
      clip: true;
      card-height: 260px;
      card-width: 180px;
      p:=SURLoading { }
    }
}
hunger commented 1 year ago

That is due to the rotation animating from the current value to the newly set value. So the first time round you rotate from 0 to 360°. Then you stop at some angle. Then you start again, rotating from whatever that angle was when you stopped to the new target value.

MAybe you can try someting like this

hunger commented 1 year ago

I'll edit your report a bit and leave it open as we should have a way to reset animations more cleanly.

PS: Thank you for the excellent bug report!

syf20020816 commented 1 year ago

Thank you for providing the case, but after my inspection, it seems that the problem lies in the issue of loop animation failure when using the visible property to hide the component

syf20020816 commented 1 year ago

https://github.com/slint-ui/slint/assets/92167095/80cbf111-18f0-4f2a-94dc-9138f8b5cf4f

hunger commented 1 year ago

Thank you for double checking. Looks like the visibilit change indeed has some unintended side effect ion the animation.

ogoffart commented 11 months ago

The animate rotation-angle { ... } means it animates from the current position to the new position in that amount of time. So if you set rotation-angle = xyz; it will animate from the current value to xyz. But if you call it twice, the current value depends on the animation, so the behavior is correct.

The truth is that iteration-count is not a great way to get continuously rotating animation. For this kind of usecase, you are better off with animation-tick.

For example: Link

rotation-angle: !animating ? 0deg : 360deg * animation-tick()/loading_animation_duration;

(this doesn't have an easing curve, but you can get one with some math in the binding)