gnome-shell/js/ui/closeDialog.js
Florian Müllner 0846238f69 js: Use implicit animations for animatable properties
We now have everything in place to replace Tweener for all animatable
properties with implicit animations, which has the following benefits:

 - they run entirely in C, while Tweener requires context switches
   to JS each frame

 - they are more reliable, as Tweener only detects when an animation
   is overwritten with another Tween, while Clutter considers any
   property change

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/22
2019-08-06 23:54:29 +02:00

191 lines
5.9 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported CloseDialog */
const { Clutter, Gio, GLib, GObject, Meta, Shell } = imports.gi;
const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
var FROZEN_WINDOW_BRIGHTNESS = -0.3;
var DIALOG_TRANSITION_TIME = 150;
var ALIVE_TIMEOUT = 5000;
var CloseDialog = GObject.registerClass({
Implements: [Meta.CloseDialog],
Properties: {
'window': GObject.ParamSpec.override('window', Meta.CloseDialog)
},
}, class CloseDialog extends GObject.Object {
_init(window) {
super._init();
this._window = window;
this._dialog = null;
this._tracked = undefined;
this._timeoutId = 0;
this._windowFocusChangedId = 0;
this._keyFocusChangedId = 0;
}
get window() {
return this._window;
}
set window(window) {
this._window = window;
}
_createDialogContent() {
let tracker = Shell.WindowTracker.get_default();
let windowApp = tracker.get_window_app(this._window);
/* Translators: %s is an application name */
let title = _("“%s” is not responding.").format(windowApp.get_name());
let subtitle = _("You may choose to wait a short while for it to " +
"continue or force the application to quit entirely.");
let icon = new Gio.ThemedIcon({ name: 'dialog-warning-symbolic' });
return new Dialog.MessageDialogContent({ icon, title, subtitle });
}
_initDialog() {
if (this._dialog)
return;
let windowActor = this._window.get_compositor_private();
this._dialog = new Dialog.Dialog(windowActor, 'close-dialog');
this._dialog.width = windowActor.width;
this._dialog.height = windowActor.height;
this._dialog.addContent(this._createDialogContent());
this._dialog.addButton({ label: _('Force Quit'),
action: this._onClose.bind(this),
default: true });
this._dialog.addButton({ label: _('Wait'),
action: this._onWait.bind(this),
key: Clutter.Escape });
global.focus_manager.add_group(this._dialog);
}
_addWindowEffect() {
// We set the effect on the surface actor, so the dialog itself
// (which is a child of the MetaWindowActor) does not get the
// effect applied itself.
let windowActor = this._window.get_compositor_private();
let surfaceActor = windowActor.get_first_child();
let effect = new Clutter.BrightnessContrastEffect();
effect.set_brightness(FROZEN_WINDOW_BRIGHTNESS);
surfaceActor.add_effect_with_name("gnome-shell-frozen-window", effect);
}
_removeWindowEffect() {
let windowActor = this._window.get_compositor_private();
let surfaceActor = windowActor.get_first_child();
surfaceActor.remove_effect_by_name("gnome-shell-frozen-window");
}
_onWait() {
this.response(Meta.CloseDialogResponse.WAIT);
}
_onClose() {
this.response(Meta.CloseDialogResponse.FORCE_CLOSE);
}
_onFocusChanged() {
if (Meta.is_wayland_compositor())
return;
let focusWindow = global.display.focus_window;
let keyFocus = global.stage.key_focus;
let shouldTrack;
if (focusWindow != null)
shouldTrack = focusWindow == this._window;
else
shouldTrack = keyFocus && this._dialog.contains(keyFocus);
if (this._tracked === shouldTrack)
return;
if (shouldTrack)
Main.layoutManager.trackChrome(this._dialog,
{ affectsInputRegion: true });
else
Main.layoutManager.untrackChrome(this._dialog);
// The buttons are broken when they aren't added to the input region,
// so disable them properly in that case
this._dialog.buttonLayout.get_children().forEach(b => {
b.reactive = shouldTrack;
});
this._tracked = shouldTrack;
}
vfunc_show() {
if (this._dialog != null)
return;
Meta.disable_unredirect_for_display(global.display);
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ALIVE_TIMEOUT,
() => {
this._window.check_alive(global.display.get_current_time_roundtrip());
return GLib.SOURCE_CONTINUE;
});
this._windowFocusChangedId =
global.display.connect('notify::focus-window',
this._onFocusChanged.bind(this));
this._keyFocusChangedId =
global.stage.connect('notify::key-focus',
this._onFocusChanged.bind(this));
this._addWindowEffect();
this._initDialog();
this._dialog.scale_y = 0;
this._dialog.set_pivot_point(0.5, 0.5);
this._dialog.ease({
scale_y: 1,
mode: Clutter.AnimationMode.LINEAR,
duration: DIALOG_TRANSITION_TIME,
onComplete: this._onFocusChanged.bind(this)
});
}
vfunc_hide() {
if (this._dialog == null)
return;
Meta.enable_unredirect_for_display(global.display);
GLib.source_remove(this._timeoutId);
this._timeoutId = 0;
global.display.disconnect(this._windowFocusChangedId);
this._windowFocusChangedId = 0;
global.stage.disconnect(this._keyFocusChangedId);
this._keyFocusChangedId = 0;
let dialog = this._dialog;
this._dialog = null;
this._removeWindowEffect();
dialog.ease({
scale_y: 0,
mode: Clutter.AnimationMode.LINEAR,
duration: DIALOG_TRANSITION_TIME,
onComplete: () => dialog.destroy()
});
}
vfunc_focus() {
if (this._dialog)
this._dialog.grab_key_focus();
}
});