ea4d5f89eb
If the resource scale or the scale factor changes while the animation is playing, we need to stop the animation and start it again once the texture is loaded, as the idle might try to access an invalidated animation child otherwise. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700
181 lines
5.0 KiB
JavaScript
181 lines
5.0 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported Animation, AnimatedIcon, Spinner */
|
|
|
|
const { Clutter, GLib, Gio, St } = imports.gi;
|
|
const Mainloop = imports.mainloop;
|
|
|
|
var ANIMATED_ICON_UPDATE_TIMEOUT = 16;
|
|
var SPINNER_ANIMATION_TIME = 300;
|
|
var SPINNER_ANIMATION_DELAY = 1000;
|
|
|
|
var Animation = class {
|
|
constructor(file, width, height, speed) {
|
|
this.actor = new St.Bin();
|
|
this.actor.set_size(width, height);
|
|
this.actor.connect('destroy', this._onDestroy.bind(this));
|
|
this.actor.connect('notify::size', this._syncAnimationSize.bind(this));
|
|
this.actor.connect('resource-scale-changed',
|
|
this._loadFile.bind(this, file, width, height));
|
|
|
|
let themeContext = St.ThemeContext.get_for_stage(global.stage);
|
|
this._scaleChangedId = themeContext.connect('notify::scale-factor',
|
|
this._loadFile.bind(this, file, width, height));
|
|
|
|
this._speed = speed;
|
|
|
|
this._isLoaded = false;
|
|
this._isPlaying = false;
|
|
this._timeoutId = 0;
|
|
this._frame = 0;
|
|
|
|
this._loadFile(file, width, height);
|
|
}
|
|
|
|
play() {
|
|
if (this._isLoaded && this._timeoutId == 0) {
|
|
if (this._frame == 0)
|
|
this._showFrame(0);
|
|
|
|
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_LOW, this._speed, this._update.bind(this));
|
|
GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._update');
|
|
}
|
|
|
|
this._isPlaying = true;
|
|
}
|
|
|
|
stop() {
|
|
if (this._timeoutId > 0) {
|
|
Mainloop.source_remove(this._timeoutId);
|
|
this._timeoutId = 0;
|
|
}
|
|
|
|
this._isPlaying = false;
|
|
}
|
|
|
|
_loadFile(file, width, height) {
|
|
let [validResourceScale, resourceScale] = this.actor.get_resource_scale();
|
|
let wasPlaying = this._isPlaying;
|
|
|
|
if (this._isPlaying)
|
|
this.stop();
|
|
|
|
this._isLoaded = false;
|
|
this.actor.destroy_all_children();
|
|
|
|
if (!validResourceScale) {
|
|
if (wasPlaying)
|
|
this.play();
|
|
return;
|
|
}
|
|
|
|
let textureCache = St.TextureCache.get_default();
|
|
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
|
this._animations = textureCache.load_sliced_image(file, width, height,
|
|
scaleFactor, resourceScale,
|
|
this._animationsLoaded.bind(this));
|
|
this.actor.set_child(this._animations);
|
|
|
|
if (wasPlaying)
|
|
this.play();
|
|
}
|
|
|
|
_showFrame(frame) {
|
|
let oldFrameActor = this._animations.get_child_at_index(this._frame);
|
|
if (oldFrameActor)
|
|
oldFrameActor.hide();
|
|
|
|
this._frame = (frame % this._animations.get_n_children());
|
|
|
|
let newFrameActor = this._animations.get_child_at_index(this._frame);
|
|
if (newFrameActor)
|
|
newFrameActor.show();
|
|
}
|
|
|
|
_update() {
|
|
this._showFrame(this._frame + 1);
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
|
|
_syncAnimationSize() {
|
|
if (!this._isLoaded)
|
|
return;
|
|
|
|
let [width, height] = this.actor.get_size();
|
|
|
|
for (let i = 0; i < this._animations.get_n_children(); ++i)
|
|
this._animations.get_child_at_index(i).set_size(width, height);
|
|
}
|
|
|
|
_animationsLoaded() {
|
|
this._isLoaded = this._animations.get_n_children() > 0;
|
|
|
|
this._syncAnimationSize();
|
|
|
|
if (this._isPlaying)
|
|
this.play();
|
|
}
|
|
|
|
_onDestroy() {
|
|
this.stop();
|
|
|
|
let themeContext = St.ThemeContext.get_for_stage(global.stage);
|
|
if (this._scaleChangedId)
|
|
themeContext.disconnect(this._scaleChangedId);
|
|
this._scaleChangedId = 0;
|
|
}
|
|
};
|
|
|
|
var AnimatedIcon = class extends Animation {
|
|
constructor(file, size) {
|
|
super(file, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
|
|
}
|
|
};
|
|
|
|
var Spinner = class extends AnimatedIcon {
|
|
constructor(size, animate = false) {
|
|
let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/process-working.svg');
|
|
super(file, size);
|
|
|
|
this.actor.opacity = 0;
|
|
this._animate = animate;
|
|
}
|
|
|
|
_onDestroy() {
|
|
this._animate = false;
|
|
super._onDestroy();
|
|
}
|
|
|
|
play() {
|
|
this.actor.remove_all_transitions();
|
|
|
|
if (this._animate) {
|
|
super.play();
|
|
this.actor.ease({
|
|
opacity: 255,
|
|
delay: SPINNER_ANIMATION_DELAY,
|
|
duration: SPINNER_ANIMATION_TIME,
|
|
mode: Clutter.AnimationMode.LINEAR
|
|
});
|
|
} else {
|
|
this.actor.opacity = 255;
|
|
super.play();
|
|
}
|
|
}
|
|
|
|
stop() {
|
|
this.actor.remove_all_transitions();
|
|
|
|
if (this._animate) {
|
|
this.actor.ease({
|
|
opacity: 0,
|
|
time: SPINNER_ANIMATION_TIME,
|
|
transition: 'linear',
|
|
onComplete: () => super.stop()
|
|
});
|
|
} else {
|
|
this.actor.opacity = 0;
|
|
super.stop();
|
|
}
|
|
}
|
|
};
|