slint icon indicating copy to clipboard operation
slint copied to clipboard

How to do animation on condition and hide parts of sub element outside of its parent

Open cppdev123 opened this issue 3 years ago • 2 comments

I'm trying to implement an ink ripple like effect for buttons. It consists of a circular rectangle (the ripple) and a mouse area to received the mouse clicks to start animations on the radius of the circle and its opacity. what I want to do: when the mouse is pressed (not clicked !) the ripple will start from radius 0 until it reaches the furthest corner of the parent (cover the whole parent) and then it disappears when opacity reaches 1 then circle returns to size 0 again and during all of this the circle should not be visible outside of its parent

what I did so far: first try: circle is initially 0 in size and when the mouse is clicked (not pressed !) animation starts from the point of mouse until it covers the parent then it disappears when opacity reaches 0 and I can't restore it to its original state to start animation again !

second try: used states with transitions to run the animation as long as the TouchArea is pressed and hide it immediately when the mouse button is released, not what I want but animation can be restarted

third try: tried to create the ripple rectangle inside if block when the touch is pressed but I can't animate here !

in all tries the circle is visible outside its parent which is not desired

Any help how to overcome these issues ?

cppdev123 avatar Jun 08 '22 21:06 cppdev123

What you ask doesn't seem easy to do right now...

I've come up with a hack

preview

Wow := Rectangle {
    property <float> controler: 0;
    animate controler { duration: 1s; }
    background: #33d;
    touch := TouchArea {
       pointer-event(e) => {
          if (e.kind == PointerEventKind.down) {
             controler = controler + 2;
          }
       }
    }
    circle := Rectangle {
        width: 0;
        height: width;
        opacity: 1.;
        border-radius: width/2;
        background: #0005;
        x: touch.pressed-x - width/2;
        y: touch.pressed-y - width/2;

        states [
            pressed when mod(ceil(root.controler), 2) != 0 : {
                circle.width: root.width * 2 * 1.4142;
                circle.opacity: 0.;
            }
        ]
        transitions [
            in pressed : {
                animate circle.width { duration: 1s; easing: ease; }
                animate circle.opacity { delay: 0.5s; duration: 300ms; easing: ease; }
            }
            out pressed : {
            }
        ]

    }
  

}


export _ := Window {
    width: 300px;
    height: 300px;
    Wow {

    }
}

We definitievly should add some feature to our animation system so such things can be easier to do.

ogoffart avatar Jun 09 '22 11:06 ogoffart

Found that clip restricts the visible part to the parent and using your hack I tried to make many circles to display one on each press if the previous is still running (a hack too) but the macro proc panicked due to unreachable code !

export struct SPoint := {
		x: float,
		y: float,
	}
	
	export global RadiusCalc := {
		callback calc-radius(SPoint, float, float) -> float; // mouse pos, parent pos, width, height
	}
	
	export RipplCircle := Rectangle {
		property <length> radius: 0;
		property <length> mx: 0;
		property <length> my: 0;
		property <float> fwidth: 0;
		property <float> fheight: 0;
		property <bool> running: false;
		width: radius * 2;
		height: radius * 2;
		x: mx - width / 2;
		y: my - height / 2;
		background: rgba(0,0,0, 0.5);
		border-radius: width / 2;
		
		property <float> counter: 0;
		animate counter { duration: 800ms; }
		
		states [
			ripple when counter > 0 && counter < 0.1 : {
				radius: RadiusCalc.calc_radius({ x: mx / 1px, y: my / 1px }, fwidth, fheight) * 1px;
				opacity: 0.;
				running: true;
			}
		]
		
		transitions [
			in ripple: {
				animate radius { duration: 800ms; }
				animate opacity { duration: 800ms; }
			}
		]
	}
	
	export InkEffect := Rectangle {
		
		preferred-width: 200px;
		preferred-height: 200px;
		background: white;
		clip: true;
		
		circle1 := RipplCircle {}
		// un commenting the next line causes macro proc panick
		//circle2 := RipplCircle { background: green; }
		
		
		tch := TouchArea {
			width: parent.width;
			height: parent.height;
			pointer-event(ev) => {
				if (ev.kind == PointerEventKind.down && ev.button == PointerEventButton.left) {
					if (!circle1.running) {
						circle1.mx = mouse-x;
						circle1.my = mouse-y;
						circle1.fwidth = parent.width / 1px;
						circle1.fheight = parent.height / 1px;
						circle1.counter = circle1.counter == 0 ? 0.1 : 0;
					}
				}
			}
			
		}		
	}

What is needed is a method to create objects dynamically and destroy them on animation end as qml does

cppdev123 avatar Jun 09 '22 15:06 cppdev123

I think sequence of state would solve this issue: https://github.com/slint-ui/slint/issues/2255 So closing as a duplicate

ogoffart avatar Feb 06 '24 09:02 ogoffart