gnome-shell/js/ui/animation.js

189 lines
5.1 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
import Clutter from 'gi://Clutter';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import St from 'gi://St';
import * as Params from '../misc/params.js';
const ANIMATED_ICON_UPDATE_TIMEOUT = 16;
const SPINNER_ANIMATION_TIME = 300;
const SPINNER_ANIMATION_DELAY = 1000;
export const Animation = GObject.registerClass(
class Animation extends St.Bin {
_init(file, width, height, speed) {
const themeContext = St.ThemeContext.get_for_stage(global.stage);
super._init({
style: `width: ${width}px; height: ${height}px;`,
});
this.connect('destroy', this._onDestroy.bind(this));
this.connect('resource-scale-changed',
this._loadFile.bind(this, file, width, height));
themeContext.connectObject('notify::scale-factor',
() => {
this._loadFile(file, width, height);
this.set_size(width * themeContext.scale_factor, height * themeContext.scale_factor);
}, this);
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) {
GLib.source_remove(this._timeoutId);
this._timeoutId = 0;
}
this._isPlaying = false;
}
_loadFile(file, width, height) {
const resourceScale = this.get_resource_scale();
let wasPlaying = this._isPlaying;
if (this._isPlaying)
this.stop();
this._isLoaded = false;
this.destroy_all_children();
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._loadFinished());
this._animations.set({
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
});
this.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;
}
_loadFinished() {
this._isLoaded = this._animations.get_n_children() > 0;
if (this._isLoaded && this._isPlaying)
this.play();
}
_onDestroy() {
this.stop();
}
});
export const AnimatedIcon = GObject.registerClass(
class AnimatedIcon extends Animation {
_init(file, size) {
super._init(file, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
}
});
export const Spinner = GObject.registerClass(
class Spinner extends AnimatedIcon {
_init(size, params) {
params = Params.parse(params, {
animate: false,
hideOnStop: false,
});
let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/process-working.svg');
super._init(file, size);
this.opacity = 0;
this._animate = params.animate;
this._hideOnStop = params.hideOnStop;
this.visible = !this._hideOnStop;
}
_onDestroy() {
this._animate = false;
super._onDestroy();
}
play() {
this.remove_all_transitions();
this.show();
if (this._animate) {
super.play();
this.ease({
opacity: 255,
delay: SPINNER_ANIMATION_DELAY,
duration: SPINNER_ANIMATION_TIME,
mode: Clutter.AnimationMode.LINEAR,
});
} else {
this.opacity = 255;
super.play();
}
}
stop() {
this.remove_all_transitions();
if (this._animate) {
this.ease({
opacity: 0,
duration: SPINNER_ANIMATION_TIME,
mode: Clutter.AnimationMode.LINEAR,
onComplete: () => {
super.stop();
if (this._hideOnStop)
this.hide();
},
});
} else {
this.opacity = 0;
super.stop();
if (this._hideOnStop)
this.hide();
}
}
});