messageList: Move notification UI to its subclasses

According to the new design, there's no similarities between
Media and common Notification anymore.

See https://gitlab.gnome.org/Teams/Design/os-mockups/-/blob/master/notifications-calendar/notifications-tbernard.png
parent 9e1f3630
......@@ -154,7 +154,7 @@ stage {
@mixin notification_bubble($flat: false) {
border-width: 1px;
border-style: solid;
border-radius: $base_border_radius + 2px;
border-radius: $base_border_radius * 3;
margin: $base_margin;
@if $flat {
......
......@@ -30,18 +30,19 @@
// message bubbles
.message {
@include notification_bubble;
padding: $base_padding;
// icon container
.message-icon-bin {
padding: ($base_padding * 3) 0 ($base_padding * 3) ($base_padding * 2);
padding: ($base_padding * 2) 0 ($base_padding * 2) ($base_padding * 2);
&:rtl {
padding: ($base_padding * 3) ($base_padding * 2) ($base_padding * 3) 0;
// padding: ($base_padding * 3) ($base_padding * 3) ($base_padding * 3) 0;
}
// icon size and color
> StIcon {
icon-size: $base_icon_size*2; // 32px
icon-size: $base_icon_size;
-st-icon-style: symbolic;
}
......@@ -52,9 +53,15 @@
}
}
.message-header {
padding: $base_padding * 2;
color: desaturate(darken($fg_color,40%), 10%);
font-weight: bold;
}
// content
.message-content {
padding: $base_padding + $base_margin * 2;
padding: 0 ($base_padding * 2) ($base_padding * 2);
spacing: 4px;
}
......@@ -65,8 +72,6 @@
// secondary container in title box
.message-secondary-bin {
padding: 0 $base_margin * 2;
// notification time stamp
> .event-time {
color: transparentize($fg_color, 0.5);
......@@ -81,9 +86,14 @@
// close button
.message-close-button {
color: lighten($fg_color, 15%);
&:hover { color: if($variant=='light', lighten($fg_color, 30%), darken($fg_color, 10%)); }
&:active { color: if($variant=='light', lighten($fg_color, 40%), darken($fg_color, 20%)); }
background-color: $fg_color;
color: $base_color;
border-radius: $base_icon_size * 5;
width: 20px;
padding: 0;
&:hover { background-color: if($variant=='light', lighten($fg_color, 30%), darken($fg_color, 10%)); }
&:active { background-color: if($variant=='light', lighten($fg_color, 40%), darken($fg_color, 20%)); }
}
// body
......@@ -98,9 +108,25 @@
}
/* Media Controls */
.message-media-content {
padding: $base_padding * 2;
}
.media-body {
color: transparentize($fg_color, 0.5);
@include fontsize($base_font_size - 1);
}
.message-media-control {
padding: $base_padding * 2 $base_padding * 4;
color: darken($fg_color, 15%);
padding: $base_padding * 2;
margin: $base_padding * 2;
border-radius: $base_border_radius + 2px;
border: 1px solid $bg_color;
background-color: $hover_bg_color;
&:last-child {
margin-left: 0;
}
// uses $hover_bg_color since the media controls are in a notification_bubble
&:hover {
......@@ -114,16 +140,11 @@
}
&:insensitive { color: darken($fg_color,40%); }
// fix border-radius for last button
&:last-child:ltr { border-radius: 0 $base_border_radius+2 $base_border_radius+2 0; }
&:last-child:rtl { border-radius: $base_border_radius+2 0 0 $base_border_radius+2; }
}
// album-art
.media-message-cover-icon {
icon-size: $base_icon_size*2 !important; // 48px
border-radius: $base_border_radius;
icon-size: $base_icon_size !important; // 48px
// when there is no artwork
&.fallback {
......
......@@ -16,6 +16,7 @@ var MSECS_IN_DAY = 24 * 60 * 60 * 1000;
var SHOW_WEEKDATE_KEY = 'show-weekdate';
var MESSAGE_ICON_SIZE = -1; // pick up from CSS
var DEFAULT_EXPAND_LINES = 6;
var NC_ = (context, str) => '%s\u0004%s'.format(context, str);
......@@ -79,6 +80,95 @@ function _getCalendarDayAbbreviation(dayNumber) {
return Shell.util_translate_time_string(abbreviations[dayNumber]);
}
var LabelExpanderLayout = GObject.registerClass({
Properties: {
'expansion': GObject.ParamSpec.double('expansion',
'Expansion',
'Expansion of the layout, between 0 (collapsed) ' +
'and 1 (fully expanded',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
0, 1, 0),
},
}, class LabelExpanderLayout extends Clutter.LayoutManager {
_init(params) {
this._expansion = 0;
this._expandLines = DEFAULT_EXPAND_LINES;
super._init(params);
}
get expansion() {
return this._expansion;
}
set expansion(v) {
if (v == this._expansion)
return;
this._expansion = v;
this.notify('expansion');
let visibleIndex = this._expansion > 0 ? 1 : 0;
for (let i = 0; this._container && i < this._container.get_n_children(); i++)
this._container.get_child_at_index(i).visible = i == visibleIndex;
this.layout_changed();
}
set expandLines(v) {
if (v == this._expandLines)
return;
this._expandLines = v;
if (this._expansion > 0)
this.layout_changed();
}
vfunc_set_container(container) {
this._container = container;
}
vfunc_get_preferred_width(container, forHeight) {
let [min, nat] = [0, 0];
for (let i = 0; i < container.get_n_children(); i++) {
if (i > 1)
break; // we support one unexpanded + one expanded child
let child = container.get_child_at_index(i);
let [childMin, childNat] = child.get_preferred_width(forHeight);
[min, nat] = [Math.max(min, childMin), Math.max(nat, childNat)];
}
return [min, nat];
}
vfunc_get_preferred_height(container, forWidth) {
let [min, nat] = [0, 0];
let children = container.get_children();
if (children[0])
[min, nat] = children[0].get_preferred_height(forWidth);
if (children[1]) {
let [min2, nat2] = children[1].get_preferred_height(forWidth);
let [expMin, expNat] = [Math.min(min2, min * this._expandLines),
Math.min(nat2, nat * this._expandLines)];
[min, nat] = [min + this._expansion * (expMin - min),
nat + this._expansion * (expNat - nat)];
}
return [min, nat];
}
vfunc_allocate(container, box) {
for (let i = 0; i < container.get_n_children(); i++) {
let child = container.get_child_at_index(i);
if (child.visible)
child.allocate(box);
}
}
});
// Abstraction for an appointment/event in a calendar
var CalendarEvent = class CalendarEvent {
......@@ -722,11 +812,96 @@ var Calendar = GObject.registerClass({
var NotificationMessage = GObject.registerClass(
class NotificationMessage extends MessageList.Message {
_init(notification) {
super._init(notification.title, notification.bannerBodyText);
this.setUseBodyMarkup(notification.bannerBodyMarkup);
super._init();
this.expanded = false;
this._useBodyMarkup = false;
this.notification = notification;
let vbox = new St.BoxLayout({
vertical: true,
x_expand: true,
});
this.set_child(vbox);
let hbox = new St.BoxLayout();
vbox.add_actor(hbox);
this._iconBin = new St.Bin({
style_class: 'message-icon-bin',
y_align: Clutter.ActorAlign.CENTER,
visible: false,
});
hbox.add_actor(this._iconBin);
let contentBox = new St.BoxLayout({
style_class: 'message-content',
vertical: true,
x_expand: true,
});
vbox.add_actor(contentBox);
let headerBox = new St.BoxLayout({
style_class: 'message-header',
x_expand: true,
});
hbox.add_actor(headerBox);
headerBox.add_actor(this._iconBin);
this._header = new St.Label({
text: notification.source.title,
y_align: Clutter.ActorAlign.CENTER,
});
headerBox.add_actor(this._header);
let titleBox = new St.BoxLayout();
contentBox.add_actor(titleBox);
this.titleLabel = new St.Label({ style_class: 'message-title' });
this.setTitle(notification.title);
titleBox.add_actor(this.titleLabel);
this._secondaryBin = new St.Bin({
style_class: 'message-secondary-bin',
x_expand: true,
y_expand: false,
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.CENTER,
});
headerBox.add_actor(this._secondaryBin);
let closeIcon = new St.Icon({
icon_name: 'window-close-symbolic',
icon_size: 16,
});
this._closeButton = new St.Button({
style_class: 'message-close-button',
child: closeIcon, opacity: 0,
});
headerBox.add_actor(this._closeButton);
this._actionBin = new St.Widget({
layout_manager: new MessageList.ScaleLayout(),
visible: false,
});
vbox.add_actor(this._actionBin);
this._bodyStack = new St.Widget({ x_expand: true });
this._bodyStack.layout_manager = new LabelExpanderLayout();
contentBox.add_actor(this._bodyStack);
this.bodyLabel = new MessageList.URLHighlighter('', false, this._useBodyMarkup);
this.bodyLabel.add_style_class_name('message-body');
this._bodyStack.add_actor(this.bodyLabel);
this.setBody(notification.bannerBodyText);
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.setUseBodyMarkup(notification.bannerBodyMarkup);
this.setIcon(this._getIcon());
this.connect('close', () => {
......@@ -741,13 +916,41 @@ class NotificationMessage extends MessageList.Message {
this.close();
});
this._updatedId = notification.connect('updated',
this._onUpdated.bind(this));
this._onUpdated.bind(this));
this._sync();
}
setUseBodyMarkup(enable) {
if (this._useBodyMarkup === enable)
return;
this._useBodyMarkup = enable;
if (this.bodyLabel)
this.setBody(this._bodyText);
}
setSecondaryActor(actor) {
this._secondaryBin.child = actor;
}
_sync() {
let visible = this.hover && this.canClose();
this._secondaryBin.ease({
translation_x: visible ? -10 : 20,
duration: 500,
});
this._closeButton.ease({
duration: 300,
opacity: visible ? 255 : 0,
});
this._closeButton.reactive = visible;
}
_getIcon() {
if (this.notification.gicon) {
return new St.Icon({ gicon: this.notification.gicon,
icon_size: MESSAGE_ICON_SIZE });
return new St.Icon({
gicon: this.notification.gicon,
icon_size: MESSAGE_ICON_SIZE,
});
} else {
return this.notification.source.createIcon(MESSAGE_ICON_SIZE);
}
......
......@@ -8,8 +8,6 @@ const Util = imports.misc.util;
var MESSAGE_ANIMATION_TIME = 100;
var DEFAULT_EXPAND_LINES = 6;
function _fixMarkup(text, allowMarkup) {
if (allowMarkup) {
// Support &amp;, &quot;, &apos;, &lt; and &gt;, escape all other
......@@ -205,97 +203,6 @@ class ScaleLayout extends Clutter.BinLayout {
}
});
var LabelExpanderLayout = GObject.registerClass({
Properties: {
'expansion': GObject.ParamSpec.double('expansion',
'Expansion',
'Expansion of the layout, between 0 (collapsed) ' +
'and 1 (fully expanded',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
0, 1, 0),
},
}, class LabelExpanderLayout extends Clutter.LayoutManager {
_init(params) {
this._expansion = 0;
this._expandLines = DEFAULT_EXPAND_LINES;
super._init(params);
}
get expansion() {
return this._expansion;
}
set expansion(v) {
if (v == this._expansion)
return;
this._expansion = v;
this.notify('expansion');
let visibleIndex = this._expansion > 0 ? 1 : 0;
for (let i = 0; this._container && i < this._container.get_n_children(); i++)
this._container.get_child_at_index(i).visible = i == visibleIndex;
this.layout_changed();
}
set expandLines(v) {
if (v == this._expandLines)
return;
this._expandLines = v;
if (this._expansion > 0)
this.layout_changed();
}
vfunc_set_container(container) {
this._container = container;
}
vfunc_get_preferred_width(container, forHeight) {
let [min, nat] = [0, 0];
for (let i = 0; i < container.get_n_children(); i++) {
if (i > 1)
break; // we support one unexpanded + one expanded child
let child = container.get_child_at_index(i);
let [childMin, childNat] = child.get_preferred_width(forHeight);
[min, nat] = [Math.max(min, childMin), Math.max(nat, childNat)];
}
return [min, nat];
}
vfunc_get_preferred_height(container, forWidth) {
let [min, nat] = [0, 0];
let children = container.get_children();
if (children[0])
[min, nat] = children[0].get_preferred_height(forWidth);
if (children[1]) {
let [min2, nat2] = children[1].get_preferred_height(forWidth);
let [expMin, expNat] = [Math.min(min2, min * this._expandLines),
Math.min(nat2, nat * this._expandLines)];
[min, nat] = [min + this._expansion * (expMin - min),
nat + this._expansion * (expNat - nat)];
}
return [min, nat];
}
vfunc_allocate(container, box) {
for (let i = 0; i < container.get_n_children(); i++) {
let child = container.get_child_at_index(i);
if (child.visible)
child.allocate(box);
}
}
});
var Message = GObject.registerClass({
Signals: {
'close': {},
......@@ -303,7 +210,7 @@ var Message = GObject.registerClass({
'unexpanded': {},
},
}, class Message extends St.Button {
_init(title, body) {
_init() {
super._init({
style_class: 'message',
accessible_role: Atk.Role.NOTIFICATION,
......@@ -311,71 +218,6 @@ var Message = GObject.registerClass({
x_expand: true,
y_expand: true,
});
this.expanded = false;
this._useBodyMarkup = false;
let vbox = new St.BoxLayout({
vertical: true,
x_expand: true,
});
this.set_child(vbox);
let hbox = new St.BoxLayout();
vbox.add_actor(hbox);
this._actionBin = new St.Widget({ layout_manager: new ScaleLayout(),
visible: false });
vbox.add_actor(this._actionBin);
this._iconBin = new St.Bin({ style_class: 'message-icon-bin',
y_expand: true,
y_align: Clutter.ActorAlign.START,
visible: false });
hbox.add_actor(this._iconBin);
let contentBox = new St.BoxLayout({ style_class: 'message-content',
vertical: true, x_expand: true });
hbox.add_actor(contentBox);
this._mediaControls = new St.BoxLayout();
hbox.add_actor(this._mediaControls);
let titleBox = new St.BoxLayout();
contentBox.add_actor(titleBox);
this.titleLabel = new St.Label({ style_class: 'message-title' });
this.setTitle(title);
titleBox.add_actor(this.titleLabel);
this._secondaryBin = new St.Bin({
style_class: 'message-secondary-bin',
x_expand: true, y_expand: true,
});
titleBox.add_actor(this._secondaryBin);
let closeIcon = new St.Icon({ icon_name: 'window-close-symbolic',
icon_size: 16 });
this._closeButton = new St.Button({
style_class: 'message-close-button',
child: closeIcon, opacity: 0,
});
titleBox.add_actor(this._closeButton);
this._bodyStack = new St.Widget({ x_expand: true });
this._bodyStack.layout_manager = new LabelExpanderLayout();
contentBox.add_actor(this._bodyStack);
this.bodyLabel = new URLHighlighter('', false, this._useBodyMarkup);
this.bodyLabel.add_style_class_name('message-body');
this._bodyStack.add_actor(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._sync();
}
close() {
......@@ -387,10 +229,6 @@ var Message = GObject.registerClass({
this._iconBin.visible = actor != null;
}
setSecondaryActor(actor) {
this._secondaryBin.child = actor;
}
setTitle(text) {
let title = text ? _fixMarkup(text.replace(/\n/g, ' '), false) : '';
this.titleLabel.clutter_text.set_markup(title);
......@@ -399,59 +237,11 @@ var Message = GObject.registerClass({
setBody(text) {
this._bodyText = text;
this.bodyLabel.setMarkup(text ? text.replace(/\n/g, ' ') : '',
this._useBodyMarkup);
this._useBodyMarkup);
if (this._expandedLabel)
this._expandedLabel.setMarkup(text, this._useBodyMarkup);
}
setUseBodyMarkup(enable) {
if (this._useBodyMarkup === enable)
return;
this._useBodyMarkup = enable;
if (this.bodyLabel)
this.setBody(this._bodyText);
}
setActionArea(actor) {
if (actor == null) {
if (this._actionBin.get_n_children() > 0)
this._actionBin.get_child_at_index(0).destroy();
return;
}
if (this._actionBin.get_n_children() > 0)
throw new Error('Message already has an action area');
this._actionBin.add_actor(actor);
this._actionBin.visible = this.expanded;
}
addMediaControl(iconName, callback) {
let icon = new St.Icon({ icon_name: iconName, icon_size: 16 });
let button = new St.Button({ style_class: 'message-media-control',
child: icon });
button.connect('clicked', callback);
this._mediaControls.add_actor(button);
return button;
}
setExpandedBody(actor) {
if (actor == null) {
if (this._bodyStack.get_n_children() > 1)
this._bodyStack.get_child_at_index(1).destroy();
return;
}
if (this._bodyStack.get_n_children() > 1)
throw new Error('Message already has an expanded body actor');
this._bodyStack.insert_child_at_index(actor, 1);
}
setExpandedLines(nLines) {
this._bodyStack.layout_manager.expandLines = nLines;
}
expand(animate) {
this.expanded = true;
......@@ -483,6 +273,10 @@ var Message = GObject.registerClass({
this.emit('expanded');
}
canClose() {
return false;