gnome-shell/js/ui/dialog.js

243 lines
7.8 KiB
JavaScript
Raw Normal View History

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Dialog, MessageDialogContent */
const { Clutter, Gio, GObject, Pango, St } = imports.gi;
var Dialog = GObject.registerClass(
class Dialog extends St.Widget {
_init(parentActor, styleClass) {
super._init({ layout_manager: new Clutter.BinLayout() });
this.connect('destroy', this._onDestroy.bind(this));
this._initialKeyFocus = null;
this._initialKeyFocusDestroyId = 0;
this._pressedKey = null;
this._buttonKeys = {};
this._createDialog();
this.add_child(this._dialog);
if (styleClass != null)
this._dialog.add_style_class_name(styleClass);
this._parentActor = parentActor;
this._eventId = this._parentActor.connect('event', this._modalEventHandler.bind(this));
this._parentActor.add_child(this);
}
_createDialog() {
this._dialog = new St.BoxLayout({ style_class: 'modal-dialog',
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
vertical: true });
// modal dialogs are fixed width and grow vertically; set the request
// mode accordingly so wrapped labels are handled correctly during
// size requests.
this._dialog.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
js/ui: Choose some actors to cache on the GPU Flag some actors that are good candidates for caching in texture memory (what Clutter calls "offscreen redirect"), thereby mostly eliminating their repaint overhead. This isn't exactly groundbreaking, it's how you're meant to use OpenGL in the first place. But the difficulty is in the design of Clutter which has some peculiarities making universal caching inefficient at the moment: * Repainting an offscreen actor is measurably slower than repainting the same actor if it was uncached. But only by less than 100%, so if an actor can avoid changing every frame then caching is usually more efficient over that timeframe. * The cached painting from a container typically includes its children, so you can't cache containers whose children are usually animating at full frame rate. That results in a performance loss. This could be remedied in future by Clutter explicitly separating a container's background painting from its child painting and always caching the background (as StWidget tries to in some cases already). So this commit selects just a few areas where caching has been verified to be beneficial, and many use cases now see their CPU usage halved: One small window active...... 10% -> 7% (-30%) ...under a panel menu........ 23% -> 9% (-61%) One maximized window active.. 12% -> 9% (-25%) ...under a panel menu........ 23% -> 11% (-52%) ...under a shell dialog...... 22% -> 12% (-45%) ...in activities overview.... 32% -> 17% (-47%) (on an i7-7700) Also a couple of bugs are fixed by this: https://bugzilla.gnome.org/show_bug.cgi?id=792634 https://bugzilla.gnome.org/show_bug.cgi?id=792633
2018-04-06 10:26:58 +00:00
this._dialog.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
this.contentLayout = new St.BoxLayout({
vertical: true,
style_class: 'modal-dialog-content-box',
y_expand: true,
});
this._dialog.add_child(this.contentLayout);
this.buttonLayout = new St.Widget({ layout_manager: new Clutter.BoxLayout({ homogeneous: true }) });
this._dialog.add_child(this.buttonLayout);
}
_onDestroy() {
if (this._eventId != 0)
this._parentActor.disconnect(this._eventId);
this._eventId = 0;
}
_modalEventHandler(actor, event) {
if (event.type() == Clutter.EventType.KEY_PRESS) {
this._pressedKey = event.get_key_symbol();
} else if (event.type() == Clutter.EventType.KEY_RELEASE) {
let pressedKey = this._pressedKey;
this._pressedKey = null;
let symbol = event.get_key_symbol();
if (symbol != pressedKey)
return Clutter.EVENT_PROPAGATE;
let buttonInfo = this._buttonKeys[symbol];
if (!buttonInfo)
return Clutter.EVENT_PROPAGATE;
let { button, action } = buttonInfo;
if (action && button.reactive) {
action();
return Clutter.EVENT_STOP;
}
}
return Clutter.EVENT_PROPAGATE;
}
_setInitialKeyFocus(actor) {
if (this._initialKeyFocus)
this._initialKeyFocus.disconnect(this._initialKeyFocusDestroyId);
this._initialKeyFocus = actor;
this._initialKeyFocusDestroyId = actor.connect('destroy', () => {
this._initialKeyFocus = null;
this._initialKeyFocusDestroyId = 0;
});
}
get initialKeyFocus() {
return this._initialKeyFocus || this;
}
addContent(actor) {
this.contentLayout.add(actor, { expand: true });
}
addButton(buttonInfo) {
let { label, action, key } = buttonInfo;
let isDefault = buttonInfo['default'];
let keys;
if (key)
keys = [key];
else if (isDefault)
keys = [Clutter.KEY_Return, Clutter.KEY_KP_Enter, Clutter.KEY_ISO_Enter];
else
keys = [];
let button = new St.Button({ style_class: 'modal-dialog-linked-button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
x_expand: true,
y_expand: true,
label });
button.connect('clicked', action);
buttonInfo['button'] = button;
if (isDefault)
button.add_style_pseudo_class('default');
if (this._initialKeyFocus == null || isDefault)
this._setInitialKeyFocus(button);
for (let i in keys)
this._buttonKeys[keys[i]] = buttonInfo;
this.buttonLayout.add_actor(button);
return button;
}
clearButtons() {
this.buttonLayout.destroy_all_children();
this._buttonKeys = {};
}
});
var MessageDialogContent = GObject.registerClass({
Properties: {
'icon': GObject.ParamSpec.object('icon', 'icon', 'icon',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
Gio.Icon.$gtype),
'title': GObject.ParamSpec.string('title', 'title', 'title',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null),
'subtitle': GObject.ParamSpec.string('subtitle', 'subtitle', 'subtitle',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null),
'body': GObject.ParamSpec.string('body', 'body', 'body',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null),
},
}, class MessageDialogContent extends St.BoxLayout {
_init(params) {
this._icon = new St.Icon({ y_align: Clutter.ActorAlign.START });
this._title = new St.Label({ style_class: 'headline' });
this._subtitle = new St.Label();
this._body = new St.Label();
['icon', 'title', 'subtitle', 'body'].forEach(prop => {
this[`_${prop}`].add_style_class_name(`message-dialog-${prop}`);
});
let textProps = { ellipsize: Pango.EllipsizeMode.NONE,
line_wrap: true };
this._subtitle.clutter_text.set(textProps);
this._body.clutter_text.set(textProps);
let defaultParams = { style_class: 'message-dialog-main-layout' };
super._init(Object.assign(defaultParams, params));
this.messageBox = new St.BoxLayout({ style_class: 'message-dialog-content',
x_expand: true,
vertical: true });
this.messageBox.add_actor(this._title);
this.messageBox.add_actor(this._subtitle);
this.messageBox.add_actor(this._body);
this.add_actor(this._icon);
this.add_actor(this.messageBox);
}
get icon() {
return this._icon.gicon;
}
get title() {
return this._title.text;
}
get subtitle() {
return this._subtitle.text;
}
get body() {
return this._body.text;
}
set icon(icon) {
this._icon.set({
gicon: icon,
visible: icon != null,
});
this.notify('icon');
}
set title(title) {
this._setLabel(this._title, 'title', title);
}
set subtitle(subtitle) {
this._setLabel(this._subtitle, 'subtitle', subtitle);
}
set body(body) {
this._setLabel(this._body, 'body', body);
}
_setLabel(label, prop, value) {
label.set({
text: value || '',
visible: value != null,
});
this.notify(prop);
}
insertBeforeBody(actor) {
this.messageBox.insert_child_below(actor, this._body);
}
});