diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index e82b2e649..99b70b72d 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -473,25 +473,24 @@ StTooltip { } /* Message Tray */ +#message-tray { + background-gradient-direction: vertical; + background-gradient-start: rgba(0,0,0,0.01); + background-gradient-end: rgba(0,0,0,0.95); + height: 28px; +} + #notification { border-radius: 5px; background: rgba(0,0,0,0.9); - border: 1px solid rgba(128,128,128,0.45); color: white; - padding: 4px; + padding: 2px 10px; spacing: 10px; } -#message-tray { - border-radius: 5px; - background: rgba(0,0,0,0.9); - border: 1px solid rgba(128,128,128,0.45); - padding: 4px; - height: 40px; -} - -#message-tray-inner { +#summary-mode { spacing: 10px; + padding: 2px 4px; } /* App Switcher */ diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index be21c4245..3522a1adc 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -16,11 +16,22 @@ const MESSAGE_TRAY_TIMEOUT = 0.2; const ICON_SIZE = 24; -function Notification() { - this._init(); +function Notification(icon, text) { + this._init(icon, text); } Notification.prototype = { + _init: function(icon, text) { + this.icon = icon; + this.text = text; + } +} + +function NotificationBox() { + this._init(); +} + +NotificationBox.prototype = { _init: function() { this.actor = new St.BoxLayout({ name: 'notification' }); @@ -29,55 +40,11 @@ Notification.prototype = { this._text = new St.Label(); this.actor.add(this._text, { expand: true, x_fill: false, y_fill: false, y_align: St.Align.MIDDLE }); - - Main.chrome.addActor(this.actor, { affectsStruts: false }); - - let primary = global.get_primary_monitor(); - this.actor.y = primary.height; - - this._hideTimeoutId = 0; }, - show: function(icon, text) { - let primary = global.get_primary_monitor(); - - if (this._hideTimeoutId > 0) - Mainloop.source_remove(this._hideTimeoutId); - this._hideTimeoutId = Mainloop.timeout_add(NOTIFICATION_TIMEOUT * 1000, Lang.bind(this, this.hide)); - - this._iconBox.child = icon; - this._text.text = text; - - this.actor.x = Math.round((primary.width - this.actor.width) / 2); - this.actor.show(); - Tweener.addTween(this.actor, - { y: primary.height - this.actor.height, - time: ANIMATION_TIME, - transition: "easeOutQuad" - }); - }, - - hide: function() { - let primary = global.get_primary_monitor(); - this._hideTimeoutId = 0; - - Tweener.addTween(this.actor, - { y: primary.height, - time: ANIMATION_TIME, - transition: "easeOutQuad", - onComplete: this.hideComplete, - onCompleteScope: this - }); - return false; - }, - - hideComplete: function() { - if (this._iconBox.child) - this._iconBox.child.destroy(); - - // Don't hide the notification if we are showing a new one. - if (this._hideTimeoutId == 0) - this.actor.hide(); + setContent: function(notification) { + this._iconBox.child = notification.icon; + this._text.text = notification.text; } }; @@ -99,7 +66,7 @@ Source.prototype = { }, notify: function(text) { - Main.notificationPopup.show(this.createIcon(ICON_SIZE), text); + Main.messageTray.showNotification(new Notification(this.createIcon(ICON_SIZE), text)); }, clicked: function() { @@ -118,10 +85,8 @@ function MessageTray() { MessageTray.prototype = { _init: function() { - this.actor = new St.Bin({ name: 'message-tray', - reactive: true, - x_align: St.Align.END }); - Main.chrome.addActor(this.actor, { affectsStruts: false }); + this.actor = new St.BoxLayout({ name: 'message-tray', + reactive: true }); let primary = global.get_primary_monitor(); this.actor.x = 0; @@ -129,6 +94,17 @@ MessageTray.prototype = { this.actor.width = primary.width; + this._summaryBin = new St.Bin({ x_align: St.Align.END }); + this.actor.add(this._summaryBin, { expand: true }); + this._summaryBin.hide(); + + this._notificationBox = new NotificationBox(); + this._notificationQueue = []; + this.actor.add(this._notificationBox.actor); + this._notificationBox.actor.hide(); + + Main.chrome.addActor(this.actor, { affectsStruts: false }); + this.actor.connect('enter-event', Lang.bind(this, this._onMessageTrayEntered)); this.actor.connect('leave-event', @@ -136,9 +112,9 @@ MessageTray.prototype = { this._isShowing = false; this.actor.show(); - this._tray = new St.BoxLayout({ name: 'message-tray-inner' }); - this.actor.child = this._tray; - this._tray.expand = true; + this._summary = new St.BoxLayout({ name: 'summary-mode' }); + this._summaryBin.child = this._summary; + this._sources = {}; this._icons = {}; }, @@ -155,7 +131,7 @@ MessageTray.prototype = { let iconBox = new St.Bin({ reactive: true }); iconBox.child = source.createIcon(ICON_SIZE); - this._tray.insert_actor(iconBox, 0); + this._summary.insert_actor(iconBox, 0); this._icons[source.id] = iconBox; this._sources[source.id] = source; @@ -174,7 +150,7 @@ MessageTray.prototype = { if (!this.contains(source)) return; - this._tray.remove_actor(this._icons[source.id]); + this._summary.remove_actor(this._icons[source.id]); delete this._icons[source.id]; delete this._sources[source.id]; }, @@ -184,12 +160,29 @@ MessageTray.prototype = { }, _onMessageTrayEntered: function() { + // Don't hide the message tray after a timeout if the user has moved the mouse over it. + // We might have a timeout in place if the user moved the mouse away from the message tray for a very short period of time + // or if we are showing a notification. if (this._hideTimeoutId > 0) Mainloop.source_remove(this._hideTimeoutId); if (this._isShowing) return; + // If the message tray was not already showing, we'll show it in the summary mode. + this._summaryBin.show(); + this._show(); + }, + + _onMessageTrayLeft: function() { + if (!this._isShowing) + return; + + // We wait just a little before hiding the message tray in case the user will quickly move the mouse back over it. + this._hideTimeoutId = Mainloop.timeout_add(MESSAGE_TRAY_TIMEOUT * 1000, Lang.bind(this, this._hide)); + }, + + _show: function() { this._isShowing = true; let primary = global.get_primary_monitor(); Tweener.addTween(this.actor, @@ -199,15 +192,7 @@ MessageTray.prototype = { }); }, - _onMessageTrayLeft: function() { - if (!this._isShowing) - return; - - this._hideTimeoutId = Mainloop.timeout_add(MESSAGE_TRAY_TIMEOUT * 1000, Lang.bind(this, this._hide)); - }, - _hide: function() { - this._isShowing = false; this._hideTimeoutId = 0; let primary = global.get_primary_monitor(); @@ -215,9 +200,36 @@ MessageTray.prototype = { Tweener.addTween(this.actor, { y: primary.height - 1, time: ANIMATION_TIME, - transition: "easeOutQuad" + transition: "easeOutQuad", + onComplete: this._hideComplete, + onCompleteScope: this }); return false; - } -}; + }, + _hideComplete: function() { + this._isShowing = false; + this._summaryBin.hide(); + this._notificationBox.actor.hide(); + if (this._notificationQueue.length > 0) + this.showNotification(this._notificationQueue.shift()); + }, + + showNotification: function(notification) { + if (this._isShowing) { + this._notificationQueue.push(notification); + return; + } + + this._notificationBox.setContent(notification); + + this._notificationBox.actor.x = Math.round((this.actor.width - this._notificationBox.actor.width) / 2); + this._notificationBox.actor.show(); + + // Because we set up the timeout before we do the animation, we add ANIMATION_TIME to NOTIFICATION_TIMEOUT, so that + // NOTIFICATION_TIMEOUT represents the time the notifiation is fully shown. + this._hideTimeoutId = Mainloop.timeout_add((NOTIFICATION_TIMEOUT + ANIMATION_TIME) * 1000, Lang.bind(this, this._hide)); + + this._show(); + } +};