diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 73f6f20cc..fa378db8e 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -2591,3 +2591,7 @@ StScrollBar StButton#vhandle:active { -boxpointer-gap: 4px; -arrow-rise: 0px; } + +.top-bar-notification-preview { + font-weight: normal; +} diff --git a/js/ui/main.js b/js/ui/main.js index 36c374f60..50b22fbaf 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -150,8 +150,8 @@ function _initializeUI() { if (LoginManager.canLock()) screenShield = new ScreenShield.ScreenShield(); - panel = new Panel.Panel(); messageTray = new MessageTray.MessageTray(); + panel = new Panel.Panel(); keyboard = new Keyboard.Keyboard(); notificationDaemon = new NotificationDaemon.NotificationDaemon(); windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler(); diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index c6e8c551d..86138059a 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -559,6 +559,8 @@ const Notification = new Lang.Class({ this._buttonBox.destroy_all_children(); } + this.gicon = params.gicon; + if (this._icon && (params.gicon || params.clear)) { this._icon.destroy(); this._icon = null; @@ -603,6 +605,8 @@ const Notification = new Lang.Class({ // is done correctly automatically. this.actor.set_text_direction(titleDirection); + this.bannerBodyText = banner; + this._bodyUrlHighlighter.setMarkup(banner, params.bannerMarkup); if (this._soundName != params.soundName || @@ -918,11 +922,94 @@ const Source = new Lang.Class({ }); Signals.addSignalMethods(Source.prototype); +const TopBarNotificationPreview = new Lang.Class({ + Name: 'TopBarNotificationPreview', + + _init: function() { + this.actor = new St.BoxLayout({ style_class: 'top-bar-notification-preview' }); + + this._icon = new St.Icon({ icon_size: 22 }); + this.actor.add_child(this._icon); + + this._title = new St.Label({ style_class: 'top-bar-notification-preview-title' }); + this.actor.add_child(this._title); + + this._body = new St.Label({ style_class: 'top-bar-notification-preview-body' }); + this.actor.add_child(this._body); + }, + + setNotification: function(notification) { + this._icon.gicon = notification.gicon; + + let title = notification.title; + title = title ? _fixMarkup(title.replace(/\n/g, ' '), false) : ''; + this._title.clutter_text.set_markup('' + title + ''); + + this._body.clutter_text.set_text(_fixMarkup(notification.bannerBodyText, false)); + }, +}); + +const TopBarNotificationController = new Lang.Class({ + Name: 'TopBarNotificationController', + + _init: function() { + this.actor = new St.Widget(); + + this._notificationPreview = new TopBarNotificationPreview(); + this.actor.add_child(this._notificationPreview.actor); + + this._notificationQueue = []; + this._timeoutId = 0; + }, + + _timeout: function() { + this._notificationQueue.shift(); + this._update(); + this._timeoutId = 0; + return GLib.SOURCE_REMOVE; + }, + + _ensureTimeout: function() { + if (this._timeoutId == 0) + this._timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 2, Lang.bind(this, this._timeout)); + }, + + get hasNotification() { + return (this._notificationQueue.length > 0); + }, + + _update: function() { + if (this._notificationQueue.length > 0) { + let notification = this._notificationQueue[0]; + this._notificationPreview.setNotification(notification); + + this._ensureTimeout(); + } + + this.emit('updated'); + }, + + pushNotification: function(notification) { + this._notificationQueue.push(notification); + + notification.connect('destroy', Lang.bind(this, function() { + let idx = this._notificationQueue.indexOf(notification); + this._notificationQueue.splice(idx, 1); + this._update(); + })); + + this._update(); + }, +}); +Signals.addSignalMethods(TopBarNotificationController.prototype); + const MessageTray = new Lang.Class({ Name: 'MessageTray', _init: function() { this._sources = new Map(); + + this.notificationPreview = new TopBarNotificationController(); }, _expireNotification: function() { @@ -1003,7 +1090,7 @@ const MessageTray = new Lang.Class({ }, _onNotify: function(source, notification) { - // Fill in here. + this.notificationPreview.pushNotification(notification); }, }); Signals.addSignalMethods(MessageTray.prototype); diff --git a/js/ui/panel.js b/js/ui/panel.js index 750149e58..d750a5aea 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -862,11 +862,56 @@ const AggregateMenu = new Lang.Class({ }, }); +const DateMenuButton2 = new Lang.Class({ + Name: 'DateMenuButton2', + + _init: function() { + this.container = new St.Widget({ layout_manager: new Clutter.BinLayout() }); + + this._notificationPreview = Main.messageTray.notificationPreview; + this.container.add_child(this._notificationPreview.actor); + this._notificationPreview.actor.x_expand = true; + this._notificationPreview.actor.x_align = Clutter.ActorAlign.CENTER; + this._notificationPreview.connect('updated', Lang.bind(this, this._sync)); + + let dateMenu = new imports.ui.dateMenu.DateMenuButton(); + this._clock = dateMenu.container; + this._clock.x_expand = true; + this._clock.x_align = Clutter.ActorAlign.CENTER; + this.container.add_child(this._clock); + + this._currentlyShowing = 'clock'; + this._sync(); + }, + + _show: function(which, animate) { + if (this._currentlyShowing == which) + return; + + this._currentlyShowing = which; + if (this._currentlyShowing == 'clock') { + this._notificationPreview.actor.visible = false; + this._clock.visible = true; + } else if (this._currentlyShowing == 'notification') { + this._notificationPreview.actor.visible = true; + this._clock.visible = false; + } + }, + + _sync: function() { + if (this._currentlyShowing == 'clock' && this._notificationPreview.hasNotification) + this._show('notification', true); + else if (this._currentlyShowing == 'notification' && !this._notificationPreview.hasNotification) + this._show('clock', false); + }, +}); +Signals.addSignalMethods(DateMenuButton2.prototype); + const PANEL_ITEM_IMPLEMENTATIONS = { 'activities': ActivitiesButton, 'aggregateMenu': AggregateMenu, 'appMenu': AppMenuButton, - 'dateMenu': imports.ui.dateMenu.DateMenuButton, + 'dateMenu': DateMenuButton2, 'a11y': imports.ui.status.accessibility.ATIndicator, 'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator, 'keyboard': imports.ui.status.keyboard.InputSourceIndicator,