
The ripples are used both for the "Hot Corner" animation and for the "Locate Pointer" animation. The latter one is an accessibility feature and should always work, even when animations are disabled. Take this into account. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2986>
113 lines
3.7 KiB
JavaScript
113 lines
3.7 KiB
JavaScript
import Clutter from 'gi://Clutter';
|
|
import St from 'gi://St';
|
|
|
|
// Shamelessly copied from the layout "hotcorner" ripples implementation
|
|
export class Ripples {
|
|
constructor(px, py, styleClass, animationRequired = false) {
|
|
this._x = 0;
|
|
this._y = 0;
|
|
|
|
this._px = px;
|
|
this._py = py;
|
|
this._animationRequired = animationRequired;
|
|
|
|
this._ripple1 = new St.BoxLayout({
|
|
style_class: styleClass,
|
|
opacity: 0,
|
|
can_focus: false,
|
|
reactive: false,
|
|
visible: false,
|
|
});
|
|
this._ripple1.set_pivot_point(px, py);
|
|
|
|
this._ripple2 = new St.BoxLayout({
|
|
style_class: styleClass,
|
|
opacity: 0,
|
|
can_focus: false,
|
|
reactive: false,
|
|
visible: false,
|
|
});
|
|
this._ripple2.set_pivot_point(px, py);
|
|
|
|
this._ripple3 = new St.BoxLayout({
|
|
style_class: styleClass,
|
|
opacity: 0,
|
|
can_focus: false,
|
|
reactive: false,
|
|
visible: false,
|
|
});
|
|
this._ripple3.set_pivot_point(px, py);
|
|
}
|
|
|
|
destroy() {
|
|
this._ripple1.destroy();
|
|
this._ripple2.destroy();
|
|
this._ripple3.destroy();
|
|
}
|
|
|
|
_animRipple(ripple, delay, duration, startScale, startOpacity, finalScale) {
|
|
// We draw a ripple by using a source image and animating it scaling
|
|
// outwards and fading away. We want the ripples to move linearly
|
|
// or it looks unrealistic, but if the opacity of the ripple goes
|
|
// linearly to zero it fades away too quickly, so we use a separate
|
|
// tween to give a non-linear curve to the fade-away and make
|
|
// it more visible in the middle section.
|
|
|
|
ripple.x = this._x;
|
|
ripple.y = this._y;
|
|
ripple.visible = true;
|
|
ripple.opacity = 255 * Math.sqrt(startOpacity);
|
|
ripple.scale_x = ripple.scale_y = startScale;
|
|
ripple.set_translation(-this._px * ripple.width, -this._py * ripple.height, 0.0);
|
|
const animationRequired = this._animationRequired;
|
|
|
|
ripple.ease({
|
|
opacity: 0,
|
|
delay,
|
|
duration,
|
|
animationRequired,
|
|
mode: Clutter.AnimationMode.EASE_IN_QUAD,
|
|
});
|
|
ripple.ease({
|
|
scale_x: finalScale,
|
|
scale_y: finalScale,
|
|
delay,
|
|
duration,
|
|
animationRequired,
|
|
mode: Clutter.AnimationMode.LINEAR,
|
|
onComplete: () => (ripple.visible = false),
|
|
});
|
|
}
|
|
|
|
addTo(stage) {
|
|
if (this._stage !== undefined)
|
|
throw new Error('Ripples already added');
|
|
|
|
this._stage = stage;
|
|
this._stage.add_child(this._ripple1);
|
|
this._stage.add_child(this._ripple2);
|
|
this._stage.add_child(this._ripple3);
|
|
}
|
|
|
|
playAnimation(x, y) {
|
|
if (this._stage === undefined)
|
|
throw new Error('Ripples not added');
|
|
|
|
this._x = x;
|
|
this._y = y;
|
|
|
|
this._stage.set_child_above_sibling(this._ripple1, null);
|
|
this._stage.set_child_above_sibling(this._ripple2, this._ripple1);
|
|
this._stage.set_child_above_sibling(this._ripple3, this._ripple2);
|
|
|
|
// Show three concentric ripples expanding outwards; the exact
|
|
// parameters were found by trial and error, so don't look
|
|
// for them to make perfect sense mathematically
|
|
|
|
// delay time scale opacity => scale
|
|
this._animRipple(this._ripple1, 0, 830, 0.25, 1.0, 1.5);
|
|
this._animRipple(this._ripple2, 50, 1000, 0.0, 0.7, 1.25);
|
|
this._animRipple(this._ripple3, 350, 1000, 0.0, 0.3, 1);
|
|
}
|
|
}
|