From 59a7fdd2c9387ee4b9b6d2b92375e57496ff718f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20D=C3=A9murget?= Date: Sun, 18 Nov 2012 22:49:54 +0100 Subject: [PATCH] Login: add a spinner for better process indication We need to do a better job of indicating login process. This can sometimes take a few seconds (particularly if you get your password wrong): we need to give better feedback of what's going on. This adds a spinner next to the login button if the authorization takes some time. https://bugzilla.gnome.org/show_bug.cgi?id=687113 --- data/theme/gnome-shell.css | 4 ++ js/gdm/loginDialog.js | 104 +++++++++++++++++++++++++++++-------- js/ui/unlockDialog.js | 66 +++++++++++++++++++---- 3 files changed, 142 insertions(+), 32 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index e99acb059..4890f2ca4 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -2213,6 +2213,10 @@ StScrollBar StButton#vhandle:active { height: .75em; } +.login-dialog .modal-dialog-button-box { + spacing: 3px; +} + .login-dialog .modal-dialog-button { border-radius: 5px; padding: 3px 18px; diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js index c72ce4781..17103227d 100644 --- a/js/gdm/loginDialog.js +++ b/js/gdm/loginDialog.js @@ -39,6 +39,7 @@ const GdmUtil = imports.gdm.util; const Lightbox = imports.ui.lightbox; const Main = imports.ui.main; const ModalDialog = imports.ui.modalDialog; +const Panel = imports.ui.panel; const PanelMenu = imports.ui.panelMenu; const Tweener = imports.ui.tweener; const UserMenu = imports.ui.userMenu; @@ -48,6 +49,10 @@ const _SCROLL_ANIMATION_TIME = 0.5; const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; const _LOGO_ICON_HEIGHT = 16; +const WORK_SPINNER_ICON_SIZE = 24; +const WORK_SPINNER_ANIMATION_DELAY = 1.0; +const WORK_SPINNER_ANIMATION_TIME = 0.3; + let _loginDialog = null; function _smoothlyResizeActor(actor, width, height) { @@ -760,6 +765,7 @@ const LoginDialog = new Lang.Class({ this._promptBox.add(this._promptLoginHint); this._signInButton = null; + this._workSpinner = null; this._sessionList = new SessionList(); this._sessionList.connect('session-activated', @@ -855,6 +861,7 @@ const LoginDialog = new Lang.Class({ this._promptEntry.text = ''; this._updateSensitivity(true); + this._setWorking(false); }, _onDefaultSessionChanged: function(client, sessionId) { @@ -927,34 +934,12 @@ const LoginDialog = new Lang.Class({ _showPrompt: function(forSecret) { let hold = new Batch.Hold(); - let cancelButtonInfo = { action: Lang.bind(this, this.cancel), - label: _("Cancel"), - key: Clutter.Escape }; - let okButtonInfo = { action: Lang.bind(this, function() { - hold.release(); - }), - label: forSecret ? C_("button", "Sign In") : _("Next"), - default: true }; - let buttons = []; - if (!this._disableUserList || this._verifyingUser) - buttons.push(cancelButtonInfo); - buttons.push(okButtonInfo); - let tasks = [function() { return this._fadeInPrompt(); }, function() { - this.setButtons(buttons); - this._signInButton = okButtonInfo.button; - - this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); - - this._promptEntryTextChangedId = - this._promptEntry.clutter_text.connect('text-changed', - Lang.bind(this, function() { - this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); - })); + this._prepareDialog(forSecret, hold); }, hold]; @@ -964,6 +949,49 @@ const LoginDialog = new Lang.Class({ return batch.run(); }, + _prepareDialog: function(forSecret, hold) { + this._workSpinner = new Panel.AnimatedIcon('process-working.svg', WORK_SPINNER_ICON_SIZE); + this._workSpinner.actor.opacity = 0; + this._workSpinner.actor.show(); + + this.buttonLayout.visible = true; + this.clearButtons(); + + if (!this._disableUserList || this._verifyingUser) + this.addButton({ action: Lang.bind(this, this.cancel), + label: _("Cancel"), + key: Clutter.Escape }, + { expand: true, + x_fill: false, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.MIDDLE }); + this.buttonLayout.add(this._workSpinner.actor, + { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.MIDDLE }); + this._signInButton = this.addButton({ action: Lang.bind(this, function() { + hold.release(); + }), + label: forSecret ? C_("button", "Sign In") : _("Next"), + default: true }, + { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.MIDDLE }); + + this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); + + this._promptEntryTextChangedId = + this._promptEntry.clutter_text.connect('text-changed', + Lang.bind(this, function() { + this._updateSignInButtonSensitivity(this._promptEntry.text.length > 0); + })); + }, + _updateSensitivity: function(sensitive) { this._promptEntry.reactive = sensitive; this._promptEntry.clutter_text.editable = sensitive; @@ -987,6 +1015,8 @@ const LoginDialog = new Lang.Class({ } let tasks = [function() { + this._setWorking(false); + return GdmUtil.fadeOutActor(this._promptBox); }, @@ -996,6 +1026,8 @@ const LoginDialog = new Lang.Class({ this._updateSensitivity(true); this._promptEntry.set_text(''); + this.clearButtons(); + this._workSpinner = null; this._signInButton = null; }]; @@ -1004,6 +1036,31 @@ const LoginDialog = new Lang.Class({ return batch.run(); }, + _setWorking: function(working) { + if (!this._workSpinner) + return; + + if (working) { + this._workSpinner.play(); + Tweener.addTween(this._workSpinner.actor, + { opacity: 255, + delay: WORK_SPINNER_ANIMATION_DELAY, + time: WORK_SPINNER_ANIMATION_TIME, + transition: 'linear' + }); + } else { + Tweener.addTween(this._workSpinner.actor, + { opacity: 0, + time: WORK_SPINNER_ANIMATION_TIME, + transition: 'linear', + onCompleteScope: this, + onComplete: function() { + this._workSpinner.stop(); + } + }); + } + }, + _askQuestion: function(verifier, serviceName, question, passwordChar) { this._promptLabel.set_text(question); @@ -1017,6 +1074,7 @@ const LoginDialog = new Lang.Class({ function() { let text = this._promptEntry.get_text(); this._updateSensitivity(false); + this._setWorking(true); this._userVerifier.answerQuery(serviceName, text); }]; diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js index 548bbe768..5cdce9295 100644 --- a/js/ui/unlockDialog.js +++ b/js/ui/unlockDialog.js @@ -14,12 +14,14 @@ const St = imports.gi.St; const Main = imports.ui.main; const ModalDialog = imports.ui.modalDialog; +const Panel = imports.ui.panel; const ShellEntry = imports.ui.shellEntry; const Tweener = imports.ui.tweener; const UserMenu = imports.ui.userMenu; const Batch = imports.gdm.batch; const GdmUtil = imports.gdm.util; +const LoginDialog = imports.gdm.loginDialog; // The timeout before going back automatically to the lock screen (in seconds) const IDLE_TIMEOUT = 2 * 60; @@ -168,13 +170,33 @@ const UnlockDialog = new Lang.Class({ this._promptLoginHint.hide(); this.contentLayout.add_actor(this._promptLoginHint); - let cancelButton = { label: _("Cancel"), - action: Lang.bind(this, this._escape), - key: Clutter.KEY_Escape }; - this._okButton = { label: _("Unlock"), - action: Lang.bind(this, this._doUnlock), - default: true }; - this.setButtons([cancelButton, this._okButton]); + this._workSpinner = new Panel.AnimatedIcon('process-working.svg', LoginDialog.WORK_SPINNER_ICON_SIZE); + this._workSpinner.actor.opacity = 0; + this._workSpinner.actor.show(); + + this.buttonLayout.visible = true; + this.addButton({ label: _("Cancel"), + action: Lang.bind(this, this._escape), + key: Clutter.KEY_Escape }, + { expand: true, + x_fill: false, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.MIDDLE }); + this.buttonLayout.add(this._workSpinner.actor, + { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.MIDDLE }); + this._okButton = this.addButton({ label: _("Unlock"), + action: Lang.bind(this, this._doUnlock), + default: true }, + { expand: false, + x_fill: false, + y_fill: false, + x_align: St.Align.END, + y_align: St.Align.MIDDLE }); let otherUserLabel = new St.Label({ text: _("Log in as another user"), style_class: 'login-dialog-not-listed-label' }); @@ -214,8 +236,30 @@ const UnlockDialog = new Lang.Class({ }, _updateOkButtonSensitivity: function(sensitive) { - this._okButton.button.reactive = sensitive; - this._okButton.button.can_focus = sensitive; + this._okButton.reactive = sensitive; + this._okButton.can_focus = sensitive; + }, + + _setWorking: function(working) { + if (working) { + this._workSpinner.play(); + Tweener.addTween(this._workSpinner.actor, + { opacity: 255, + delay: LoginDialog.WORK_SPINNER_ANIMATION_DELAY, + time: LoginDialog.WORK_SPINNER_ANIMATION_TIME, + transition: 'linear' + }); + } else { + Tweener.addTween(this._workSpinner.actor, + { opacity: 0, + time: LoginDialog.WORK_SPINNER_ANIMATION_TIME, + transition: 'linear', + onCompleteScope: this, + onComplete: function() { + this._workSpinner.stop(); + } + }); + } }, _showMessage: function(userVerifier, message, styleClass) { @@ -248,6 +292,7 @@ const UnlockDialog = new Lang.Class({ this._currentQuery = serviceName; this._updateSensitivity(true); + this._setWorking(false); }, _showLoginHint: function(verifier, message) { @@ -266,6 +311,7 @@ const UnlockDialog = new Lang.Class({ // the actual reply to GDM will be sent as soon as asked this._firstQuestionAnswer = this._promptEntry.text; this._updateSensitivity(false); + this._setWorking(true); return; } @@ -276,6 +322,7 @@ const UnlockDialog = new Lang.Class({ this._currentQuery = null; this._updateSensitivity(false); + this._setWorking(true); this._userVerifier.answerQuery(query, this._promptEntry.text); }, @@ -296,6 +343,7 @@ const UnlockDialog = new Lang.Class({ this._promptEntry.text = ''; this._updateSensitivity(false); + this._setWorking(false); }, _escape: function() {