diff --git a/data/theme/gnome-shell-sass/widgets/_message-list.scss b/data/theme/gnome-shell-sass/widgets/_message-list.scss index 15464bd80..10fa88258 100644 --- a/data/theme/gnome-shell-sass/widgets/_message-list.scss +++ b/data/theme/gnome-shell-sass/widgets/_message-list.scss @@ -65,60 +65,96 @@ @extend %card; padding: 0 !important; - // icon container - .message-icon-bin { - padding: ($base_padding * 3); + // subtract side padding to accommodate the close button + &:ltr { padding-right: $base_padding - 2px; }; + &:rtl { padding-left: $base_padding - 2px; }; - &:ltr {padding-right:$base_padding;} - &:rtl {padding-left:$base_padding;} + // message header + .message-header { + padding: 0 $scaled_padding; + margin: 0 $base_padding; + margin-top: $base_padding; + color: $insensitive_fg_color; + spacing: $base_padding; - // icon size and color - > StIcon { - icon-size: $large_icon_size; + // header source icon + .message-source-icon { + padding: 2px 0; + icon-size: $scalable_icon_size; // 16px -st-icon-style: symbolic; } + + // box that contains the source icon, source name and timestamp of the message + .message-header-content { + spacing: $base_padding; + min-height: to_em(24px); + padding-bottom: $base_padding; + + // header source title + .message-source-title { + font-weight: bold; + } + + // Time label + .event-time { + @extend %caption; + color: $insensitive_fg_color; + // Add bottom padding to align the app name with the time horizontally + padding-bottom: to_em(1px); + + &:ltr { text-align: right }; + &:rtl { text-align: left }; + } + } + + // close button + .message-close-button { + @extend .icon-button; + color: $fg_color !important; + background-color: transparentize($fg_color, 0.8); + padding: 0 !important; + border: 4px transparent solid; + &:hover {background-color: transparentize($fg_color, 0.7);} + &:insensitive {background-color: transparentize($fg_color, 0.9);} + &:active {background-color: transparentize($fg_color, 0.8);} + } } - // content - .message-content { - spacing: 4px; - padding: ($base_padding * 1.5); - margin-bottom: $base_margin * 2; - } - - .message-title-box { + // container for message contents + .message-box { + padding: 0 $base_padding; + padding-bottom: $base_padding; + margin: $base_padding; + margin-top: $base_padding; spacing: $base_padding; - // title - .message-title { - font-weight: bold; + + // icon container + .message-icon-bin { + &:ltr {padding-right:$base_padding;} + &:rtl {padding-left:$base_padding;} + + // icon size and color + > StIcon { + icon-size: $base_icon_size * 3; // 48px + -st-icon-style: symbolic; + } } - // notification time stamp - > .event-time { - @extend %caption; - color: $insensitive_fg_color; - padding-bottom: to_em(1px); + // If the header isn't displayed we need more top margin + &:first-child { + margin-top: $base_padding * 2; + } - &:ltr { text-align: right }; - &:rtl { text-align: left }; + // text of the message + .message-content { + spacing: $base_margin; + + // message title + .message-title { + font-weight: bold; + } } } - - - // close button - .message-close-button { - @extend .icon-button; - color: $fg_color; - background-color: transparentize($fg_color, 0.8); - padding: 0 !important; - border: 5px transparent solid; - margin: 1px; - &:hover {background-color: transparentize($fg_color, 0.7);} - &:active {background-color: transparentize($fg_color, 0.8);} - } - - // body - .message-body {color: darken($fg_color, 10%);} } // URLs in messages @@ -129,7 +165,6 @@ /* Media Controls */ .message-media-control { padding: 0 $base_padding * 3; - margin: $base_padding * 2 0; border-radius: $base_border_radius; color: $fg_color; border: 1px solid transparent; @@ -158,10 +193,6 @@ } } - // fix margin for last button - &:last-child:ltr { margin-right: $base_margin * 3; } - &:last-child:rtl { margin-left: $base_margin * 3; } - & StIcon { icon-size: $base_icon_size; } } diff --git a/js/ui/calendar.js b/js/ui/calendar.js index fc4ebd1b9..455cfa184 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -767,7 +767,7 @@ export const Calendar = GObject.registerClass({ export const NotificationMessage = GObject.registerClass( class NotificationMessage extends MessageList.Message { _init(notification) { - super._init(notification.title, notification.bannerBodyText); + super._init(notification.source, notification.title, notification.bannerBodyText); this.setUseBodyMarkup(notification.bannerBodyMarkup); this.notification = notification; diff --git a/js/ui/messageList.js b/js/ui/messageList.js index 2ecea4965..8338e7766 100644 --- a/js/ui/messageList.js +++ b/js/ui/messageList.js @@ -375,6 +375,67 @@ class TimeLabel extends St.Label { } }); +const MessageHeader = GObject.registerClass( +class MessageHeader extends St.BoxLayout { + constructor(source) { + super({ + style_class: 'message-header', + x_expand: true, + }); + + const sourceIconEffect = new Clutter.DesaturateEffect(); + const sourceIcon = new St.Icon({ + style_class: 'message-source-icon', + y_align: Clutter.ActorAlign.CENTER, + fallback_icon_name: 'application-x-executable-symbolic', + }); + sourceIcon.add_effect(sourceIconEffect); + this.add_child(sourceIcon); + + sourceIcon.connect('style-changed', () => { + const themeNode = sourceIcon.get_theme_node(); + sourceIconEffect.enabled = themeNode.get_icon_style() === St.IconStyle.SYMBOLIC; + }); + + const headerContent = new St.BoxLayout({ + style_class: 'message-header-content', + y_align: Clutter.ActorAlign.CENTER, + x_expand: true, + }); + this.add_child(headerContent); + + this.closeButton = new St.Button({ + style_class: 'message-close-button', + icon_name: 'window-close-symbolic', + y_align: Clutter.ActorAlign.CENTER, + opacity: 0, + }); + this.add_child(this.closeButton); + + const sourceTitle = new St.Label({ + style_class: 'message-source-title', + y_align: Clutter.ActorAlign.END, + }); + headerContent.add_child(sourceTitle); + + source.bind_property_full('title', + sourceTitle, + 'text', + GObject.BindingFlags.SYNC_CREATE, + // Translators: this is the string displayed in the header when a message + // source doesn't have a name + (bind, value) => [true, value === null || value === '' ? _('Unknown App') : value], + null); + source.bind_property('icon', + sourceIcon, + 'gicon', + GObject.BindingFlags.SYNC_CREATE); + + this.timeLabel = new TimeLabel(); + headerContent.add_child(this.timeLabel); + } +}); + export const Message = GObject.registerClass({ Signals: { 'close': {}, @@ -382,7 +443,7 @@ export const Message = GObject.registerClass({ 'unexpanded': {}, }, }, class Message extends St.Button { - _init(title, body) { + _init(source, title, body) { super._init({ style_class: 'message', accessible_role: Atk.Role.NOTIFICATION, @@ -400,7 +461,12 @@ export const Message = GObject.registerClass({ }); this.set_child(vbox); - let hbox = new St.BoxLayout(); + this._header = new MessageHeader(source); + vbox.add_child(this._header); + + const hbox = new St.BoxLayout({ + style_class: 'message-box', + }); vbox.add_child(hbox); this._actionBin = new St.Widget({ @@ -427,26 +493,12 @@ export const Message = GObject.registerClass({ this._mediaControls = new St.BoxLayout(); hbox.add_child(this._mediaControls); - let titleBox = new St.BoxLayout({style_class: 'message-title-box'}); - contentBox.add_child(titleBox); - this.titleLabel = new St.Label({ style_class: 'message-title', y_align: Clutter.ActorAlign.END, }); this.setTitle(title); - titleBox.add_child(this.titleLabel); - - this._timeLabel = new TimeLabel(); - titleBox.add_child(this._timeLabel); - - this._closeButton = new St.Button({ - style_class: 'message-close-button', - icon_name: 'window-close-symbolic', - y_align: Clutter.ActorAlign.CENTER, - opacity: 0, - }); - titleBox.add_child(this._closeButton); + contentBox.add_child(this.titleLabel); this._bodyStack = new St.Widget({x_expand: true}); this._bodyStack.layout_manager = new LabelExpanderLayout(); @@ -457,10 +509,11 @@ export const Message = GObject.registerClass({ this._bodyStack.add_child(this.bodyLabel); this.setBody(body); - this._closeButton.connect('clicked', this.close.bind(this)); - let actorHoverId = this.connect('notify::hover', this._sync.bind(this)); - this._closeButton.connect('destroy', this.disconnect.bind(this, actorHoverId)); this.connect('destroy', this._onDestroy.bind(this)); + + this._header.closeButton.connect('clicked', this.close.bind(this)); + let actorHoverId = this.connect('notify::hover', this._sync.bind(this)); + this._header.closeButton.connect('destroy', this.disconnect.bind(this, actorHoverId)); this._sync(); } @@ -474,11 +527,11 @@ export const Message = GObject.registerClass({ } get datetime() { - return this._timeLabel.datetime; + return this._header.timeLabel.datetime; } set datetime(datetime) { - this._timeLabel.datetime = datetime; + this._header.timeLabel.datetime = datetime; } setTitle(text) { @@ -596,8 +649,8 @@ export const Message = GObject.registerClass({ _sync() { let visible = this.hover && this.canClose(); - this._closeButton.opacity = visible ? 255 : 0; - this._closeButton.reactive = visible; + this._header.closeButton.opacity = visible ? 255 : 0; + this._header.closeButton.reactive = visible; } _onDestroy() { diff --git a/js/ui/mpris.js b/js/ui/mpris.js index bf73e0ef5..78b61f4cf 100644 --- a/js/ui/mpris.js +++ b/js/ui/mpris.js @@ -23,17 +23,13 @@ const MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.'; export const MediaMessage = GObject.registerClass( class MediaMessage extends MessageList.Message { _init(player) { - super._init('', ''); + super._init(player.source, '', ''); this._player = player; this._icon = new St.Icon({style_class: 'media-message-cover-icon'}); this.setIcon(this._icon); - // reclaim space used by unused elements - this._timeLabel.hide(); - this._closeButton.hide(); - this._prevButton = this.addMediaControl('media-skip-backward-symbolic', () => { this._player.previous(); @@ -104,6 +100,7 @@ export class MprisPlayer extends Signals.EventEmitter { this._trackTitle = ''; this._trackCoverUrl = ''; this._busName = busName; + this.source = new MessageList.Source(); } get status() { @@ -229,6 +226,11 @@ export class MprisPlayer extends Signals.EventEmitter { this._app = null; } + this.source.set({ + title: this._app?.get_name() ?? null, + icon: this._app?.get_icon() ?? null, + }); + this.emit('changed'); let visible = this._playerProxy.CanPlay;