From 9bc6847ee186915afdb24361038db79e35bd5923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Fri, 14 Aug 2026 09:02:06 -0700 Subject: [PATCH 01/11] Theoretically do animated icon --- data/gresource.xml | 49 +--------- data/icons/demo.svg | 218 ++++++++++++++++++++++++++++++++++++++++++++ data/indicator.css | 50 ---------- meson.build | 2 +- src/Indicator.vala | 29 +++--- 5 files changed, 231 insertions(+), 117 deletions(-) create mode 100644 data/icons/demo.svg delete mode 100644 data/indicator.css diff --git a/data/gresource.xml b/data/gresource.xml index 9cd66643..9ceecc6d 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -2,56 +2,9 @@ AppEntry.css - indicator.css NotificationEntry.css - icons/disabled-10.svg - icons/disabled-20.svg - icons/disabled-30.svg - icons/disabled-40.svg - icons/disabled-50.svg - icons/disabled-60.svg - icons/disabled-70.svg - icons/disabled-80.svg - icons/disabled-90.svg - icons/disabled.svg - - icons/new-10.svg - icons/new-20.svg - icons/new-30.svg - icons/new-40.svg - icons/new-50.svg - icons/new-60.svg - icons/new-70.svg - icons/new-80.svg - icons/new-90.svg - icons/new.svg - - icons/notification.svg - - icons/disabled-10.svg - icons/disabled-20.svg - icons/disabled-30.svg - icons/disabled-40.svg - icons/disabled-50.svg - icons/disabled-60.svg - icons/disabled-70.svg - icons/disabled-80.svg - icons/disabled-90.svg - icons/disabled.svg - - icons/new-10.svg - icons/new-20.svg - icons/new-30.svg - icons/new-40.svg - icons/new-50.svg - icons/new-60.svg - icons/new-70.svg - icons/new-80.svg - icons/new-90.svg - icons/new.svg - - icons/notification.svg + icons/demo.svg diff --git a/data/icons/demo.svg b/data/icons/demo.svg new file mode 100644 index 00000000..4cef0373 --- /dev/null +++ b/data/icons/demo.svg @@ -0,0 +1,218 @@ + + + + + + + bell ringinging ring alarm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/indicator.css b/data/indicator.css deleted file mode 100644 index f527b235..00000000 --- a/data/indicator.css +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-2.0-or-later - * SPDX-FileCopyrightText: 2017-2023 elementary, Inc. (https://elementary.io) - */ - -.notification-icon { - animation: none; - min-width: 24px; - opacity: 1; - transition: none; - -gtk-icon-source: -gtk-icontheme("notification-symbolic"); -} - -.notification-icon.new { - animation: notification-new 500ms cubic-bezier(0.4, 0.0, 0.2, 1); - -gtk-icon-source: -gtk-icontheme("notification-new-symbolic"); -} - -.notification-icon.disabled { - animation: notification-disabled 160ms cubic-bezier(0.4, 0.0, 0.2, 1); - -gtk-icon-source: -gtk-icontheme("notification-disabled-symbolic"); -} - -@keyframes notification-disabled { - 0% { -gtk-icon-source: -gtk-icontheme("notification-symbolic"); } - 10% { -gtk-icon-source: -gtk-icontheme("notification-disabled-10-symbolic"); } - 20% { -gtk-icon-source: -gtk-icontheme("notification-disabled-20-symbolic"); } - 30% { -gtk-icon-source: -gtk-icontheme("notification-disabled-30-symbolic"); } - 40% { -gtk-icon-source: -gtk-icontheme("notification-disabled-40-symbolic"); } - 50% { -gtk-icon-source: -gtk-icontheme("notification-disabled-50-symbolic"); } - 60% { -gtk-icon-source: -gtk-icontheme("notification-disabled-60-symbolic"); } - 70% { -gtk-icon-source: -gtk-icontheme("notification-disabled-70-symbolic"); } - 80% { -gtk-icon-source: -gtk-icontheme("notification-disabled-80-symbolic"); } - 90% { -gtk-icon-source: -gtk-icontheme("notification-disabled-90-symbolic"); } - 100% { -gtk-icon-source: -gtk-icontheme("notification-disabled-symbolic"); } -} - -@keyframes notification-new { - 0% { -gtk-icon-source: -gtk-icontheme("notification-symbolic"); } - 10% { -gtk-icon-source: -gtk-icontheme("notification-new-10-symbolic"); } - 20% { -gtk-icon-source: -gtk-icontheme("notification-new-20-symbolic"); } - 30% { -gtk-icon-source: -gtk-icontheme("notification-new-30-symbolic"); } - 40% { -gtk-icon-source: -gtk-icontheme("notification-new-40-symbolic"); } - 50% { -gtk-icon-source: -gtk-icontheme("notification-new-50-symbolic"); } - 60% { -gtk-icon-source: -gtk-icontheme("notification-new-60-symbolic"); } - 70% { -gtk-icon-source: -gtk-icontheme("notification-new-70-symbolic"); } - 80% { -gtk-icon-source: -gtk-icontheme("notification-new-80-symbolic"); } - 90% { -gtk-icon-source: -gtk-icontheme("notification-new-90-symbolic"); } - 100% { -gtk-icon-source: -gtk-icontheme("notification-new-symbolic"); } -} diff --git a/meson.build b/meson.build index 28c68e5d..dc643fb3 100644 --- a/meson.build +++ b/meson.build @@ -51,7 +51,7 @@ shared_module( dependency('glib-2.0'), dependency('gobject-2.0'), dependency('granite-7', version: '>=7.7.0'), - dependency('gtk4'), + dependency('gtk4', version: '>=4.22'), dependency('libadwaita-1'), wingpanel_dep ], diff --git a/src/Indicator.vala b/src/Indicator.vala index 912ced72..0f362616 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -8,12 +8,16 @@ public class Notifications.Indicator : Wingpanel.Indicator { private const string CHILD_PATH = "/io/elementary/notifications/applications/%s/"; private const string REMEMBER_KEY = "remember"; + private const int ICON_STATE_NORMAL = 0; + private const int ICON_STATE_NEW = 1; + private const int ICON_STATE_DISABLED = 2; + private Gee.HashMap app_settings_cache; private GLib.Settings notify_settings; private Gtk.Box? main_box = null; private Wingpanel.PopoverMenuItem clear_all_btn; - private Gtk.Spinner? dynamic_icon = null; + private Gtk.Svg? dynamic_icon = null; private NotificationsList nlist; private List previous_session = null; @@ -38,20 +42,11 @@ public class Notifications.Indicator : Wingpanel.Indicator { public override Gtk.Widget get_display_widget () { if (dynamic_icon == null) { - var provider = new Gtk.CssProvider (); - provider.load_from_resource ("io/elementary/wingpanel/notifications/indicator.css"); - - Gtk.StyleContext.add_provider_for_display ( - Gdk.Display.get_default (), - provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - ); + dynamic_icon = new Gtk.Svg.from_resource ("resource:///org/elementary/wingpanel/icons/scalable/status/demo-symbolic.svg"); - dynamic_icon = new Gtk.Spinner () { - spinning = true, + var image = new Gtk.Image.from_paintable (svg) { tooltip_markup = _("Updating notifications…") }; - dynamic_icon.add_css_class ("notification-icon"); nlist = new NotificationsList (); @@ -92,7 +87,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { }); } - return dynamic_icon; + return image; } private async void load_session_notifications () { @@ -204,13 +199,11 @@ public class Notifications.Indicator : Wingpanel.Indicator { private void set_display_icon_name () { if (notify_settings.get_boolean ("do-not-disturb")) { - dynamic_icon.add_css_class ("disabled"); + dynamic_icon.state = ICON_STATE_DISABLED; } else if (nlist != null && nlist.app_entries.size > 0) { - dynamic_icon.remove_css_class ("disabled"); - dynamic_icon.add_css_class ("new"); + dynamic_icon.state = ICON_STATE_NEW; } else { - dynamic_icon.remove_css_class ("disabled"); - dynamic_icon.remove_css_class ("new"); + dynamic_icon.state = ICON_STATE_NORMAL; } update_tooltip (); } From 97e3c85a62d4659492a5056e5d6ba6d940a9c973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Fri, 14 Aug 2026 15:24:29 -0700 Subject: [PATCH 02/11] Basic state --- data/gresource.xml | 4 +- data/icons/demo.svg | 218 ------------------------------------ data/icons/disabled-10.svg | 28 ----- data/icons/disabled-20.svg | 28 ----- data/icons/disabled-30.svg | 25 ----- data/icons/disabled-40.svg | 25 ----- data/icons/disabled-50.svg | 25 ----- data/icons/disabled-60.svg | 25 ----- data/icons/disabled-70.svg | 25 ----- data/icons/disabled-80.svg | 25 ----- data/icons/disabled-90.svg | 35 ------ data/icons/disabled.svg | 25 ----- data/icons/new-10.svg | 30 ----- data/icons/new-20.svg | 30 ----- data/icons/new-30.svg | 30 ----- data/icons/new-40.svg | 30 ----- data/icons/new-50.svg | 30 ----- data/icons/new-60.svg | 30 ----- data/icons/new-70.svg | 30 ----- data/icons/new-80.svg | 36 ------ data/icons/new-90.svg | 36 ------ data/icons/new.svg | 35 ------ data/icons/notification.svg | 103 ++++++++++++++--- src/Indicator.vala | 10 +- 24 files changed, 95 insertions(+), 823 deletions(-) delete mode 100644 data/icons/demo.svg delete mode 100644 data/icons/disabled-10.svg delete mode 100644 data/icons/disabled-20.svg delete mode 100644 data/icons/disabled-30.svg delete mode 100644 data/icons/disabled-40.svg delete mode 100644 data/icons/disabled-50.svg delete mode 100644 data/icons/disabled-60.svg delete mode 100644 data/icons/disabled-70.svg delete mode 100644 data/icons/disabled-80.svg delete mode 100644 data/icons/disabled-90.svg delete mode 100644 data/icons/disabled.svg delete mode 100644 data/icons/new-10.svg delete mode 100644 data/icons/new-20.svg delete mode 100644 data/icons/new-30.svg delete mode 100644 data/icons/new-40.svg delete mode 100644 data/icons/new-50.svg delete mode 100644 data/icons/new-60.svg delete mode 100644 data/icons/new-70.svg delete mode 100644 data/icons/new-80.svg delete mode 100644 data/icons/new-90.svg delete mode 100644 data/icons/new.svg diff --git a/data/gresource.xml b/data/gresource.xml index 9ceecc6d..6bd9acfc 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -3,8 +3,6 @@ AppEntry.css NotificationEntry.css - - - icons/demo.svg + icons/notification.svg diff --git a/data/icons/demo.svg b/data/icons/demo.svg deleted file mode 100644 index 4cef0373..00000000 --- a/data/icons/demo.svg +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - - bell ringinging ring alarm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/icons/disabled-10.svg b/data/icons/disabled-10.svg deleted file mode 100644 index dd44f4ad..00000000 --- a/data/icons/disabled-10.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-20.svg b/data/icons/disabled-20.svg deleted file mode 100644 index 33888b73..00000000 --- a/data/icons/disabled-20.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-30.svg b/data/icons/disabled-30.svg deleted file mode 100644 index 31e8dc17..00000000 --- a/data/icons/disabled-30.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-40.svg b/data/icons/disabled-40.svg deleted file mode 100644 index c9b41254..00000000 --- a/data/icons/disabled-40.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-50.svg b/data/icons/disabled-50.svg deleted file mode 100644 index 63aad306..00000000 --- a/data/icons/disabled-50.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-60.svg b/data/icons/disabled-60.svg deleted file mode 100644 index 6c47d540..00000000 --- a/data/icons/disabled-60.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-70.svg b/data/icons/disabled-70.svg deleted file mode 100644 index ee21180e..00000000 --- a/data/icons/disabled-70.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-80.svg b/data/icons/disabled-80.svg deleted file mode 100644 index 065f4f7f..00000000 --- a/data/icons/disabled-80.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-90.svg b/data/icons/disabled-90.svg deleted file mode 100644 index f3fc6f45..00000000 --- a/data/icons/disabled-90.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - diff --git a/data/icons/disabled.svg b/data/icons/disabled.svg deleted file mode 100644 index bbf984e1..00000000 --- a/data/icons/disabled.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/new-10.svg b/data/icons/new-10.svg deleted file mode 100644 index 05aec32a..00000000 --- a/data/icons/new-10.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/data/icons/new-20.svg b/data/icons/new-20.svg deleted file mode 100644 index d7b195de..00000000 --- a/data/icons/new-20.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/data/icons/new-30.svg b/data/icons/new-30.svg deleted file mode 100644 index 9e3b34f1..00000000 --- a/data/icons/new-30.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/data/icons/new-40.svg b/data/icons/new-40.svg deleted file mode 100644 index 6499179a..00000000 --- a/data/icons/new-40.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/data/icons/new-50.svg b/data/icons/new-50.svg deleted file mode 100644 index 5325b9c6..00000000 --- a/data/icons/new-50.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/data/icons/new-60.svg b/data/icons/new-60.svg deleted file mode 100644 index f92abb01..00000000 --- a/data/icons/new-60.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/data/icons/new-70.svg b/data/icons/new-70.svg deleted file mode 100644 index b29efbca..00000000 --- a/data/icons/new-70.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/data/icons/new-80.svg b/data/icons/new-80.svg deleted file mode 100644 index eab3c453..00000000 --- a/data/icons/new-80.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/data/icons/new-90.svg b/data/icons/new-90.svg deleted file mode 100644 index 94c80ad4..00000000 --- a/data/icons/new-90.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/data/icons/new.svg b/data/icons/new.svg deleted file mode 100644 index d86a94e2..00000000 --- a/data/icons/new.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/data/icons/notification.svg b/data/icons/notification.svg index 308ab118..ecc7573f 100644 --- a/data/icons/notification.svg +++ b/data/icons/notification.svg @@ -1,18 +1,91 @@ - - + xmlns='http://www.w3.org/2000/svg' + xmlns:svg='http://www.w3.org/2000/svg' + xmlns:gpa='https://www.gtk.org/grappa' + gpa:version='1' + gpa:state-names='state0 state1 state2' + gpa:state='0' + width='24' + height='24'> + + + class='' + style='isolation: auto' + color='rgb(190,190,190)' + fill='url(#gpa:foreground) rgb(0,0,0)' + fill-opacity='1' + fill-rule='nonzero' + overflow='visible' + d='M 12 5 A 1 1 0 0 0 11 6 L 11 6.01 C 9.236 6.466, 8.002 8.178, 8 10 C 8 10, 8 15, 6.34 15.621 C 6.324 15.632, 6.317 15.652, 6.3 15.664 A 0.726 0.726 0 0 0 6.139 15.84 C 6.121 15.867, 6.1 15.888, 6.086 15.916 A 0.738 0.738 0 0 0 6 16.25 C 6 16.666, 6.335 17, 6.75 17 L 17.25 17 C 17.666 17, 18 16.666, 18 16.25 A 0.738 0.738 0 0 0 17.914 15.916 C 17.9 15.888, 17.879 15.866, 17.862 15.84 A 0.725 0.725 0 0 0 17.7 15.664 C 17.684 15.652, 17.677 15.632, 17.66 15.621 C 16 15.001, 16 10, 16 10 C 15.998 8.177, 14.765 6.465, 13 6.01 L 13 6 A 1 1 0 0 0 12 5 Z M 10.067 18 A 2 2 0 0 0 12 19.5 A 2 2 0 0 0 13.936 18 Z' + gpa:states='state0' + gpa:animation-repeat='0' + gpa:animation-segment='0'> + + + + + + + diff --git a/src/Indicator.vala b/src/Indicator.vala index 0f362616..4ec6c148 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -18,6 +18,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { private Gtk.Box? main_box = null; private Wingpanel.PopoverMenuItem clear_all_btn; private Gtk.Svg? dynamic_icon = null; + private Gtk.Image image; private NotificationsList nlist; private List previous_session = null; @@ -42,9 +43,10 @@ public class Notifications.Indicator : Wingpanel.Indicator { public override Gtk.Widget get_display_widget () { if (dynamic_icon == null) { - dynamic_icon = new Gtk.Svg.from_resource ("resource:///org/elementary/wingpanel/icons/scalable/status/demo-symbolic.svg"); + dynamic_icon = new Gtk.Svg.from_resource ("/io/elementary/wingpanel/notifications/icons/notification.svg"); - var image = new Gtk.Image.from_paintable (svg) { + image = new Gtk.Image.from_paintable (dynamic_icon) { + pixel_size = 24, tooltip_markup = _("Updating notifications…") }; @@ -74,7 +76,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { gesture_click.reset (); }); - dynamic_icon.add_controller (gesture_click); + image.add_controller (gesture_click); previous_session = Session.get_instance ().get_session_notifications (); Timeout.add (2000, () => { // Do not block animated drawing of wingpanel @@ -249,7 +251,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { break; } - dynamic_icon.tooltip_markup = "%s\n%s".printf (description, accel_label); + image.tooltip_markup = "%s\n%s".printf (description, accel_label); } } From 75de49ab7db69538c049ba25c85b82eda2c49822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Sat, 15 Aug 2026 11:17:20 -0700 Subject: [PATCH 03/11] more animations --- data/icons/notification.svg | 166 +++++++++++++++++++++++++----------- src/Indicator.vala | 6 ++ 2 files changed, 120 insertions(+), 52 deletions(-) diff --git a/data/icons/notification.svg b/data/icons/notification.svg index ecc7573f..9df4e133 100644 --- a/data/icons/notification.svg +++ b/data/icons/notification.svg @@ -5,75 +5,88 @@ gpa:version='1' gpa:state-names='state0 state1 state2' gpa:state='0' + id='svg4' width='24' height='24'> - - - - + + + + + d='M 14 17.5 A 2 2 0 0 1 12 19.5 A 2 2 0 0 1 10 17.5 A 2 2 0 0 1 12 15.5 A 2 2 0 0 1 14 17.5 Z' + gpa:animation-easing='ease-in-out' + gpa:attach-to='bell' + gpa:attach-pos='0.552326'> + + + + diff --git a/src/Indicator.vala b/src/Indicator.vala index 4ec6c148..5d095795 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -50,6 +50,12 @@ public class Notifications.Indicator : Wingpanel.Indicator { tooltip_markup = _("Updating notifications…") }; + image.realize.connect (() => { + dynamic_icon.set_frame_clock (image.get_frame_clock ()); + dynamic_icon.play (); + }); + + nlist = new NotificationsList (); monitor.notification_received.connect (on_notification_received); From 1e827caf9588ab2e457717c5b63b1c3d4c90bddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Sat, 15 Aug 2026 11:26:42 -0700 Subject: [PATCH 04/11] Prototype Granite.Symbol --- meson.build | 1 + src/Indicator.vala | 19 +++++-------------- src/Widgets/Symbol.vala | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 src/Widgets/Symbol.vala diff --git a/meson.build b/meson.build index dc643fb3..a641addb 100644 --- a/meson.build +++ b/meson.build @@ -42,6 +42,7 @@ shared_module( 'src/Widgets/NotificationsList.vala', 'src/Widgets/NotificationEntry.vala', 'src/Widgets/AppEntry.vala', + 'src' / 'Widgets' / 'Symbol.vala', 'src/Services/NotificationsMonitor.vala', 'src/Services/Notification.vala', 'src/Services/Session.vala', diff --git a/src/Indicator.vala b/src/Indicator.vala index 5d095795..d0050bc8 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -17,8 +17,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { private Gtk.Box? main_box = null; private Wingpanel.PopoverMenuItem clear_all_btn; - private Gtk.Svg? dynamic_icon = null; - private Gtk.Image image; + private Granite.Symbol? dynamic_icon = null; private NotificationsList nlist; private List previous_session = null; @@ -43,19 +42,11 @@ public class Notifications.Indicator : Wingpanel.Indicator { public override Gtk.Widget get_display_widget () { if (dynamic_icon == null) { - dynamic_icon = new Gtk.Svg.from_resource ("/io/elementary/wingpanel/notifications/icons/notification.svg"); - - image = new Gtk.Image.from_paintable (dynamic_icon) { + dynamic_icon = new Granite.Symbol ("/io/elementary/wingpanel/notifications/icons/notification.svg") { pixel_size = 24, tooltip_markup = _("Updating notifications…") }; - image.realize.connect (() => { - dynamic_icon.set_frame_clock (image.get_frame_clock ()); - dynamic_icon.play (); - }); - - nlist = new NotificationsList (); monitor.notification_received.connect (on_notification_received); @@ -82,7 +73,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { gesture_click.reset (); }); - image.add_controller (gesture_click); + dynamic_icon.add_controller (gesture_click); previous_session = Session.get_instance ().get_session_notifications (); Timeout.add (2000, () => { // Do not block animated drawing of wingpanel @@ -95,7 +86,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { }); } - return image; + return dynamic_icon; } private async void load_session_notifications () { @@ -257,7 +248,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { break; } - image.tooltip_markup = "%s\n%s".printf (description, accel_label); + dynamic_icon.tooltip_markup = "%s\n%s".printf (description, accel_label); } } diff --git a/src/Widgets/Symbol.vala b/src/Widgets/Symbol.vala new file mode 100644 index 00000000..d830745a --- /dev/null +++ b/src/Widgets/Symbol.vala @@ -0,0 +1,38 @@ +/* +* SPDX-License-Identifier: LGPL-2.1-or-later +* SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) +*/ + +public class Granite.Symbol : Granite.Bin{ + public string resource_path { get; construct; } + + public int pixel_size { + get { return image.pixel_size; } + set { image.pixel_size = value; } + } + + public uint state { + get { return svg.state; } + set { svg.state = value; } + } + + private Gtk.Image image; + private Gtk.Svg svg; + + public Symbol (string resource_path) { + Object (resource_path: resource_path); + } + + construct { + svg = new Gtk.Svg.from_resource (resource_path); + + image = new Gtk.Image.from_paintable (svg); + + child = image; + + child.realize.connect (() => { + svg.set_frame_clock (get_frame_clock ()); + svg.play (); + }); + } +} From 36b555091388abcf03aeedef75d50482d67015b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 26 Aug 2026 09:55:44 -0700 Subject: [PATCH 05/11] Fix up animations --- data/icons/notification.svg | 281 ++++++++++++++++++++---------------- src/Indicator.vala | 10 +- src/Widgets/Symbol.vala | 46 +++++- 3 files changed, 206 insertions(+), 131 deletions(-) diff --git a/data/icons/notification.svg b/data/icons/notification.svg index 9df4e133..a2a08021 100644 --- a/data/icons/notification.svg +++ b/data/icons/notification.svg @@ -3,135 +3,135 @@ xmlns:svg='http://www.w3.org/2000/svg' xmlns:gpa='https://www.gtk.org/grappa' gpa:version='1' - gpa:state-names='state0 state1 state2' + gpa:state-names='normal active disabled' gpa:state='0' id='svg4' width='24' height='24'> - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Indicator.vala b/src/Indicator.vala index d0050bc8..90442a75 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -17,7 +17,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { private Gtk.Box? main_box = null; private Wingpanel.PopoverMenuItem clear_all_btn; - private Granite.Symbol? dynamic_icon = null; + private NotificationsIndicator.Symbol? dynamic_icon = null; private NotificationsList nlist; private List previous_session = null; @@ -42,7 +42,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { public override Gtk.Widget get_display_widget () { if (dynamic_icon == null) { - dynamic_icon = new Granite.Symbol ("/io/elementary/wingpanel/notifications/icons/notification.svg") { + dynamic_icon = new NotificationsIndicator.Symbol ("/io/elementary/wingpanel/notifications/icons/notification.svg") { pixel_size = 24, tooltip_markup = _("Updating notifications…") }; @@ -198,11 +198,11 @@ public class Notifications.Indicator : Wingpanel.Indicator { private void set_display_icon_name () { if (notify_settings.get_boolean ("do-not-disturb")) { - dynamic_icon.state = ICON_STATE_DISABLED; + dynamic_icon.state = NotificationsIndicator.SymbolState.DISABLED; } else if (nlist != null && nlist.app_entries.size > 0) { - dynamic_icon.state = ICON_STATE_NEW; + dynamic_icon.state = NotificationsIndicator.SymbolState.ACTIVE; } else { - dynamic_icon.state = ICON_STATE_NORMAL; + dynamic_icon.state = NotificationsIndicator.SymbolState.NORMAL; } update_tooltip (); } diff --git a/src/Widgets/Symbol.vala b/src/Widgets/Symbol.vala index d830745a..954111d4 100644 --- a/src/Widgets/Symbol.vala +++ b/src/Widgets/Symbol.vala @@ -3,7 +3,16 @@ * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) */ -public class Granite.Symbol : Granite.Bin{ +namespace NotificationsIndicator.SymbolState { + // The default state + public const string NORMAL = "normal"; + // Disabled state represented by a slash + public const string DISABLED = "disabled"; + // e.g. paired, connected, needs attention + public const string ACTIVE = "active"; +} + +public class NotificationsIndicator.Symbol : Granite.Bin { public string resource_path { get; construct; } public int pixel_size { @@ -11,9 +20,40 @@ public class Granite.Symbol : Granite.Bin{ set { image.pixel_size = value; } } - public uint state { + public uint state_index { get { return svg.state; } - set { svg.state = value; } + set { + uint length = -1; + svg.get_state_names (out length); + + if (value > length - 1) { + warning ("Granite.Symbol set to undefined state. Ignoring."); + return; + } + + svg.state = value; + notify_property ("state"); + } + } + + public string state { + get { + uint length = -1; + return svg.get_state_names (out length)[state_index]; + } + set { + uint length = -1; + var names = svg.get_state_names (out length); + + for (int i = 0; i < length; i++) { + if (names[i] == value) { + state_index = i; + return; + } + } + + warning ("Granite.Symbol set to undefined state. Ignoring."); + } } private Gtk.Image image; From c43416989fe04e7d3d0ddb8c0a5ba34e387a3314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 26 Aug 2026 10:08:05 -0700 Subject: [PATCH 06/11] Add bell clip --- data/icons/notification.svg | 62 +++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/data/icons/notification.svg b/data/icons/notification.svg index a2a08021..70d0aa7e 100644 --- a/data/icons/notification.svg +++ b/data/icons/notification.svg @@ -27,23 +27,9 @@ fill='freeze' to='1' keyTimes='0; 1'/> - + - + + + + + + + + + Date: Wed, 26 Aug 2026 10:37:29 -0700 Subject: [PATCH 07/11] fix mask on alert --- data/icons/notification.svg | 403 +++++++++++++++++++----------------- 1 file changed, 217 insertions(+), 186 deletions(-) diff --git a/data/icons/notification.svg b/data/icons/notification.svg index 70d0aa7e..cebfaaec 100644 --- a/data/icons/notification.svg +++ b/data/icons/notification.svg @@ -1,212 +1,243 @@ + + gpa:version="1" + gpa:state-names="normal active disabled" + gpa:state="0" + id="svg4" + width="24" + height="24" + version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:gpa="https://www.gtk.org/grappa"> + + id="mask-group" + mask="url(#disabled-mask)"> + id="alert-mask-group" + mask="url(#alert-mask)"> + attributeName="opacity" + begin="gpa:states(normal active, disabled)" + dur="200ms" + fill="freeze" + to="0.5" + keyTimes="0; 1" /> + attributeName="opacity" + begin="gpa:states(disabled, normal active)" + dur="200ms" + fill="freeze" + to="1" + keyTimes="0; 1" /> + id="bell-group" + transform-origin="55% 45%"> + attributeName="transform" + begin="gpa:states(normal disabled, active)" + dur="500ms" + repeatCount="1" + fill="freeze" + values="0 0 0; -3 0 0; 10 0 0; 32 0 0; 20 0 0; 26 0 0; 22 0 0" + keySplines="0 0 1 1; 0 0 0.406056 0.998339; 0.333556 0.333333 0.666889 0.666667; 0.960121 0 0.915405 1; 0.084493 0.333333 1 0.666667; 0 0 1 1" + keyTimes="0; 0; 0.2; 0.200612; 0.533333; 0.866667; 1" + calcMode="spline" + type="rotate" /> + attributeName="transform" + begin="gpa:states(active, normal disabled)" + dur="100ms" + repeatCount="1" + fill="freeze" + from="22 0 0" + to="0 0 0" + keyTimes="0; 1" + type="rotate" /> - + id="bell" + display="inline" + color="rgb(190,190,190)" + fill="url(#gpa:foreground) rgb(0,0,0)" + fill-opacity="1" + fill-rule="nonzero" + stroke="none" + overflow="visible" + d="M 12 5 C 11.4477 5, 11 5.44772, 11 6 L 11 6.01 C 9.236 6.466, 8.002 8.178, 8 10 C 8 10, 8 15, 6.34 15.621 C 6.324 15.632, 6.317 15.652, 6.3 15.664 C 6.237 15.7134, 6.1826 15.7729, 6.139 15.84 C 6.121 15.867, 6.1 15.888, 6.086 15.916 C 6.03136 16.019, 6.00188 16.1334, 6 16.25 C 6 16.666, 6.335 17, 6.75 17 L 17.25 17 C 17.666 17, 18 16.666, 18 16.25 C 17.9981 16.1334, 17.9686 16.019, 17.914 15.916 C 17.9 15.888, 17.879 15.866, 17.862 15.84 C 17.8181 15.7728, 17.7634 15.7133, 17.7 15.664 C 17.684 15.652, 17.677 15.632, 17.66 15.621 C 16 15.001, 16 10, 16 10 C 15.998 8.177, 14.765 6.465, 13 6.01 L 13 6 C 13 5.44772, 12.5523 5, 12 5 Z" + class="foreground foreground-fill" + style="isolation: auto" + gpa:fill="foreground" + gpa:states="normal active disabled" + gpa:animation-repeat="0" + gpa:animation-segment="0" /> + id="bell-mask" + opacity="1"> - + id="rect1" + fill="rgb(255,255,255)" + x="0" + y="0" + width="100%" + height="100%" /> - + id="bell-clip" + fill="rgb(0,0,0)" + d="M 11.9043 4.00391 C 11.1395 4.02787, 10.4298 4.54272, 10.1445 5.24805 C 8.07491 6.06098, 6.84457 8.32281, 6.98474 10.499 C 6.87252 11.8923, 6.854 13.4185, 6.0625 14.623 C 4.91296 15.1045, 4.60014 16.856, 5.65919 17.6164 C 6.6065 18.3067, 7.86266 17.8822, 8.95 18 C 11.8333 17.9785, 14.721 18.0427, 17.6016 17.9688 C 18.9765 17.7536, 19.4893 15.8079, 18.4004 14.9492 C 18.0165 14.6939, 17.6532 14.3926, 17.5509 13.8967 C 16.9411 12.2694, 17.1431 10.5023, 16.8594 8.82182 C 16.5016 7.23099, 15.3268 5.8553, 13.8477 5.2207 C 13.5202 4.46552, 12.7321 3.95117, 11.9043 4.00391 Z" + style="isolation: auto" + gpa:states="normal active disabled" /> + id="clapper" + display="inline" + mask="url(#bell-mask)" + fill="url(#gpa:foreground) rgb(0,0,0)" + fill-opacity="1" + fill-rule="nonzero" + stroke="none" + d="M 14 17.5 A 2 2 0 0 1 12 19.5 A 2 2 0 0 1 10 17.5 A 2 2 0 0 1 12 15.5 A 2 2 0 0 1 14 17.5 Z" + class="foreground foreground-fill" + style="isolation: auto" + gpa:fill="foreground" /> + + + + attributeName="transform" + begin="gpa:states(normal disabled, active)" + dur="500ms" + repeatCount="1" + fill="freeze" + values="0 0; 0.5 0.5; 0.75 0.75; 1.1 1.1; 0.95 0.95; 1.05 1.05; 1 1" + keySplines="0 0 1 1; 0 0 0.406056 0.998339; 0.333556 0.333333 0.666889 0.666667; 0.960121 0 0.915405 1; 0.084493 0.333333 1 0.666667; 0 0 1 1" + keyTimes="0; 0; 0.2; 0.200612; 0.533333; 0.866667; 1" + calcMode="spline" + type="scale" /> - - + attributeName="transform" + begin="gpa:states(active, normal disabled)" + dur="100ms" + repeatCount="1" + fill="freeze" + from="1 1" + to="0 0" + keyTimes="0; 1" + type="scale" /> + + + + + + + - + id="slash" + display="inline" + visibility="hidden" + transform="none" + opacity="1" + fill="none" + stroke="url(#gpa:foreground) rgb(0,0,0)" + stroke-opacity="1" + stroke-width="1.25" + stroke-linecap="round" + stroke-linejoin="round" + stroke-miterlimit="4" + x1="6.045" + y1="5.42123" + x2="19.2458" + y2="18.6085" + gpa:stroke-minwidth="25%" + gpa:stroke-maxwidth="200%" + class="transparent-fill foreground-stroke" + gpa:stroke="foreground" + gpa:states="disabled" + gpa:transition-type="animate" + gpa:transition-easing="ease-in-out" + gpa:transition-duration="200ms" /> + id="disabled-mask" + opacity="1"> - + id="rect2" + fill="rgb(255,255,255)" + x="0" + y="0" + width="100%" + height="100%" /> - + id="slash-clip" + visibility="hidden" + stroke="rgb(0,0,0)" + stroke-opacity="1" + stroke-width="1.25" + stroke-linecap="round" + stroke-linejoin="round" + stroke-miterlimit="4" + x1="6.045" + y1="7.045" + x2="19.2458" + y2="20.23" + gpa:stroke-minwidth="25%" + gpa:stroke-maxwidth="200%" + gpa:states="disabled" + gpa:transition-type="animate" + gpa:transition-easing="ease-in-out" + gpa:transition-duration="200ms" /> From 0ce51c3d368bf083aba490f7861cefe2be7ebdfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 26 Aug 2026 11:17:01 -0700 Subject: [PATCH 08/11] Fix clapper clip --- data/icons/notification.svg | 451 +++++++++++++++++++----------------- 1 file changed, 237 insertions(+), 214 deletions(-) diff --git a/data/icons/notification.svg b/data/icons/notification.svg index cebfaaec..17ff96ee 100644 --- a/data/icons/notification.svg +++ b/data/icons/notification.svg @@ -1,243 +1,266 @@ - - + xmlns='http://www.w3.org/2000/svg' + xmlns:svg='http://www.w3.org/2000/svg' + xmlns:gpa='https://www.gtk.org/grappa' + gpa:version='1' + gpa:state-names='normal active disabled' + gpa:state='0' + id='svg4' + width='24' + height='24'> + id='mask-group' + mask='url(#disabled-mask)'> + id='alert-mask-group' + mask='url(#alert-mask)'> + attributeName='opacity' + begin='gpa:states(normal active, disabled)' + dur='200ms' + fill='freeze' + to='0.5' + keyTimes='0; 1'/> - + attributeName='opacity' + begin='gpa:states(disabled, normal active)' + dur='200ms' + fill='freeze' + to='1' + keyTimes='0; 1'/> + + attributeName='transform' + begin='gpa:states(normal disabled, active)' + dur='500ms' + repeatCount='1' + fill='freeze' + values='0 0 0; -3 0 0; 10 0 0; 32 0 0; 20 0 0; 26 0 0; 22 0 0' + keySplines='0 0 1 1; 0 0 0.406056 0.998339; 0.333556 0.333333 0.666889 0.666667; 0.960121 0 0.915405 1; 0.084493 0.333333 1 0.666667; 0 0 1 1' + keyTimes='0; 0; 0.2; 0.200612; 0.533333; 0.866667; 1' + calcMode='spline' + type='rotate'/> - - - - - - + attributeName='transform' + begin='gpa:states(active, normal disabled)' + dur='100ms' + repeatCount='1' + fill='freeze' + from='22 0 0' + to='0 0 0' + keyTimes='0; 1' + type='rotate'/> + + id='clapper' + display='inline' + mask='url(#bell-mask)' + fill='url(#gpa:foreground) rgb(0,0,0)' + fill-opacity='1' + fill-rule='nonzero' + stroke='none' + d='M 14 17.5 A 2 2 0 0 1 12 19.5 A 2 2 0 0 1 10 17.5 A 2 2 0 0 1 12 15.5 A 2 2 0 0 1 14 17.5 Z' + class='foreground foreground-fill' + style='isolation: auto' + gpa:fill='foreground'> + + id='alert-group'> + id='alert' + display='inline' + transform='scale(0, 0)' + transform-origin='16px 8px' + color='rgb(190,190,190)' + fill='url(#gpa:error) rgb(204,0,0)' + fill-opacity='1' + fill-rule='nonzero' + stroke='none' + overflow='visible' + d='M 20 8 A 4 4 0 0 1 12 8 A 4 4 0 1 1 20 8 Z' + class='error error-fill' + style='mix-blend-mode: normal; isolation: auto' + gpa:fill='error'> + attributeName='transform' + begin='gpa:states(normal disabled, active)' + dur='500ms' + repeatCount='1' + fill='freeze' + values='0 0; 0.5 0.5; 0.75 0.75; 1.1 1.1; 0.95 0.95; 1.05 1.05; 1 1' + keySplines='0 0 1 1; 0 0 0.406056 0.998339; 0.333556 0.333333 0.666889 0.666667; 0.960121 0 0.915405 1; 0.084493 0.333333 1 0.666667; 0 0 1 1' + keyTimes='0; 0; 0.2; 0.200612; 0.533333; 0.866667; 1' + calcMode='spline' + type='scale'/> + attributeName='transform' + begin='gpa:states(active, normal disabled)' + dur='100ms' + repeatCount='1' + fill='freeze' + from='1 1' + to='0 0' + keyTimes='0; 1' + type='scale'/> + id='alert-mask' + opacity='1'> + id='bg' + fill='rgb(255,255,255)' + x='0' + y='0' + width='100%' + height='100%'> + + id='alert-clip' + transform='scale(0, 0)' + transform-origin='16px 8px' + fill='rgb(0,0,0)' + stroke-width='0' + d='M 15.2793 3.05859 C 13.4546 3.33212, 11.8137 4.66287, 11.2755 6.44172 C 10.7016 8.04737, 11.0989 9.917, 12.1836 11.2207 C 13.4262 12.6884, 15.553 13.3679, 17.4004 12.774 C 19.0479 12.333, 20.3634 10.9453, 20.8184 9.31055 C 21.2363 7.75054, 20.8549 6.02048, 19.8164 4.7793 C 18.7692 3.55005, 17.129 2.87923, 15.5176 3.0293 C 15.435 3.01736, 15.3576 3.03527, 15.2793 3.05859 Z' + style='mix-blend-mode: normal; isolation: auto'> + attributeName='transform' + begin='gpa:states(normal disabled, active)' + dur='500ms' + repeatCount='1' + fill='freeze' + values='0 0; 0.5 0.5; 0.75 0.75; 1.1 1.1; 0.95 0.95; 1.05 1.05; 1 1' + keySplines='0 0 1 1; 0 0 0.406056 0.998339; 0.333556 0.333333 0.666889 0.666667; 0.960121 0 0.915405 1; 0.084493 0.333333 1 0.666667; 0 0 1 1' + keyTimes='0; 0; 0.2; 0.200612; 0.533333; 0.866667; 1' + calcMode='spline' + type='scale'/> + attributeName='transform' + begin='gpa:states(active, normal disabled)' + dur='100ms' + repeatCount='1' + fill='freeze' + from='1 1' + to='0 0' + keyTimes='0; 1' + type='scale'/> + id='slash' + display='inline' + visibility='hidden' + transform='none' + opacity='1' + fill='none' + stroke='url(#gpa:foreground) rgb(0,0,0)' + stroke-opacity='1' + stroke-width='1.25' + stroke-linecap='round' + stroke-linejoin='round' + stroke-miterlimit='4' + x1='6.045' + y1='5.42123' + x2='19.2458' + y2='18.6085' + gpa:stroke-minwidth='25%' + gpa:stroke-maxwidth='200%' + class='transparent-fill foreground-stroke' + gpa:stroke='foreground' + gpa:states='disabled' + gpa:transition-type='animate' + gpa:transition-easing='ease-in-out' + gpa:transition-duration='200ms'> + + id='disabled-mask' + opacity='1'> + id='rect2' + fill='rgb(255,255,255)' + x='0' + y='0' + width='100%' + height='100%'> + + id='slash-clip' + visibility='hidden' + stroke='rgb(0,0,0)' + stroke-opacity='1' + stroke-width='1.25' + stroke-linecap='round' + stroke-linejoin='round' + stroke-miterlimit='4' + x1='6.045' + y1='7.045' + x2='19.2458' + y2='20.23' + gpa:stroke-minwidth='25%' + gpa:stroke-maxwidth='200%' + gpa:states='disabled' + gpa:transition-type='animate' + gpa:transition-easing='ease-in-out' + gpa:transition-duration='200ms'> + + + + + + + + + From 259791d7679cac105058302388c06ff1c4122df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 27 Aug 2026 18:00:22 -0700 Subject: [PATCH 09/11] bump dep in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2214fae7..f677d083 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You'll need the following dependencies: * libgdk-pixbuf-2.0-dev * libglib2.0-dev * libgranite-7-dev >= 7.7.0 -* libgtk-4-dev +* libgtk-4-dev >= 4.22 * libwingpanel-9-dev * meson >= 0.58.0 * valac From e9b25d81d45832382a9350c15edaa5a0f237c49e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 27 Aug 2026 19:48:02 -0700 Subject: [PATCH 10/11] Remove extra constants --- src/Indicator.vala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Indicator.vala b/src/Indicator.vala index 7c426824..ee58e59f 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -8,10 +8,6 @@ public class Notifications.Indicator : Wingpanel.Indicator { private const string CHILD_PATH = "/io/elementary/notifications/applications/%s/"; private const string REMEMBER_KEY = "remember"; - private const int ICON_STATE_NORMAL = 0; - private const int ICON_STATE_NEW = 1; - private const int ICON_STATE_DISABLED = 2; - private Gee.HashMap app_settings_cache; private GLib.Settings notify_settings; From f9a17ad789aa5366cf4f0cda2ae23ea45d93fa9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 27 Aug 2026 19:48:32 -0700 Subject: [PATCH 11/11] don't use join path in meson --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index a641addb..75f10a71 100644 --- a/meson.build +++ b/meson.build @@ -42,7 +42,7 @@ shared_module( 'src/Widgets/NotificationsList.vala', 'src/Widgets/NotificationEntry.vala', 'src/Widgets/AppEntry.vala', - 'src' / 'Widgets' / 'Symbol.vala', + 'src/Widgets/Symbol.vala', 'src/Services/NotificationsMonitor.vala', 'src/Services/Notification.vala', 'src/Services/Session.vala',