// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // // A widget showing the user avatar and name import Clutter from 'gi://Clutter'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import St from 'gi://St'; import * as Params from '../misc/params.js'; const AVATAR_ICON_SIZE = 64; // Adapted from gdm/gui/user-switch-applet/applet.c // // Copyright (C) 2004-2005 James M. Cape . // Copyright (C) 2008,2009 Red Hat, Inc. export const Avatar = GObject.registerClass( class Avatar extends St.Bin { _init(user, params) { let themeContext = St.ThemeContext.get_for_stage(global.stage); params = Params.parse(params, { styleClass: 'user-icon', reactive: false, iconSize: AVATAR_ICON_SIZE, }); 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. themeContext.connectObject('notify::scale-factor', this.update.bind(this), this); } vfunc_style_changed() { super.vfunc_style_changed(); let node = this.get_theme_node(); let [found, iconSize] = node.lookup_length('icon-size', false); if (!found) return; let themeContext = St.ThemeContext.get_for_stage(global.stage); // node.lookup_length() returns a scaled value, but we // need unscaled this._iconSize = iconSize / themeContext.scaleFactor; this.update(); } setSensitive(sensitive) { this.reactive = sensitive; } update() { let iconFile = null; if (this._user) { iconFile = this._user.get_icon_file(); if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS)) iconFile = null; } let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); this.set_size( this._iconSize * scaleFactor, this._iconSize * scaleFactor); if (iconFile) { this.child = null; this.add_style_class_name('user-avatar'); 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, }); } } }); export const 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._user.connectObject( 'notify::is-loaded', this._updateUser.bind(this), 'changed', this._updateUser.bind(this), this); this._updateUser(); } vfunc_allocate(box) { this.set_allocation(box); let availWidth = box.x2 - box.x1; let availHeight = box.y2 - box.y1; let [, , natRealNameWidth] = this._realNameLabel.get_preferred_size(); let childBox = new Clutter.ActorBox(); let hiddenLabel; if (natRealNameWidth <= availWidth) { this._currentLabel = this._realNameLabel; hiddenLabel = this._userNameLabel; } else { this._currentLabel = this._userNameLabel; hiddenLabel = this._realNameLabel; } this.label_actor = this._currentLabel; hiddenLabel.allocate(childBox); childBox.set_size(availWidth, availHeight); this._currentLabel.allocate(childBox); } 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 = ''; } } }); export const UserWidget = GObject.registerClass( class UserWidget extends St.BoxLayout { _init(user, orientation = Clutter.Orientation.HORIZONTAL) { // If user is null, that implies a username-based login authorization. this._user = user; let vertical = orientation == Clutter.Orientation.VERTICAL; let xAlign = vertical ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START; let styleClass = vertical ? 'user-widget vertical' : 'user-widget horizontal'; super._init({ styleClass, vertical, xAlign, }); this._avatar = new Avatar(user); this._avatar.x_align = Clutter.ActorAlign.CENTER; this.add_child(this._avatar); this._userLoadedId = 0; this._userChangedId = 0; if (user) { this._label = new UserWidgetLabel(user); this.add_child(this._label); this._label.bind_property('label-actor', this, 'label-actor', GObject.BindingFlags.SYNC_CREATE); this._user.connectObject( 'notify::is-loaded', this._updateUser.bind(this), 'changed', this._updateUser.bind(this), this); } else { this._label = new St.Label({ style_class: 'user-widget-label', text: 'Empty User', opacity: 0, }); this.add_child(this._label); } this._updateUser(); } _updateUser() { this._avatar.update(); } });