339797dbb4
Setting up implicit animations is more verbose than using tweener, in particular when setting up callbacks to run on update or completion. In order to make its use more convenient, monkey-patch ClutterActor with an ease() method that works similarly to Tweener.addTween(). https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
208 lines
6.8 KiB
JavaScript
208 lines
6.8 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Config = imports.misc.config;
|
|
|
|
imports.gi.versions.Clutter = Config.LIBMUTTER_API_VERSION;
|
|
imports.gi.versions.Gio = '2.0';
|
|
imports.gi.versions.GdkPixbuf = '2.0';
|
|
imports.gi.versions.Gtk = '3.0';
|
|
imports.gi.versions.TelepathyGLib = '0.12';
|
|
imports.gi.versions.TelepathyLogger = '0.2';
|
|
|
|
const { Clutter, GLib, Shell, St } = imports.gi;
|
|
const Gettext = imports.gettext;
|
|
|
|
// We can't import shell JS modules yet, because they may have
|
|
// variable initializations, etc, that depend on init() already having
|
|
// been run.
|
|
|
|
|
|
// "monkey patch" in some varargs ClutterContainer methods; we need
|
|
// to do this per-container class since there is no representation
|
|
// of interfaces in Javascript
|
|
function _patchContainerClass(containerClass) {
|
|
// This one is a straightforward mapping of the C method
|
|
containerClass.prototype.child_set = function(actor, props) {
|
|
let meta = this.get_child_meta(actor);
|
|
for (let prop in props)
|
|
meta[prop] = props[prop];
|
|
};
|
|
|
|
// clutter_container_add() actually is a an add-many-actors
|
|
// method. We conveniently, but somewhat dubiously, take the
|
|
// this opportunity to make it do something more useful.
|
|
containerClass.prototype.add = function(actor, props) {
|
|
this.add_actor(actor);
|
|
if (props)
|
|
this.child_set(actor, props);
|
|
};
|
|
}
|
|
|
|
function _patchLayoutClass(layoutClass, styleProps) {
|
|
if (styleProps)
|
|
layoutClass.prototype.hookup_style = function(container) {
|
|
container.connect('style-changed', () => {
|
|
let node = container.get_theme_node();
|
|
for (let prop in styleProps) {
|
|
let [found, length] = node.lookup_length(styleProps[prop], false);
|
|
if (found)
|
|
this[prop] = length;
|
|
}
|
|
});
|
|
};
|
|
layoutClass.prototype.child_set = function(actor, props) {
|
|
let meta = this.get_child_meta(actor.get_parent(), actor);
|
|
for (let prop in props)
|
|
meta[prop] = props[prop];
|
|
};
|
|
}
|
|
|
|
function _adjustEasingTime(msecs) {
|
|
if (!St.Settings.get().enable_animations)
|
|
return 1;
|
|
return St.get_slow_down_factor() * msecs;
|
|
}
|
|
|
|
function _easeActor(actor, props, easingParams) {
|
|
let { duration, delay, mode,
|
|
onStopped, onUpdate, onComplete } = easingParams;
|
|
|
|
let animatedProps = Object.keys(props).map(p => p.replace('_', '-', 'g'));
|
|
|
|
actor.save_easing_state();
|
|
|
|
if (duration)
|
|
actor.set_easing_duration(duration);
|
|
|
|
if (delay)
|
|
actor.set_easing_delay(delay);
|
|
|
|
if (mode)
|
|
actor.set_easing_mode(mode);
|
|
|
|
actor.set(props);
|
|
|
|
if (onUpdate || onComplete || onStopped) {
|
|
let transition = actor.get_transition(animatedProps[0]);
|
|
|
|
if (transition) {
|
|
let updateId = 0;
|
|
if (onUpdate)
|
|
updateId = transition.connect('new-frame', onUpdate);
|
|
|
|
let id = transition.connect('stopped', isFinished => {
|
|
if (updateId != 0)
|
|
transition.disconnect(updateId);
|
|
transition.disconnect(id);
|
|
|
|
if (onComplete)
|
|
onComplete();
|
|
if (onStopped)
|
|
onStopped(isFinished);
|
|
});
|
|
} else {
|
|
if (onComplete)
|
|
onComplete();
|
|
if (onStopped)
|
|
onStopped(true);
|
|
}
|
|
}
|
|
|
|
actor.restore_easing_state();
|
|
}
|
|
|
|
function _loggingFunc() {
|
|
let fields = {'MESSAGE': [].join.call(arguments, ', ')};
|
|
let domain = "GNOME Shell";
|
|
|
|
// If the caller is an extension, add it as metadata
|
|
let extension = imports.misc.extensionUtils.getCurrentExtension();
|
|
if (extension != null) {
|
|
domain = extension.metadata.name;
|
|
fields['GNOME_SHELL_EXTENSION_UUID'] = extension.uuid;
|
|
fields['GNOME_SHELL_EXTENSION_NAME'] = extension.metadata.name;
|
|
}
|
|
|
|
GLib.log_structured(domain, GLib.LogLevelFlags.LEVEL_MESSAGE, fields);
|
|
}
|
|
|
|
function init() {
|
|
// Add some bindings to the global JS namespace; (gjs keeps the web
|
|
// browser convention of having that namespace be called 'window'.)
|
|
window.global = Shell.Global.get();
|
|
|
|
window.log = _loggingFunc;
|
|
|
|
window._ = Gettext.gettext;
|
|
window.C_ = Gettext.pgettext;
|
|
window.ngettext = Gettext.ngettext;
|
|
window.N_ = s => s;
|
|
|
|
// Miscellaneous monkeypatching
|
|
_patchContainerClass(St.BoxLayout);
|
|
|
|
_patchLayoutClass(Clutter.TableLayout, { row_spacing: 'spacing-rows',
|
|
column_spacing: 'spacing-columns' });
|
|
_patchLayoutClass(Clutter.GridLayout, { row_spacing: 'spacing-rows',
|
|
column_spacing: 'spacing-columns' });
|
|
_patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' });
|
|
|
|
let origSetEasingDuration = Clutter.Actor.prototype.set_easing_duration;
|
|
Clutter.Actor.prototype.set_easing_duration = function(msecs) {
|
|
origSetEasingDuration.call(this, _adjustEasingTime(msecs));
|
|
};
|
|
let origSetEasingDelay = Clutter.Actor.prototype.set_easing_delay;
|
|
Clutter.Actor.prototype.set_easing_delay = function(msecs) {
|
|
origSetEasingDelay.call(this, _adjustEasingTime(msecs));
|
|
};
|
|
|
|
Clutter.Actor.prototype.ease = function(props, easingParams) {
|
|
_easeActor(this, props, easingParams);
|
|
};
|
|
|
|
Clutter.Actor.prototype.toString = function() {
|
|
return St.describe_actor(this);
|
|
};
|
|
// Deprecation warning for former JS classes turned into an actor subclass
|
|
Object.defineProperty(Clutter.Actor.prototype, 'actor', {
|
|
get() {
|
|
let klass = this.constructor.name;
|
|
let { stack } = new Error();
|
|
log(`Usage of object.actor is deprecated for ${klass}\n${stack}`);
|
|
return this;
|
|
}
|
|
});
|
|
|
|
let origToString = Object.prototype.toString;
|
|
Object.prototype.toString = function() {
|
|
let base = origToString.call(this);
|
|
try {
|
|
if ('actor' in this && this.actor instanceof Clutter.Actor)
|
|
return base.replace(/\]$/, ' delegate for ' + this.actor.toString().substring(1));
|
|
else
|
|
return base;
|
|
} catch(e) {
|
|
return base;
|
|
}
|
|
};
|
|
|
|
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783
|
|
Date.prototype.toLocaleFormat = function(format) {
|
|
return Shell.util_format_date(format, this.getTime());
|
|
};
|
|
|
|
let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR');
|
|
if (slowdownEnv) {
|
|
let factor = parseFloat(slowdownEnv);
|
|
if (!isNaN(factor) && factor > 0.0)
|
|
St.set_slow_down_factor(factor);
|
|
}
|
|
|
|
// OK, now things are initialized enough that we can import shell JS
|
|
const Format = imports.format;
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
Tweener.init();
|
|
String.prototype.format = Format.format;
|
|
}
|