Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions data/theme/cinnamon-sass/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ $destructive_color: #ff7b63;
$warning_color: #f8e45c;
$warning_bg_color: #cd9309;

$notification_badge_bg_color: #ef2591;

$accent_color: #78aeed;
$accent_bg_color: #3584e4;

Expand Down
20 changes: 16 additions & 4 deletions data/theme/cinnamon-sass/widgets/_windowlist.scss
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,26 @@

&-button-label { padding-left: 4px;}

&-number-label {
font-size: 0.8em;
&-windows-badge {
z-index: 99;
border-radius: 9999px;
background-color: $bg_color;
color: $fg_color
}

&-windows-badge-label {
font-size: 0.8em;
}

&-badge {
&-notifications-badge {
z-index: 99;
border-radius: 9999px;
background-color: $bg_color;
background-color: $notification_badge_bg_color;
color: $fg_color
}

&-notifications-badge-label {
font-size: 0.8em;
}
}

Expand Down
106 changes: 72 additions & 34 deletions files/usr/share/cinnamon/applets/[email protected]/appGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class AppGroup {
this.labelVisiblePref = this.state.settings.titleDisplay !== TitleDisplay.None && this.state.isHorizontal;
this.drawLabel = this.labelVisiblePref;
this.progress = 0;
this.notifications = [];

this.actor = new Cinnamon.GenericContainer({
name: 'appButton',
Expand All @@ -119,31 +120,50 @@ class AppGroup {
});
this.actor.add_child(this.progressOverlay);

// Create the app button icon, number label, and text label for titleDisplay
// Create the app button icon, window count and notification badges, and text label for titleDisplay
this.iconBox = new Cinnamon.Slicer({name: 'appMenuIcon'});
this.actor.add_child(this.iconBox);
this.setActorAttributes(null, params.metaWindow);

this.badge = new St.BoxLayout({
style_class: 'grouped-window-list-badge',
this.windowsBadge = new St.BoxLayout({
style_class: 'grouped-window-list-windows-badge',
important: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE,
show_on_set_parent: false,
});
this.windowsBadgeLabel = new St.Label({
style_class: 'grouped-window-list-windows-badge-label',
important: true,
text: ''
});
this.windowsBadgeLabel.clutter_text.ellipsize = false;
this.windowsBadge.add(this.windowsBadgeLabel, {
x_align: St.Align.START,
y_align: St.Align.START,
});
this.actor.add_child(this.windowsBadge);
this.windowsBadge.set_text_direction(St.TextDirection.LTR);

this.notificationsBadge = new St.BoxLayout({
style_class: 'grouped-window-list-notifications-badge',
important: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE,
show_on_set_parent: false,
});
this.numberLabel = new St.Label({
style_class: 'grouped-window-list-number-label',
this.notificationsBadgeLabel = new St.Label({
style_class: 'grouped-window-list-notifications-badge-label',
important: true,
text: '',
anchor_x: -3 * global.ui_scale,
text: ''
});
this.numberLabel.clutter_text.ellipsize = false;
this.badge.add(this.numberLabel, {
this.notificationsBadgeLabel.clutter_text.ellipsize = false;
this.notificationsBadge.add(this.notificationsBadgeLabel, {
x_align: St.Align.START,
y_align: St.Align.START,
});
this.actor.add_child(this.badge);
this.badge.set_text_direction(St.TextDirection.LTR);
this.actor.add_child(this.notificationsBadge);
this.notificationsBadge.set_text_direction(St.TextDirection.LTR);

this.label = new St.Label({
style_class: 'grouped-window-list-button-label',
Expand Down Expand Up @@ -172,6 +192,7 @@ class AppGroup {
this.calcWindowNumber();
this.on_orientation_changed(true);
this.handleFavorite();
this.state.trigger('copyNotifications', this);
}

initThumbnailMenu() {
Expand Down Expand Up @@ -372,6 +393,8 @@ class AppGroup {
const allocWidth = box.x2 - box.x1;
const allocHeight = box.y2 - box.y1;
const childBox = new Clutter.ActorBox();
const windowBadgeBox = new Clutter.ActorBox();
const notifBadgeBox = new Clutter.ActorBox();
const direction = this.actor.get_text_direction();

// Set the icon to be left-justified (or right-justified) and centered vertically
Expand All @@ -394,15 +417,23 @@ class AppGroup {

this.iconBox.allocate(childBox, flags);

// Set badge position
const windowCountFactor = this.groupState.windowCount > 9 ? 1.5 : 2;
const badgeOffset = 2 * global.ui_scale;
childBox.x1 = childBox.x1 - badgeOffset;
childBox.x2 = childBox.x1 + (this.numberLabel.width * windowCountFactor);
childBox.y1 = Math.max(childBox.y1 - badgeOffset, 0);
childBox.y2 = childBox.y1 + this.badge.get_preferred_height(childBox.get_width())[1];

this.badge.allocate(childBox, flags);
// Set windows badge position
const windowBadgeXCenter = this.iconBox.x;
const windowBadgeYCenter = this.iconBox.y;
windowBadgeBox.x1 = Math.max(windowBadgeXCenter - Math.floor(this.windowsBadgeLabel.width / 2), 0);
windowBadgeBox.x2 = windowBadgeBox.x1 + this.windowsBadgeLabel.width;
windowBadgeBox.y1 = Math.max(windowBadgeYCenter - Math.floor(this.windowsBadgeLabel.height / 2), 0);
windowBadgeBox.y2 = windowBadgeBox.y1 + this.windowsBadgeLabel.height;
this.windowsBadge.allocate(windowBadgeBox, flags);

// Set notifications badge position
const notifBadgeXCenter = this.iconBox.x + this.iconBox.width;
const notifBadgeYCenter = this.iconBox.y;
notifBadgeBox.x2 = Math.min(notifBadgeXCenter + Math.floor(this.notificationsBadgeLabel.width / 2), box.x2);
notifBadgeBox.x1 = notifBadgeBox.x2 - this.notificationsBadgeLabel.width;
notifBadgeBox.y1 = Math.max(notifBadgeYCenter - Math.floor(this.notificationsBadgeLabel.height / 2), 0);
notifBadgeBox.y2 = notifBadgeBox.y1 + this.notificationsBadgeLabel.height;
this.notificationsBadge.allocate(notifBadgeBox, flags);

// Set label position
if (this.drawLabel) {
Expand Down Expand Up @@ -676,8 +707,8 @@ class AppGroup {
}

showOrderLabel(number) {
this.numberLabel.text = (number + 1).toString();
this.badge.show();
this.windowsBadgeLabel.text = (number + 1).toString();
this.windowsBadge.show();
}

launchNewInstance(offload=false) {
Expand Down Expand Up @@ -917,6 +948,7 @@ class AppGroup {
this.setIcon(metaWindow)

this.calcWindowNumber();
this.updateNotificationsBadge();
this.onFocusChange();
}
set({
Expand Down Expand Up @@ -1074,20 +1106,26 @@ class AppGroup {
calcWindowNumber() {
if (this.groupState.willUnmount) return;

const windowCount = this.groupState.metaWindows ? this.groupState.metaWindows.length : 0;
this.numberLabel.text = windowCount.toString();

this.groupState.set({windowCount});

if (this.state.settings.numDisplay) {
if (windowCount <= 1) {
this.badge.hide();
} else {
this.badge.show();
this.groupState.set({windowCount: this.groupState.metaWindows ? this.groupState.metaWindows.length : 0});

if (this.groupState.windowCount > 1 && this.state.settings.enableWindowCountBadges) {
// paddingSpace is a hack to make make the single digit badge more rounded
const paddingSpace = this.groupState.windowCount < 10 ? " " : "";
this.windowsBadgeLabel.text = paddingSpace + this.groupState.windowCount.toString() + paddingSpace;
this.windowsBadge.show();
} else {
this.windowsBadge.hide();
}
}

}
updateNotificationsBadge() {
if (this.notifications.length > 0 && this.state.settings.enableNotificationBadges) {
// paddingSpace is a hack to make the single digit badge more rounded
const paddingSpace = this.notifications.length <10 ? " " : "";
this.notificationsBadgeLabel.text = paddingSpace + this.notifications.length.toString() + paddingSpace;
this.notificationsBadge.show();
} else {
this.badge.hide();
this.notificationsBadge.hide();
}
}

Expand Down
97 changes: 95 additions & 2 deletions files/usr/share/cinnamon/applets/[email protected]/applet.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Applet = imports.ui.applet;
const Cinnamon = imports.gi.Cinnamon;
const Main = imports.ui.main;
const DND = imports.ui.dnd;
const MessageTray = imports.ui.messageTray;
const {AppletSettings} = imports.ui.settings;
const {SignalManager} = imports.misc.signalManager;
const {throttle, unref, trySpawnCommandLine} = imports.misc.util;
Expand Down Expand Up @@ -255,6 +256,7 @@ class GroupedWindowListApplet extends Applet.Applet {
openAbout: () => this.openAbout(),
configureApplet: () => this.configureApplet(),
removeApplet: (event) => this.confirmRemoveApplet(event),
copyNotifications: (appGroup) => this.copyNotifications(appGroup)
});

this.settings = new AppletSettings(this.state.settings, metadata.uuid, instance_id);
Expand Down Expand Up @@ -291,6 +293,7 @@ class GroupedWindowListApplet extends Applet.Applet {
this.signals.connect(global.display, 'window-created', (...args) => this.onWindowCreated(...args));
this.signals.connect(global.settings, 'changed::panel-edit-mode', (...args) => this.on_panel_edit_mode_changed(...args));
this.signals.connect(Main.themeManager, 'theme-set', (...args) => this.refreshCurrentWorkspace(...args));
this.signals.connect(Main.messageTray, 'notify-applet-update', this._onNotificationReceived.bind(this));
}

bindSettings() {
Expand All @@ -307,7 +310,8 @@ class GroupedWindowListApplet extends Applet.Applet {
{key: 'super-num-hotkeys', value: 'SuperNumHotkeys', cb: this.bindAppKeys},
{key: 'title-display', value: 'titleDisplay', cb: this.updateTitleDisplay},
{key: 'launcher-animation-effect', value: 'launcherAnimationEffect', cb: null},
{key: 'number-display', value: 'numDisplay', cb: this.updateWindowNumberState},
{key: 'enable-window-count-badges', value: 'enableWindowCountBadges', cb: this.onEnableWindowCountBadgeChange},
{key: 'enable-notification-badges', value: 'enableNotificationBadges', cb: this.onEnableNotificationsChange},
{key: 'enable-app-button-dragging', value: 'enableDragging', cb: this.draggableSettingChanged},
{key: 'thumbnail-scroll-behavior', value: 'thumbnailScrollBehavior', cb: null},
{key: 'show-thumbnails', value: 'showThumbs', cb: this.updateVerticalThumbnailState},
Expand Down Expand Up @@ -357,6 +361,7 @@ class GroupedWindowListApplet extends Applet.Applet {
}
this.bindAppKeys();
this.state.set({appletReady: true});
MessageTray.extensionsHandlingNotifications++;
}

_updateState(initialUpdate) {
Expand Down Expand Up @@ -424,6 +429,10 @@ class GroupedWindowListApplet extends Applet.Applet {
});
this.settings.finalize();
unref(this, RESERVE_KEYS);
MessageTray.extensionsHandlingNotifications--;
if (MessageTray.extensionsHandlingNotifications === 0) {
this._destroyAllNotifications();
}
}

on_panel_icon_size_changed(iconSize) {
Expand Down Expand Up @@ -584,7 +593,7 @@ class GroupedWindowListApplet extends Applet.Applet {
});
}

updateWindowNumberState() {
onEnableWindowCountBadgeChange() {
this.workspaces.forEach(
workspace => workspace.calcAllWindowNumbers()
);
Expand Down Expand Up @@ -1022,6 +1031,90 @@ class GroupedWindowListApplet extends Applet.Applet {
this.state.set({thumbnailCloseButtonOffset: global.ui_scale > 1 ? -10 : 0});
this.refreshAllWorkspaces();
}

copyNotifications(newAppGroup) {
// Copy notifications from any existing appGroup with the same appId.
this.workspaces.some(workspace => {
if (!workspace) return false;
return workspace.appGroups.some(appGroup => {
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return false;
if (appGroup.groupState.appId === newAppGroup.groupState.appId) {
newAppGroup.notifications = appGroup.notifications.slice(); // Shallow copy.
return true;
}
return false;
});
});
}

_onNotificationReceived(mtray, notification) {
let appId = notification.source.app?.get_id();

if (!appId) {
return;
}

// Add notification to all appgroups with appId.
let notificationAdded = false;

this.workspaces.forEach(workspace => {
if (!workspace) return;
workspace.appGroups.forEach(appGroup => {
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
if (appId === appGroup.groupState.appId) {
appGroup.notifications.push(notification);
notificationAdded = true;
appGroup.updateNotificationsBadge();
}
});
});

if (notificationAdded) {
notification.appId = appId;
notification.connect('destroy', () => this._onNotificationDestroyed(notification));
}
}

_onNotificationDestroyed(notification) {
if (!this.workspaces) return;

this.workspaces.forEach(workspace => {
if (!workspace) return;
workspace.appGroups.forEach(appGroup => {
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
if (notification.appId === appGroup.groupState.appId) {
const index = appGroup.notifications.indexOf(notification);
if (index > -1) {
appGroup.notifications.splice(index, 1)
appGroup.updateNotificationsBadge();
}
}
});
});
}

_destroyAllNotifications() {
this.workspaces.forEach(workspace => {
if (!workspace) return;
workspace.appGroups.forEach(appGroup => {
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
// Iterate backwards due to in place element deletion.
for (let i = appGroup.notifications.length - 1; i >= 0; i--) {
appGroup.notifications[i].destroy();
}
});
});
}

onEnableNotificationsChange() {
this.workspaces.forEach(workspace => {
if (!workspace) return;
workspace.appGroups.forEach(appGroup => {
if (!appGroup || !appGroup.groupState || appGroup.groupState.willUnmount) return;
appGroup.updateNotificationsBadge();
});
});
}
}

function main(metadata, orientation, panel_height, instance_id) {
Expand Down
Loading
Loading