.gitlab-ci
.settings
data
docs
js
extensionPrefs
gdm
misc
perf
portalHelper
ui
components
status
accessDialog.js
altTab.js
animation.js
appDisplay.js
appFavorites.js
audioDeviceSelection.js
background.js
backgroundMenu.js
barLevel.js
boxpointer.js
calendar.js
checkBox.js
closeDialog.js
ctrlAltTab.js
dash.js
dateMenu.js
dialog.js
dnd.js
edgeDragAction.js
endSessionDialog.js
environment.js
extensionDownloader.js
extensionSystem.js
focusCaretTracker.js
grabHelper.js
ibusCandidatePopup.js
iconGrid.js
inhibitShortcutsDialog.js
kbdA11yDialog.js
keyboard.js
layout.js
lightbox.js
locatePointer.js
lookingGlass.js
magnifier.js
magnifierDBus.js
main.js
messageList.js
messageTray.js
modalDialog.js
mpris.js
notificationDaemon.js
osdMonitorLabeler.js
osdWindow.js
overview.js
overviewControls.js
padOsd.js
pageIndicators.js
panel.js
panelMenu.js
pointerA11yTimeout.js
pointerWatcher.js
popupMenu.js
remoteSearch.js
ripples.js
runDialog.js
screenShield.js
screencast.js
screenshot.js
scripting.js
search.js
sessionMode.js
shellDBus.js
shellEntry.js
shellMountOperation.js
slider.js
switchMonitor.js
switcherPopup.js
tweener.js
unlockDialog.js
userWidget.js
viewSelector.js
windowAttentionHandler.js
windowManager.js
windowMenu.js
workspace.js
workspaceSwitcherPopup.js
workspaceThumbnail.js
workspacesView.js
xdndHandler.js
js-resources.gresource.xml
meson.build
portal-resources.gresource.xml
prefs-resources.gresource.xml
lint
man
meson
po
src
subprojects
test
tests
tools
.eslintrc.json
.gitignore
.gitlab-ci.yml
.gitmodules
COPYING
HACKING.md
NEWS
README.md
README.mdwn
cldr2json.py
config.h.meson
gnome-shell.doap
meson.build
meson_options.txt

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
260 lines
8.1 KiB
JavaScript
260 lines
8.1 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported ModalDialog */
|
|
|
|
const { Atk, Clutter, GObject, Shell, St } = imports.gi;
|
|
|
|
const Dialog = imports.ui.dialog;
|
|
const Layout = imports.ui.layout;
|
|
const Lightbox = imports.ui.lightbox;
|
|
const Main = imports.ui.main;
|
|
const Params = imports.misc.params;
|
|
|
|
var OPEN_AND_CLOSE_TIME = 100;
|
|
var FADE_OUT_DIALOG_TIME = 1000;
|
|
|
|
var State = {
|
|
OPENED: 0,
|
|
CLOSED: 1,
|
|
OPENING: 2,
|
|
CLOSING: 3,
|
|
FADED_OUT: 4
|
|
};
|
|
|
|
var ModalDialog = GObject.registerClass({
|
|
Properties: {
|
|
'state': GObject.ParamSpec.int('state', 'Dialog state', 'state',
|
|
GObject.ParamFlags.READABLE,
|
|
Math.min(...Object.values(State)),
|
|
Math.max(...Object.values(State)),
|
|
State.CLOSED)
|
|
},
|
|
Signals: { 'opened': {}, 'closed': {} }
|
|
}, class ModalDialog extends St.Widget {
|
|
_init(params) {
|
|
super._init({ visible: false,
|
|
x: 0,
|
|
y: 0,
|
|
accessible_role: Atk.Role.DIALOG });
|
|
|
|
params = Params.parse(params, { shellReactive: false,
|
|
styleClass: null,
|
|
actionMode: Shell.ActionMode.SYSTEM_MODAL,
|
|
shouldFadeIn: true,
|
|
shouldFadeOut: true,
|
|
destroyOnClose: true });
|
|
|
|
this._state = State.CLOSED;
|
|
this._hasModal = false;
|
|
this._actionMode = params.actionMode;
|
|
this._shellReactive = params.shellReactive;
|
|
this._shouldFadeIn = params.shouldFadeIn;
|
|
this._shouldFadeOut = params.shouldFadeOut;
|
|
this._destroyOnClose = params.destroyOnClose;
|
|
|
|
Main.layoutManager.modalDialogGroup.add_actor(this);
|
|
|
|
let constraint = new Clutter.BindConstraint({ source: global.stage,
|
|
coordinate: Clutter.BindCoordinate.ALL });
|
|
this.add_constraint(constraint);
|
|
|
|
this.backgroundStack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
|
this._backgroundBin = new St.Bin({ child: this.backgroundStack,
|
|
x_fill: true, y_fill: true });
|
|
this._monitorConstraint = new Layout.MonitorConstraint();
|
|
this._backgroundBin.add_constraint(this._monitorConstraint);
|
|
this.add_actor(this._backgroundBin);
|
|
|
|
this.dialogLayout = new Dialog.Dialog(this.backgroundStack, params.styleClass);
|
|
this.contentLayout = this.dialogLayout.contentLayout;
|
|
this.buttonLayout = this.dialogLayout.buttonLayout;
|
|
|
|
if (!this._shellReactive) {
|
|
this._lightbox = new Lightbox.Lightbox(this,
|
|
{ inhibitEvents: true,
|
|
radialEffect: true });
|
|
this._lightbox.highlight(this._backgroundBin);
|
|
|
|
this._eventBlocker = new Clutter.Actor({ reactive: true });
|
|
this.backgroundStack.add_actor(this._eventBlocker);
|
|
}
|
|
|
|
global.focus_manager.add_group(this.dialogLayout);
|
|
this._initialKeyFocus = null;
|
|
this._initialKeyFocusDestroyId = 0;
|
|
this._savedKeyFocus = null;
|
|
}
|
|
|
|
get state() {
|
|
return this._state;
|
|
}
|
|
|
|
_setState(state) {
|
|
if (this._state == state)
|
|
return;
|
|
|
|
this._state = state;
|
|
this.notify('state');
|
|
}
|
|
|
|
clearButtons() {
|
|
this.dialogLayout.clearButtons();
|
|
}
|
|
|
|
setButtons(buttons) {
|
|
this.clearButtons();
|
|
|
|
for (let buttonInfo of buttons)
|
|
this.addButton(buttonInfo);
|
|
}
|
|
|
|
addButton(buttonInfo) {
|
|
return this.dialogLayout.addButton(buttonInfo);
|
|
}
|
|
|
|
_fadeOpen(onPrimary) {
|
|
if (onPrimary)
|
|
this._monitorConstraint.primary = true;
|
|
else
|
|
this._monitorConstraint.index = global.display.get_current_monitor();
|
|
|
|
this._setState(State.OPENING);
|
|
|
|
this.dialogLayout.opacity = 255;
|
|
if (this._lightbox)
|
|
this._lightbox.show();
|
|
this.opacity = 0;
|
|
this.show();
|
|
this.ease({
|
|
opacity: 255,
|
|
duration: this._shouldFadeIn ? OPEN_AND_CLOSE_TIME : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => {
|
|
this._setState(State.OPENED);
|
|
this.emit('opened');
|
|
}
|
|
});
|
|
}
|
|
|
|
setInitialKeyFocus(actor) {
|
|
if (this._initialKeyFocusDestroyId)
|
|
this._initialKeyFocus.disconnect(this._initialKeyFocusDestroyId);
|
|
|
|
this._initialKeyFocus = actor;
|
|
|
|
this._initialKeyFocusDestroyId = actor.connect('destroy', () => {
|
|
this._initialKeyFocus = null;
|
|
this._initialKeyFocusDestroyId = 0;
|
|
});
|
|
}
|
|
|
|
open(timestamp, onPrimary) {
|
|
if (this.state == State.OPENED || this.state == State.OPENING)
|
|
return true;
|
|
|
|
if (!this.pushModal(timestamp))
|
|
return false;
|
|
|
|
this._fadeOpen(onPrimary);
|
|
return true;
|
|
}
|
|
|
|
_closeComplete() {
|
|
this._setState(State.CLOSED);
|
|
this.hide();
|
|
this.emit('closed');
|
|
|
|
if (this._destroyOnClose)
|
|
this.destroy();
|
|
}
|
|
|
|
close(timestamp) {
|
|
if (this.state == State.CLOSED || this.state == State.CLOSING)
|
|
return;
|
|
|
|
this._setState(State.CLOSING);
|
|
this.popModal(timestamp);
|
|
this._savedKeyFocus = null;
|
|
|
|
if (this._shouldFadeOut) {
|
|
this.ease({
|
|
opacity: 0,
|
|
duration: OPEN_AND_CLOSE_TIME,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => this._closeComplete()
|
|
});
|
|
} else {
|
|
this._closeComplete();
|
|
}
|
|
}
|
|
|
|
// Drop modal status without closing the dialog; this makes the
|
|
// dialog insensitive as well, so it needs to be followed shortly
|
|
// by either a close() or a pushModal()
|
|
popModal(timestamp) {
|
|
if (!this._hasModal)
|
|
return;
|
|
|
|
let focus = global.stage.key_focus;
|
|
if (focus && this.contains(focus))
|
|
this._savedKeyFocus = focus;
|
|
else
|
|
this._savedKeyFocus = null;
|
|
Main.popModal(this, timestamp);
|
|
this._hasModal = false;
|
|
|
|
if (!this._shellReactive)
|
|
this._eventBlocker.raise_top();
|
|
}
|
|
|
|
pushModal(timestamp) {
|
|
if (this._hasModal)
|
|
return true;
|
|
|
|
let params = { actionMode: this._actionMode };
|
|
if (timestamp)
|
|
params['timestamp'] = timestamp;
|
|
if (!Main.pushModal(this, params))
|
|
return false;
|
|
|
|
this._hasModal = true;
|
|
if (this._savedKeyFocus) {
|
|
this._savedKeyFocus.grab_key_focus();
|
|
this._savedKeyFocus = null;
|
|
} else {
|
|
let focus = this._initialKeyFocus || this.dialogLayout.initialKeyFocus;
|
|
focus.grab_key_focus();
|
|
}
|
|
|
|
if (!this._shellReactive)
|
|
this._eventBlocker.lower_bottom();
|
|
return true;
|
|
}
|
|
|
|
// This method is like close, but fades the dialog out much slower,
|
|
// and leaves the lightbox in place. Once in the faded out state,
|
|
// the dialog can be brought back by an open call, or the lightbox
|
|
// can be dismissed by a close call.
|
|
//
|
|
// The main point of this method is to give some indication to the user
|
|
// that the dialog response has been acknowledged but will take a few
|
|
// moments before being processed.
|
|
// e.g., if a user clicked "Log Out" then the dialog should go away
|
|
// immediately, but the lightbox should remain until the logout is
|
|
// complete.
|
|
_fadeOutDialog(timestamp) {
|
|
if (this.state == State.CLOSED || this.state == State.CLOSING)
|
|
return;
|
|
|
|
if (this.state == State.FADED_OUT)
|
|
return;
|
|
|
|
this.popModal(timestamp);
|
|
this.dialogLayout.ease({
|
|
opacity: 0,
|
|
duration: FADE_OUT_DIALOG_TIME,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => this.state = State.FADED_OUT
|
|
});
|
|
}
|
|
});
|