notifications: Add buttons to Calendar.NotificationMessage via actions

Since we also want to show the buttons in the calendar move the
code to add buttons/actions from `MessageTray.NotificationBanner` to
`Calendar.NotificationMessage`.

As nothing uses custom buttons that aren't backed by actions anymore,
remove that bit of the public API.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/7099
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3173>
This commit is contained in:
Julian Sparber 2024-02-21 12:07:10 +01:00 committed by Florian Müllner
parent 7ff5f4ea51
commit d44adf3b78
4 changed files with 71 additions and 87 deletions

View File

@ -14,12 +14,12 @@ $notification_banner_width: 34em;
@if $contrast == 'high' {
@include draw_hc_inset();
}
.notification-actions {
spacing: 0;
}
.notification-button {
@extend %bubble_button;
}
}
.notification-buttons-bin {
spacing: 0;
}
.notification-button {
@extend %bubble_button;
}

View File

@ -18,6 +18,7 @@ import {formatDateWithCFormatString} from '../misc/dateUtils.js';
import {loadInterfaceXML} from '../misc/fileUtils.js';
const SHOW_WEEKDATE_KEY = 'show-weekdate';
const MAX_NOTIFICATION_BUTTONS = 3;
const NC_ = (context, str) => `${context}\u0004${str}`;
@ -782,11 +783,19 @@ class NotificationMessage extends MessageList.Message {
});
notification.connectObject(
'updated', this._onUpdated.bind(this),
'action-added', (_, action) => this._addAction(action),
'action-removed', (_, action) => this._removeAction(action),
'destroy', () => {
this.notification = null;
if (!this._closed)
this.close();
}, this);
this._actions = new Map();
this.notification.actions.forEach(action => {
this._addAction(action);
});
}
_getIcon() {
@ -815,6 +824,36 @@ class NotificationMessage extends MessageList.Message {
canClose() {
return true;
}
_addAction(action) {
if (!this._buttonBox) {
this._buttonBox = new St.BoxLayout({
x_expand: true,
style_class: 'notification-buttons-bin',
});
this.setActionArea(this._buttonBox);
global.focus_manager.add_group(this._buttonBox);
}
if (this._buttonBox.get_n_children() >= MAX_NOTIFICATION_BUTTONS)
return;
const button = new St.Button({
style_class: 'notification-button',
x_expand: true,
label: action.label,
});
button.connect('clicked', () => action.activate());
this._actions.set(action, button);
this._buttonBox.add_child(button);
}
_removeAction(action) {
this._actions.get(action)?.destroy();
this._actions.delete(action);
}
});
const NotificationSection = GObject.registerClass(

View File

@ -469,7 +469,7 @@ export const Message = GObject.registerClass({
});
vbox.add_child(hbox);
this._actionBin = new St.Widget({
this._actionBin = new St.Bin({
layout_manager: new ScaleLayout(),
visible: false,
});
@ -557,17 +557,8 @@ export const Message = GObject.registerClass({
}
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_child(actor);
this._actionBin.visible = this.expanded;
this._actionBin.child = actor;
this._actionBin.visible = actor && this.expanded;
}
addMediaControl(iconName, callback) {
@ -583,7 +574,7 @@ export const Message = GObject.registerClass({
expand(animate) {
this.expanded = true;
this._actionBin.visible = this._actionBin.get_n_children() > 0;
this._actionBin.visible = !!this._actionBin.child;
const duration = animate ? MessageTray.ANIMATION_TIME : 0;
this._bodyStack.ease_property('@layout.expansion', 1, {

View File

@ -27,7 +27,6 @@ const LONGER_HIDE_TIMEOUT = 600;
const MAX_NOTIFICATIONS_IN_QUEUE = 3;
const MAX_NOTIFICATIONS_PER_SOURCE = 3;
const MAX_NOTIFICATION_BUTTONS = 3;
// We delay hiding of the tray if the mouse is within MOUSE_LEFT_ACTOR_THRESHOLD
// range from the point where it left the tray.
@ -498,7 +497,18 @@ export const Notification = GObject.registerClass({
// @label: the label for the action's button
// @callback: the callback for the action
addAction(label, callback) {
const action = new Action(label, callback);
const action = new Action(label, () => {
callback();
// We don't hide a resident notification when the user invokes one of its actions,
// because it is common for such notifications to update themselves with new
// information based on the action. We'd like to display the updated information
// in place, rather than pop-up a new notification.
if (this.resident)
return;
this.destroy();
});
this.actions.push(action);
this.emit('action-added', action);
}
@ -557,8 +567,14 @@ export const Notification = GObject.registerClass({
activate() {
this.emit('activated');
if (!this.resident)
this.destroy();
// We don't hide a resident notification when the user invokes one of its actions,
// because it is common for such notifications to update themselves with new
// information based on the action. We'd like to display the updated information
// in place, rather than pop-up a new notification.
if (this.resident)
return;
this.destroy();
}
destroy(reason = NotificationDestroyedReason.DISMISSED) {
@ -579,10 +595,6 @@ export const NotificationBanner = GObject.registerClass({
this.can_focus = false;
this.add_style_class_name('notification-banner');
this._buttonBox = null;
this._addActions();
this.notification.connectObject('activated', () => {
// We hide all types of notifications once the user clicks on
// them because the common outcome of clicking should be the
@ -591,64 +603,6 @@ export const NotificationBanner = GObject.registerClass({
this.emit('done-displaying');
}, this);
}
_onUpdated(n, clear) {
super._onUpdated(n, clear);
if (clear) {
this.setActionArea(null);
this._buttonBox = null;
}
this._addActions();
}
_addActions() {
this.notification.actions.forEach(action => {
this.addAction(action.label, () => action.activate());
});
}
addButton(button, callback) {
if (!this._buttonBox) {
this._buttonBox = new St.BoxLayout({
style_class: 'notification-actions',
x_expand: true,
});
this.setActionArea(this._buttonBox);
global.focus_manager.add_group(this._buttonBox);
}
if (this._buttonBox.get_n_children() >= MAX_NOTIFICATION_BUTTONS)
return null;
this._buttonBox.add_child(button);
button.connect('clicked', () => {
callback();
if (!this.notification.resident) {
// We don't hide a resident notification when the user invokes one of its actions,
// because it is common for such notifications to update themselves with new
// information based on the action. We'd like to display the updated information
// in place, rather than pop-up a new notification.
this.emit('done-displaying');
this.notification.destroy();
}
});
return button;
}
addAction(label, callback) {
const button = new St.Button({
style_class: 'notification-button',
label,
x_expand: true,
can_focus: true,
});
return this.addButton(button, callback);
}
});
export const Source = GObject.registerClass({