marc2332 / freya

Cross-platform GUI library for 🦀 Rust powered by 🧬 Dioxus and 🎨 Skia.
https://freyaui.dev/
MIT License
1.33k stars 51 forks source link

issue: Animations are not playing. #769

Closed Aiving closed 1 month ago

Aiving commented 1 month ago

While continuing to try to implement the ripple effect from Material Design (which I mentioned in #767), I came across the fact that animations.start() doesn't seem to do anything. Obviously, this has something to do with the info variable and probably the pointerdown closure. However, I have not found out the exact reason why this is happening.

Branch: fix/element-rendering-rethink

It is quite difficult for me to provide the minimum code to reproduce the problem, so below will be all the code I am currently using:

use freya::prelude::*;

const INITIAL_ORIGIN_SCALE: f32 = 0.2;
const PADDING: f32 = 10.0;
const SOFT_EDGE_MINIMUM_SIZE: f32 = 75.0;
const SOFT_EDGE_CONTAINER_RATIO: f32 = 0.35;

fn main() {
    launch(app);
}

fn app() -> Element {
    rsx! {
        rect {
            height: "100%",
            width: "100%",
            main_align: "center",
            cross_align: "center",

            rect {
                height: "192",
                width: "192",
                background: "blue",
                corner_radius: "12",
                main_align: "center",
                cross_align: "center",
                overflow: "clip",

                Ripple { }
            }
        }
    }
}

#[derive(Default, PartialEq, Clone)]
struct RipplePosition {
    start: Point2D,
    end: Point2D,
}

impl RipplePosition {
    fn new(cursor: Point2D, size: f32, (height, width): (f32, f32)) -> Self {
        Self {
            start: Point2D::new(cursor.x - size / 2.0, cursor.y - size / 2.0),
            end: Point2D::new((width - size) / 2.0, (height - size) / 2.0),
        }
    }
}

#[derive(Default, PartialEq, Clone)]
struct RippleInfo {
    position: RipplePosition,
    radius: f32,
    scale: f32,
}

impl RippleInfo {
    fn new(cursor: Point2D, area: Rect<f32, Measure>) -> Self {
        let (size, origin) = ((area.height(), area.width()), area.origin);
        let (radius, scale) = Self::get_size(size);
        let position = RipplePosition::new((cursor - origin).to_point(), radius, size);

        Self {
            position,
            radius,
            scale,
        }
    }

    fn get_size((height, width): (f32, f32)) -> (f32, f32) {
        let max_dim = height.max(width);
        let soft_edge_size = SOFT_EDGE_MINIMUM_SIZE.max(SOFT_EDGE_CONTAINER_RATIO * max_dim);

        let initial_size = (max_dim * INITIAL_ORIGIN_SCALE).floor();
        let hypotenuse = (width.powi(2) + height.powi(2)).sqrt();
        let max_radius = hypotenuse + PADDING;

        (initial_size, (max_radius + soft_edge_size) / initial_size)
    }
}

#[component]
pub fn Ripple() -> Element {
    let (node_ref, size) = use_node_signal();

    let mut info = use_signal(RippleInfo::default);

    let animations = use_animation(move |ctx| {
        let info = info();

        (
            ctx.with(AnimNum::new(0.0, 1.0).time(450)),
            ctx.with(AnimNum::new(info.radius, info.radius * info.scale).time(450)),
            ctx.with(AnimNum::new(info.position.start.x, info.position.end.x).time(450)),
            ctx.with(AnimNum::new(info.position.start.y, info.position.end.y).time(450)),
        )
    });

    let (opacity, radius, x, y) = animations.get();

    let pointerdown = move |event: PointerEvent| {
        info.set(RippleInfo::new(
            event.get_screen_coordinates().to_f32(),
            size.read().area,
        ));

        animations.start(); // Nothing happens
    };

    rsx! {
        rect {
            background: "rgb(255, 0, 0, 0.5)",
            width: "100%",
            height: "100%",
            overflow: "clip",

            onpointerdown: pointerdown,

            reference: node_ref,

            rect {
                background: "rgb(255, 255, 255, 0.5)",
                width: "{radius.read().as_f32()}",
                height: "{radius.read().as_f32()}",
                opacity: "{opacity.read().as_f32()}",
                position: "absolute",
                position_left: "{x.read().as_f32()}",
                position_top: "{y.read().as_f32()}",
                corner_radius: "{radius.read().as_f32() / 2.0}",
            }
        }
    }
}
marc2332 commented 1 month ago

Hey, by just looking at the code I would say that the animation is not starting because the same signal it depends (info) has a new value at the same time you are starting it. This seems like a common case so I'll see what I can do about it.

Aiving commented 1 month ago

I fixed it using use_effect (also added RippleState).

image

However, now there is one problem and one question.

Problem: for some reason, the mouseleave (or pointerleave) event is not emitted. Question: Is there a way to track when the animation ends and then do something?

mouseleave-problem

marc2332 commented 1 month ago

Problem: for some reason, the mouseleave (or pointerleave) event is not emitted.

For the leave events to be emitted you need to also register a mouseenter or pointerenter event, you can leave it empty but it's how currently freya detects that an element is interested in the "leave" event. I plan to improve this at some point

Question: Is there a way to track when the animation ends and then do something?

You can use a use_effect or use_memo, call !animations.is_running() && animations.has_run_yet()

Aiving commented 1 month ago

Thanks!