mirror of
https://github.com/edu4rdshl/gnome-shell.git
synced 2026-07-18 07:34:54 +00:00
messageList: Drop MessageSection in favor of MessageView
We need more control over the MessageView for grouping messages by app.
This commit is contained in:
parent
0022b8fd51
commit
bd6e7f6c43
3 changed files with 12 additions and 359 deletions
|
|
@ -39,19 +39,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.message-list-sections {
|
||||
// to account for scrollbar
|
||||
&:ltr {margin-right: $base_margin * 3; }
|
||||
&:rtl {margin-left: $base_margin * 3;}
|
||||
|
||||
spacing: $base_padding * 2;
|
||||
}
|
||||
|
||||
.message-list-section,
|
||||
.message-list-section-list {
|
||||
spacing: $base_padding * 2;
|
||||
}
|
||||
|
||||
// do-not-disturb + clear button
|
||||
.message-list-controls {
|
||||
// NOTE: remove the padding if notification_bubble could remove margin for drop shadow
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import St from 'gi://St';
|
|||
|
||||
import * as MessageList from './messageList.js';
|
||||
import * as PopupMenu from './popupMenu.js';
|
||||
import {ensureActorVisibleInScrollView} from '../misc/animationUtils.js';
|
||||
|
||||
import {formatDateWithCFormatString} from '../misc/dateUtils.js';
|
||||
import {loadInterfaceXML} from '../misc/fileUtils.js';
|
||||
|
|
@ -797,8 +796,8 @@ class DoNotDisturbSwitch extends PopupMenu.Switch {
|
|||
|
||||
export const CalendarMessageList = GObject.registerClass(
|
||||
class CalendarMessageList extends St.Widget {
|
||||
_init() {
|
||||
super._init({
|
||||
constructor() {
|
||||
super({
|
||||
style_class: 'message-list',
|
||||
layout_manager: new Clutter.BinLayout(),
|
||||
x_expand: true,
|
||||
|
|
@ -815,10 +814,13 @@ class CalendarMessageList extends St.Widget {
|
|||
});
|
||||
this.add_child(box);
|
||||
|
||||
this._messageView = new MessageList.MessageView();
|
||||
|
||||
this._scrollView = new St.ScrollView({
|
||||
style_class: 'vfade',
|
||||
overlay_scrollbars: true,
|
||||
x_expand: true, y_expand: true,
|
||||
child: this._messageView,
|
||||
});
|
||||
box.add_child(this._scrollView);
|
||||
|
||||
|
|
@ -854,52 +856,18 @@ class CalendarMessageList extends St.Widget {
|
|||
accessible_name: C_('action', 'Clear all notifications'),
|
||||
});
|
||||
this._clearButton.connect('clicked', () => {
|
||||
this._sectionList.get_children().forEach(s => s.clear());
|
||||
this._messageView.clear();
|
||||
});
|
||||
hbox.add_child(this._clearButton);
|
||||
|
||||
this._placeholder.bind_property('visible',
|
||||
this._clearButton, 'visible',
|
||||
GObject.BindingFlags.INVERT_BOOLEAN);
|
||||
|
||||
this._sectionList = new St.BoxLayout({
|
||||
style_class: 'message-list-sections',
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
});
|
||||
this._sectionList.connectObject(
|
||||
'child-added', this._sync.bind(this),
|
||||
'child-removed', this._sync.bind(this),
|
||||
this);
|
||||
this._scrollView.child = this._sectionList;
|
||||
|
||||
this._mediaSection = new MessageList.MediaSection();
|
||||
this._addSection(this._mediaSection);
|
||||
|
||||
this._notificationSection = new MessageList.NotificationSection();
|
||||
this._addSection(this._notificationSection);
|
||||
}
|
||||
|
||||
_addSection(section) {
|
||||
section.connectObject(
|
||||
'notify::visible', this._sync.bind(this),
|
||||
'notify::empty', this._sync.bind(this),
|
||||
'notify::can-clear', this._sync.bind(this),
|
||||
'destroy', () => this._sectionList.remove_child(section),
|
||||
'message-focused', (_s, messageActor) => {
|
||||
ensureActorVisibleInScrollView(this._scrollView, messageActor);
|
||||
}, this);
|
||||
this._sectionList.add_child(section);
|
||||
}
|
||||
|
||||
_sync() {
|
||||
let sections = this._sectionList.get_children();
|
||||
|
||||
let empty = sections.every(s => s.empty || !s.visible);
|
||||
this._placeholder.visible = empty;
|
||||
|
||||
let canClear = sections.some(s => s.canClear && s.visible);
|
||||
this._clearButton.reactive = canClear;
|
||||
this._messageView.bind_property('empty',
|
||||
this._placeholder, 'visible',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
this._messageView.bind_property('can-clear',
|
||||
this._clearButton, 'reactive',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -803,308 +803,6 @@ class MediaMessage extends Message {
|
|||
}
|
||||
});
|
||||
|
||||
export const MessageListSection = GObject.registerClass({
|
||||
Properties: {
|
||||
'can-clear': GObject.ParamSpec.boolean(
|
||||
'can-clear', 'can-clear', 'can-clear',
|
||||
GObject.ParamFlags.READABLE,
|
||||
false),
|
||||
'empty': GObject.ParamSpec.boolean(
|
||||
'empty', 'empty', 'empty',
|
||||
GObject.ParamFlags.READABLE,
|
||||
true),
|
||||
},
|
||||
Signals: {
|
||||
'can-clear-changed': {},
|
||||
'empty-changed': {},
|
||||
'message-focused': {param_types: [Message.$gtype]},
|
||||
},
|
||||
}, class MessageListSection extends St.BoxLayout {
|
||||
_init() {
|
||||
super._init({
|
||||
style_class: 'message-list-section',
|
||||
clip_to_allocation: true,
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
});
|
||||
|
||||
this._list = new St.BoxLayout({
|
||||
style_class: 'message-list-section-list',
|
||||
vertical: true,
|
||||
});
|
||||
this.add_child(this._list);
|
||||
|
||||
this._list.connect('child-added', this._sync.bind(this));
|
||||
this._list.connect('child-removed', this._sync.bind(this));
|
||||
|
||||
Main.sessionMode.connectObject(
|
||||
'updated', () => this._sync(), this);
|
||||
|
||||
this._empty = true;
|
||||
this._canClear = false;
|
||||
this._sync();
|
||||
}
|
||||
|
||||
get empty() {
|
||||
return this._empty;
|
||||
}
|
||||
|
||||
get canClear() {
|
||||
return this._canClear;
|
||||
}
|
||||
|
||||
get _messages() {
|
||||
return this._list.get_children().map(i => i.child);
|
||||
}
|
||||
|
||||
_onKeyFocusIn(messageActor) {
|
||||
this.emit('message-focused', messageActor);
|
||||
}
|
||||
|
||||
addMessage(message, animate) {
|
||||
this.addMessageAtIndex(message, -1, animate);
|
||||
}
|
||||
|
||||
addMessageAtIndex(message, index, animate) {
|
||||
if (this._messages.includes(message))
|
||||
throw new Error('Message was already added previously');
|
||||
|
||||
let listItem = new St.Bin({
|
||||
child: message,
|
||||
layout_manager: new ScaleLayout(),
|
||||
pivot_point: new Graphene.Point({x: .5, y: .5}),
|
||||
});
|
||||
listItem._connectionsIds = [];
|
||||
|
||||
listItem._connectionsIds.push(message.connect('key-focus-in',
|
||||
this._onKeyFocusIn.bind(this)));
|
||||
listItem._connectionsIds.push(message.connect('close', () => {
|
||||
this.removeMessage(message, true);
|
||||
}));
|
||||
listItem._connectionsIds.push(message.connect('destroy', () => {
|
||||
listItem._connectionsIds.forEach(id => message.disconnect(id));
|
||||
listItem.destroy();
|
||||
}));
|
||||
|
||||
this._list.insert_child_at_index(listItem, index);
|
||||
|
||||
const duration = animate ? MESSAGE_ANIMATION_TIME : 0;
|
||||
listItem.set({scale_x: 0, scale_y: 0});
|
||||
listItem.ease({
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
duration,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
}
|
||||
|
||||
moveMessage(message, index, animate) {
|
||||
if (!this._messages.includes(message))
|
||||
throw new Error('Impossible to move untracked message');
|
||||
|
||||
let listItem = message.get_parent();
|
||||
|
||||
if (!animate) {
|
||||
this._list.set_child_at_index(listItem, index);
|
||||
return;
|
||||
}
|
||||
|
||||
let onComplete = () => {
|
||||
this._list.set_child_at_index(listItem, index);
|
||||
listItem.ease({
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
duration: MESSAGE_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
};
|
||||
listItem.ease({
|
||||
scale_x: 0,
|
||||
scale_y: 0,
|
||||
duration: MESSAGE_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete,
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(message, animate) {
|
||||
const messages = this._messages;
|
||||
|
||||
if (!messages.includes(message))
|
||||
throw new Error('Impossible to remove untracked message');
|
||||
|
||||
let listItem = message.get_parent();
|
||||
listItem._connectionsIds.forEach(id => message.disconnect(id));
|
||||
|
||||
let nextMessage = null;
|
||||
|
||||
if (message.has_key_focus()) {
|
||||
const index = messages.indexOf(message);
|
||||
nextMessage =
|
||||
messages[index + 1] ||
|
||||
messages[index - 1] ||
|
||||
this._list;
|
||||
}
|
||||
|
||||
const duration = animate ? MESSAGE_ANIMATION_TIME : 0;
|
||||
listItem.ease({
|
||||
scale_x: 0,
|
||||
scale_y: 0,
|
||||
duration,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => {
|
||||
listItem.destroy();
|
||||
nextMessage?.grab_key_focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
let messages = this._messages.filter(msg => msg.canClose());
|
||||
|
||||
// If there are few messages, letting them all zoom out looks OK
|
||||
if (messages.length < 2) {
|
||||
messages.forEach(message => {
|
||||
message.close();
|
||||
});
|
||||
} else {
|
||||
// Otherwise we slide them out one by one, and then zoom them
|
||||
// out "off-screen" in the end to smoothly shrink the parent
|
||||
let delay = MESSAGE_ANIMATION_TIME / Math.max(messages.length, 5);
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
let message = messages[i];
|
||||
message.get_parent().ease({
|
||||
translation_x: this._list.width,
|
||||
opacity: 0,
|
||||
duration: MESSAGE_ANIMATION_TIME,
|
||||
delay: i * delay,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => message.close(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_shouldShow() {
|
||||
return !this.empty;
|
||||
}
|
||||
|
||||
_sync() {
|
||||
let messages = this._messages;
|
||||
let empty = messages.length === 0;
|
||||
|
||||
if (this._empty !== empty) {
|
||||
this._empty = empty;
|
||||
this.notify('empty');
|
||||
}
|
||||
|
||||
let canClear = messages.some(m => m.canClose());
|
||||
if (this._canClear !== canClear) {
|
||||
this._canClear = canClear;
|
||||
this.notify('can-clear');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const NotificationSection = GObject.registerClass(
|
||||
class NotificationSection extends MessageListSection {
|
||||
_init() {
|
||||
super._init();
|
||||
|
||||
this._nUrgent = 0;
|
||||
|
||||
Main.messageTray.connect('source-added', this._sourceAdded.bind(this));
|
||||
Main.messageTray.getSources().forEach(source => {
|
||||
this._sourceAdded(Main.messageTray, source);
|
||||
});
|
||||
}
|
||||
|
||||
_sourceAdded(tray, source) {
|
||||
source.connectObject(
|
||||
'notification-added', this._onNotificationAdded.bind(this),
|
||||
'notification-removed', this._onNotificationRemoved.bind(this),
|
||||
this);
|
||||
}
|
||||
|
||||
_onNotificationAdded(source, notification) {
|
||||
let message = new NotificationMessage(notification);
|
||||
|
||||
const isUrgent = notification.urgency === MessageTray.Urgency.CRITICAL;
|
||||
|
||||
notification.connectObject(
|
||||
'notify::datetime', () => {
|
||||
// The datetime property changes whenever the notification is updated
|
||||
this.moveMessage(message, isUrgent ? 0 : this._nUrgent, this.mapped);
|
||||
}, this);
|
||||
|
||||
if (isUrgent) {
|
||||
// Keep track of urgent notifications to keep them on top
|
||||
this._nUrgent++;
|
||||
} else if (this.mapped) {
|
||||
// Only acknowledge non-urgent notifications in case it
|
||||
// has important actions that are inaccessible when not
|
||||
// shown as banner
|
||||
notification.acknowledged = true;
|
||||
}
|
||||
|
||||
const index = isUrgent ? 0 : this._nUrgent;
|
||||
this.addMessageAtIndex(message, index, this.mapped);
|
||||
}
|
||||
|
||||
_onNotificationRemoved(source_, notification) {
|
||||
const message = this._messages.find(m => m.notification === notification);
|
||||
|
||||
if (notification.urgency === MessageTray.Urgency.CRITICAL)
|
||||
this._nUrgent--;
|
||||
|
||||
this.removeMessage(message, this.mapped);
|
||||
}
|
||||
|
||||
vfunc_map() {
|
||||
this._messages.forEach(message => {
|
||||
if (message.notification.urgency !== MessageTray.Urgency.CRITICAL)
|
||||
message.notification.acknowledged = true;
|
||||
});
|
||||
super.vfunc_map();
|
||||
}
|
||||
});
|
||||
|
||||
export const MediaSection = GObject.registerClass(
|
||||
class MediaSection extends MessageListSection {
|
||||
constructor() {
|
||||
super();
|
||||
this._players = new Map();
|
||||
this._mediaSource = new Mpris.MediaSource();
|
||||
|
||||
this._mediaSource.connectObject(
|
||||
'player-added', (_, player) => this._addPlayer(player),
|
||||
'player-removed', (_, player) => this._removePlayer(player),
|
||||
this);
|
||||
|
||||
this._mediaSource.players.forEach(player => {
|
||||
this._addPlayer(player);
|
||||
});
|
||||
}
|
||||
|
||||
_addPlayer(player) {
|
||||
if (this._players.has(player))
|
||||
throw new Error('Player was already added previously');
|
||||
|
||||
const message = new MediaMessage(player);
|
||||
this._players.set(player, message);
|
||||
this.addMessage(message, true);
|
||||
}
|
||||
|
||||
_removePlayer(player) {
|
||||
const message = this._players.get(player);
|
||||
|
||||
if (message)
|
||||
this.removeMessage(message, true);
|
||||
|
||||
this._players.delete(player);
|
||||
}
|
||||
});
|
||||
|
||||
export const MessageView = GObject.registerClass({
|
||||
Implements: [St.Scrollable],
|
||||
Properties: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue