gnome-shell/js/ui/userWidget.js
Jonas Ådahl 988a0e7314 Adapt to painting and picking API change
While still leaving them unused, pass around ClutterPaintContext and
ClutterPickContext when painting and picking.

The reason for splitting this change up in two is to make it possible to
bisect easier in between the API change and the change to using the
framebuffer passed around with the temporary contexts.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/827
2019-12-03 19:07:15 +00:00

199 lines
6.4 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
//
// A widget showing the user avatar and name
/* exported UserWidget */
const { Clutter, GLib, GObject, St } = imports.gi;
const Params = imports.misc.params;
var AVATAR_ICON_SIZE = 64;
// Adapted from gdm/gui/user-switch-applet/applet.c
//
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
// Copyright (C) 2008,2009 Red Hat, Inc.
var Avatar = GObject.registerClass(
class Avatar extends St.Bin {
_init(user, params) {
let themeContext = St.ThemeContext.get_for_stage(global.stage);
params = Params.parse(params, { reactive: false,
iconSize: AVATAR_ICON_SIZE,
styleClass: 'user-icon' });
super._init({
style_class: params.styleClass,
reactive: params.reactive,
width: params.iconSize * themeContext.scaleFactor,
height: params.iconSize * themeContext.scaleFactor,
});
this._iconSize = params.iconSize;
this._user = user;
this.bind_property('reactive', this, 'track-hover',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('reactive', this, 'can-focus',
GObject.BindingFlags.SYNC_CREATE);
// Monitor the scaling factor to make sure we recreate the avatar when needed.
this._scaleFactorChangeId =
themeContext.connect('notify::scale-factor', this.update.bind(this));
this.connect('destroy', this._onDestroy.bind(this));
}
_onDestroy() {
if (this._scaleFactorChangeId) {
let themeContext = St.ThemeContext.get_for_stage(global.stage);
themeContext.disconnect(this._scaleFactorChangeId);
delete this._scaleFactorChangeId;
}
}
setSensitive(sensitive) {
this.reactive = sensitive;
}
update() {
let iconFile = this._user.get_icon_file();
if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS))
iconFile = null;
if (iconFile) {
this.child = null;
let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
this.set_size(
this._iconSize * scaleFactor,
this._iconSize * scaleFactor);
this.style = `
background-image: url("${iconFile}");
background-size: cover;`;
} else {
this.style = null;
this.child = new St.Icon({ icon_name: 'avatar-default-symbolic',
icon_size: this._iconSize });
}
}
});
var UserWidgetLabel = GObject.registerClass(
class UserWidgetLabel extends St.Widget {
_init(user) {
super._init({ layout_manager: new Clutter.BinLayout() });
this._user = user;
this._realNameLabel = new St.Label({ style_class: 'user-widget-label',
y_align: Clutter.ActorAlign.CENTER });
this.add_child(this._realNameLabel);
this._userNameLabel = new St.Label({ style_class: 'user-widget-label',
y_align: Clutter.ActorAlign.CENTER });
this.add_child(this._userNameLabel);
this._currentLabel = null;
this._userLoadedId = this._user.connect('notify::is-loaded', this._updateUser.bind(this));
this._userChangedId = this._user.connect('changed', this._updateUser.bind(this));
this._updateUser();
// We can't override the destroy vfunc because that might be called during
// object finalization, and we can't call any JS inside a GC finalize callback,
// so we use a signal, that will be disconnected by GObject the first time
// the actor is destroyed (which is guaranteed to be as part of a normal
// destroy() call from JS, possibly from some ancestor)
this.connect('destroy', this._onDestroy.bind(this));
}
_onDestroy() {
if (this._userLoadedId != 0) {
this._user.disconnect(this._userLoadedId);
this._userLoadedId = 0;
}
if (this._userChangedId != 0) {
this._user.disconnect(this._userChangedId);
this._userChangedId = 0;
}
}
vfunc_allocate(box, flags) {
this.set_allocation(box, flags);
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
let [, , natRealNameWidth] = this._realNameLabel.get_preferred_size();
if (natRealNameWidth <= availWidth)
this._currentLabel = this._realNameLabel;
else
this._currentLabel = this._userNameLabel;
this.label_actor = this._currentLabel;
let childBox = new Clutter.ActorBox();
childBox.x1 = 0;
childBox.y1 = 0;
childBox.x2 = availWidth;
childBox.y2 = availHeight;
this._currentLabel.allocate(childBox, flags);
}
vfunc_paint(paintContext) {
this._currentLabel.paint(paintContext);
}
_updateUser() {
if (this._user.is_loaded) {
this._realNameLabel.text = this._user.get_real_name();
this._userNameLabel.text = this._user.get_user_name();
} else {
this._realNameLabel.text = '';
this._userNameLabel.text = '';
}
}
});
var UserWidget = GObject.registerClass(
class UserWidget extends St.BoxLayout {
_init(user) {
super._init({ style_class: 'user-widget', vertical: false });
this._user = user;
this.connect('destroy', this._onDestroy.bind(this));
this._avatar = new Avatar(user);
this.add_child(this._avatar);
this._label = new UserWidgetLabel(user);
this.add_child(this._label);
this._label.bind_property('label-actor', this, 'label-actor',
GObject.BindingFlags.SYNC_CREATE);
this._userLoadedId = this._user.connect('notify::is-loaded', this._updateUser.bind(this));
this._userChangedId = this._user.connect('changed', this._updateUser.bind(this));
this._updateUser();
}
_onDestroy() {
if (this._userLoadedId != 0) {
this._user.disconnect(this._userLoadedId);
this._userLoadedId = 0;
}
if (this._userChangedId != 0) {
this._user.disconnect(this._userChangedId);
this._userChangedId = 0;
}
}
_updateUser() {
this._avatar.update();
}
});