diff --git a/README.md b/README.md index 186ea9a..9ccdc9e 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Post your setup here: [Config flex 💪](https://github.com/ErikReider/SwayNotif ## Features +- Grouped notifications - Keyboard shortcuts - Notification body markup with image support - Inline replies diff --git a/data/icons/meson.build b/data/icons/meson.build new file mode 100644 index 0000000..0f93645 --- /dev/null +++ b/data/icons/meson.build @@ -0,0 +1,4 @@ +app_resources += gnome.compile_resources('icon-resources', + 'swaync_icons.gresource.xml', + c_name: 'sway_notification_center_icons' +) diff --git a/data/icons/swaync-close-symbolic.svg b/data/icons/swaync-close-symbolic.svg new file mode 100644 index 0000000..f68a861 --- /dev/null +++ b/data/icons/swaync-close-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/data/icons/swaync-collapse-symbolic.svg b/data/icons/swaync-collapse-symbolic.svg new file mode 100644 index 0000000..1e7f5c5 --- /dev/null +++ b/data/icons/swaync-collapse-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/data/icons/swaync_icons.gresource.xml b/data/icons/swaync_icons.gresource.xml new file mode 100644 index 0000000..7e681b2 --- /dev/null +++ b/data/icons/swaync_icons.gresource.xml @@ -0,0 +1,7 @@ + + + + swaync-collapse-symbolic.svg + swaync-close-symbolic.svg + + diff --git a/data/meson.build b/data/meson.build index 01f1eb7..699cddc 100644 --- a/data/meson.build +++ b/data/meson.build @@ -2,6 +2,8 @@ install_data('org.erikreider.swaync.gschema.xml', install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') ) +subdir('icons') + compile_schemas = find_program('glib-compile-schemas', required: false) if compile_schemas.found() test('Validate schema file', compile_schemas, diff --git a/meson.build b/meson.build index 170520e..f8249a0 100644 --- a/meson.build +++ b/meson.build @@ -9,6 +9,9 @@ add_project_arguments(['--enable-gobject-tracing'], language: 'vala') add_project_arguments(['--enable-checking'], language: 'vala') i18n = import('i18n') +gnome = import('gnome') + +app_resources = [] subdir('data') subdir('src') diff --git a/src/animation/animation.vala b/src/animation/animation.vala new file mode 100644 index 0000000..2068b7e --- /dev/null +++ b/src/animation/animation.vala @@ -0,0 +1,150 @@ +namespace SwayNotificationCenter { + public class Animation : Object { + + private unowned Gtk.Widget widget; + + double value; + + double value_from; + double value_to; + int64 duration; + + int64 start_time; + uint tick_cb_id; + ulong unmap_cb_id; + + unowned AnimationEasingFunc easing_func; + unowned AnimationValueCallback value_cb; + unowned AnimationDoneCallback done_cb; + + bool is_done; + + + public delegate void AnimationValueCallback (double value); + + public delegate void AnimationDoneCallback (); + + public delegate double AnimationEasingFunc (double t); + + public Animation (Gtk.Widget widget, int64 duration, + AnimationEasingFunc easing_func, + AnimationValueCallback value_cb, + AnimationDoneCallback done_cb) { + this.widget = widget; + this.duration = duration; + this.easing_func = easing_func; + this.value_cb = value_cb; + this.done_cb = done_cb; + + this.is_done = false; + } + + ~Animation () { + stop (); + } + + void set_value (double value) { + this.value = value; + this.value_cb (value); + } + + void done () { + if (is_done) return; + + is_done = true; + done_cb (); + } + + bool tick_cb (Gtk.Widget widget, Gdk.FrameClock frame_clock) { + int64 frame_time = frame_clock.get_frame_time () / 1000; /* ms */ + double t = (double) (frame_time - start_time) / duration; + + if (t >= 1) { + tick_cb_id = 0; + + set_value (value_to); + + if (unmap_cb_id != 0) { + SignalHandler.disconnect (widget, unmap_cb_id); + unmap_cb_id = 0; + } + + done (); + + return Source.REMOVE; + } + + set_value (lerp (value_from, value_to, easing_func (t))); + + return Source.CONTINUE; + } + + public void start (double from, double to) { + this.value_from = from; + this.value_to = to; + this.value = from; + this.is_done = false; + + unowned Gtk.Settings ? gsettings = Gtk.Settings.get_default (); + bool animations_enabled = + gsettings != null ? gsettings.gtk_enable_animations : true; + if (animations_enabled != true || + !widget.get_mapped () || duration <= 0) { + set_value (value_to); + + done (); + return; + } + + + start_time = widget.get_frame_clock ().get_frame_time () / 1000; + + if (tick_cb_id != 0) return; + + unmap_cb_id = + Signal.connect_swapped (widget, "unmap", (Callback) stop, this); + tick_cb_id = widget.add_tick_callback (tick_cb); + } + + public void stop () { + if (tick_cb_id != 0) { + widget.remove_tick_callback (tick_cb_id); + tick_cb_id = 0; + } + + if (unmap_cb_id != 0) { + SignalHandler.disconnect (widget, unmap_cb_id); + unmap_cb_id = 0; + } + + done (); + } + + public double get_value () { + return value; + } + + public static double lerp (double a, double b, double t) { + return a * (1.0 - t) + b * t; + } + + public static double ease_out_cubic (double t) { + double p = t - 1; + return p * p * p + 1; + } + + public static double ease_in_cubic (double t) { + return t * t * t; + } + + public static double ease_in_out_cubic (double t) { + double p = t * 2; + + if (p < 1) return 0.5 * p * p * p; + + p -= 2; + + return 0.5 * (p * p * p + 2); + } + } +} diff --git a/src/constants.vala.in b/src/constants.vala.in index 28cfa2c..31779bc 100644 --- a/src/constants.vala.in +++ b/src/constants.vala.in @@ -1,4 +1,5 @@ public class Constants { public const string VERSION = @VERSION@; public const string VERSIONNUM = @VERSION_NUM@; + public const uint ANIMATION_DURATION = 400; } diff --git a/src/controlCenter/controlCenter.ui b/src/controlCenter/controlCenter.ui index fe11730..07d230a 100644 --- a/src/controlCenter/controlCenter.ui +++ b/src/controlCenter/controlCenter.ui @@ -35,24 +35,7 @@ True never - - True - False - True - none - - - True - False - end - none - False - - - - + diff --git a/src/controlCenter/controlCenter.vala b/src/controlCenter/controlCenter.vala index 28acdd2..15c2ab1 100644 --- a/src/controlCenter/controlCenter.vala +++ b/src/controlCenter/controlCenter.vala @@ -1,20 +1,30 @@ namespace SwayNotificationCenter { [GtkTemplate (ui = "/org/erikreider/sway-notification-center/controlCenter/controlCenter.ui")] public class ControlCenter : Gtk.ApplicationWindow { - [GtkChild] unowned Gtk.Box notifications_box; [GtkChild] - unowned Gtk.ScrolledWindow scrolled_window; - [GtkChild] - unowned Gtk.Viewport viewport; - [GtkChild] unowned Gtk.Stack stack; [GtkChild] - unowned Gtk.ListBox list_box; + unowned Gtk.ScrolledWindow scrolled_window; + FadedViewport viewport = new FadedViewport (20); + Gtk.ListBox list_box = new Gtk.ListBox (); + [GtkChild] unowned Gtk.Box box; + unowned NotificationGroup ? expanded_group = null; + private double fade_animation_progress = 1.0; + private Animation ? notification_fade_animation; + private double scroll_animation_progress = 1.0; + private Animation ? scroll_animation; + + HashTable noti_groups_id = + new HashTable (direct_hash, direct_equal); + /** NOTE: Only includes groups with ids with length of > 0 */ + HashTable noti_groups_name = + new HashTable (str_hash, str_equal); + const string STACK_NOTIFICATIONS_PAGE = "notifications-list"; const string STACK_PLACEHOLDER_PAGE = "notifications-placeholder"; @@ -25,9 +35,8 @@ namespace SwayNotificationCenter { private SwayncDaemon swaync_daemon; private NotiDaemon noti_daemon; - private uint list_position = 0; + private int list_position = 0; - private double last_upper = 0; private bool list_reverse = false; private Gtk.Align list_align = Gtk.Align.START; @@ -40,6 +49,18 @@ namespace SwayNotificationCenter { this.swaync_daemon.reloading_css.connect (reload_notifications_style); + viewport.set_visible (true); + viewport.set_vexpand (true); + viewport.set_shadow_type (Gtk.ShadowType.NONE); + scrolled_window.add (viewport); + + list_box.set_visible (true); + list_box.set_valign (Gtk.Align.END); + list_box.set_selection_mode (Gtk.SelectionMode.NONE); + list_box.set_activate_on_single_click (false); + list_box.get_style_context ().add_class ("control-center-list"); + viewport.add (list_box); + if (swaync_daemon.use_layer_shell) { if (!GtkLayerShell.is_supported ()) { stderr.printf ("GTKLAYERSHELL IS NOT SUPPORTED!\n"); @@ -56,8 +77,6 @@ namespace SwayNotificationCenter { GtkLayerShell.set_anchor (this, GtkLayerShell.Edge.BOTTOM, true); } - viewport.size_allocate.connect (size_alloc); - this.map.connect (() => { set_anchor (); // Wait until the layer has attached @@ -154,71 +173,7 @@ namespace SwayNotificationCenter { return true; }); - this.key_press_event.connect ((w, event_key) => { - if (this.get_focus () is Gtk.Entry) return false; - if (event_key.type == Gdk.EventType.KEY_PRESS) { - var children = list_box.get_children (); - Notification noti = (Notification) - list_box.get_focus_child (); - switch (Gdk.keyval_name (event_key.keyval)) { - case "Return": - if (noti != null) noti.click_default_action (); - break; - case "Delete": - case "BackSpace": - if (noti != null) { - if (children.length () == 0) break; - if (list_reverse && - children.first ().data != noti) { - list_position--; - } else if (children.last ().data == noti) { - if (list_position > 0) list_position--; - } - close_notification (noti.param.applied_id, true); - } - break; - case "C": - close_all_notifications (); - break; - case "D": - try { - swaync_daemon.toggle_dnd (); - } catch (Error e) { - error ("Error: %s\n", e.message); - } - break; - case "Down": - if (list_position + 1 < children.length ()) { - ++list_position; - } - break; - case "Up": - if (list_position > 0) --list_position; - break; - case "Home": - list_position = 0; - break; - case "End": - list_position = children.length () - 1; - if (list_position == uint.MAX) list_position = 0; - break; - default: - // Pressing 1-9 to activate a notification action - for (int i = 0; i < 9; i++) { - uint keyval = Gdk.keyval_from_name ( - (i + 1).to_string ()); - if (event_key.keyval == keyval) { - if (noti != null) noti.click_alt_action (i); - break; - } - } - break; - } - navigate_list (list_position); - } - // Override the builtin list navigation - return true; - }); + key_press_event.connect (key_press_event_cb); stack.set_visible_child_name (STACK_PLACEHOLDER_PAGE); // Switches the stack page depending on the amount of notifications @@ -232,6 +187,171 @@ namespace SwayNotificationCenter { }); add_widgets (); + + notification_fade_animation = new Animation (this, Constants.ANIMATION_DURATION, + Animation.ease_in_out_cubic, + fade_animation_value_cb, + fade_animation_done_cb); + scroll_animation = new Animation (this, Constants.ANIMATION_DURATION, + Animation.ease_in_out_cubic, + scroll_animation_value_cb, + scroll_animation_done_cb); + list_box.draw.connect (list_box_draw_cb); + } + + void fade_animation_value_cb (double progress) { + this.fade_animation_progress = progress; + + this.queue_draw (); + } + + void fade_animation_done_cb () {} + + void fade_animate (double to) { + notification_fade_animation.stop (); + notification_fade_animation.start (fade_animation_progress, to); + } + + void scroll_animation_value_cb (double progress) { + this.scroll_animation_progress = progress; + + // Scroll to the top of the group + if (scroll_animation_progress > 0) { + scrolled_window.vadjustment.set_value (scroll_animation_progress); + } + } + + void scroll_animation_done_cb () { + int y = expanded_group.get_relative_y (list_box); + if (y > 0) { + scrolled_window.vadjustment.set_value (y); + } + } + + void scroll_animate (double to) { + scroll_animation.stop (); + scroll_animation.start (scroll_animation_progress, to); + } + + /// Fade non-expanded groups when one group is expanded + private bool list_box_draw_cb (Cairo.Context cr) { + Cairo.Pattern fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1); + fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1 - fade_animation_progress - 0.5); + + foreach (unowned Gtk.Widget widget in list_box.get_children ()) { + Gtk.Allocation alloc; + widget.get_allocated_size (out alloc, null); + + cr.save (); + cr.translate (0, alloc.y); + + cr.push_group (); + widget.draw (cr); + + cr.scale (alloc.width, alloc.height); + if (widget != expanded_group) { + cr.set_source (fade_gradient); + cr.rectangle (0, 0, alloc.width, alloc.height); + cr.set_operator (Cairo.Operator.DEST_OUT); + cr.fill (); + } + + cr.pop_group_to_source (); + cr.paint (); + + cr.restore (); + } + return true; + } + + private bool key_press_event_cb (Gdk.EventKey event_key) { + if (this.get_focus () is Gtk.Entry) return false; + if (event_key.type == Gdk.EventType.KEY_PRESS) { + var children = list_box.get_children (); + var group = (NotificationGroup) list_box.get_focus_child (); + switch (Gdk.keyval_name (event_key.keyval)) { + case "Return": + if (group != null) { + var noti = group.get_latest_notification (); + if (group.only_single_notification () && noti != null) { + noti.click_default_action (); + break; + } + group.on_expand_change (group.toggle_expanded ()); + } + break; + case "Delete": + case "BackSpace": + if (group != null) { + int len = (int) children.length (); + if (len == 0) break; + // Add a delta so that we select the next notification + // due to it not being gone from the list yet due to + // the fade transition + int delta = 2; + if (list_reverse) { + if (children.first ().data != group) { + delta = 0; + } + list_position--; + } else { + if (list_position > 0) list_position--; + if (children.last ().data == group) { + delta = 0; + } + } + var noti = group.get_latest_notification (); + if (group.only_single_notification () && noti != null) { + close_notification (noti.param.applied_id, true); + break; + } + group.close_all_notifications (); + navigate_list (list_position + delta); + return true; + } + break; + case "C": + close_all_notifications (); + break; + case "D": + try { + swaync_daemon.toggle_dnd (); + } catch (Error e) { + error ("Error: %s\n", e.message); + } + break; + case "Down": + if (list_position + 1 < children.length ()) { + ++list_position; + } + break; + case "Up": + if (list_position > 0) --list_position; + break; + case "Home": + list_position = 0; + break; + case "End": + list_position = ((int) children.length ()) - 1; + if (list_position == uint.MAX) list_position = 0; + break; + default: + // Pressing 1-9 to activate a notification action + for (int i = 0; i < 9; i++) { + uint keyval = Gdk.keyval_from_name ( + (i + 1).to_string ()); + if (event_key.keyval == keyval && group != null) { + var noti = group.get_latest_notification (); + noti.click_alt_action (i); + break; + } + } + break; + } + navigate_list (list_position); + } + // Override the builtin list navigation + return true; } /** Adds all custom widgets. Removes previous widgets */ @@ -358,28 +478,37 @@ namespace SwayNotificationCenter { box.set_valign (align_y); list_box.set_valign (list_align); - list_box.set_sort_func ((w1, w2) => { - var a = (Notification) w1; - var b = (Notification) w2; - if (a == null || b == null) return 0; - // Sort the list in reverse if needed - if (a.param.time == b.param.time) return 0; - int val = list_reverse ? 1 : -1; - return a.param.time > b.param.time ? val : val * -1; - }); + list_box.set_sort_func (list_box_sort_func); // Always set the size request in all events. box.set_size_request (ConfigModel.instance.control_center_width, ConfigModel.instance.control_center_height); } - private void size_alloc () { - var adj = viewport.vadjustment; - double upper = adj.get_upper (); - if (last_upper < upper) { - scroll_to_start (list_reverse); + /** + * Returns < 0 if row1 should be before row2, 0 if they are equal + * and > 0 otherwise + */ + private int list_box_sort_func (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) { + int val = list_reverse ? 1 : -1; + + var a_group = (NotificationGroup) row1; + var b_group = (NotificationGroup) row2; + + // Check urgency before time + var a_urgency = a_group.get_is_urgent (); + var b_urgency = b_group.get_is_urgent (); + if (a_urgency != b_urgency) { + return a_urgency ? val : val * -1; } - last_upper = upper; + + // Check time + var a_time = a_group.get_time (); + var b_time = b_group.get_time (); + if (a_time < 0 || b_time < 0) return 0; + // Sort the list in reverse if needed + if (a_time == b_time) return 0; + return a_time > b_time ? val : val * -1; } private void scroll_to_start (bool reverse) { @@ -391,20 +520,26 @@ namespace SwayNotificationCenter { } public uint notification_count () { - return list_box.get_children ().length (); + uint count = 0; + foreach (unowned Gtk.Widget widget in list_box.get_children ()) { + if (widget is NotificationGroup) { + count += ((NotificationGroup) widget).get_num_notifications (); + } + } + return count; } public void close_all_notifications () { foreach (var w in list_box.get_children ()) { - Notification noti = (Notification) w; - if (noti != null) noti.close_notification (false); + NotificationGroup group = (NotificationGroup) w; + if (group != null) group.close_all_notifications (); } try { swaync_daemon.subscribe_v2 (notification_count (), - swaync_daemon.get_dnd (), - get_visibility (), - swaync_daemon.inhibited); + swaync_daemon.get_dnd (), + get_visibility (), + swaync_daemon.inhibited); } catch (Error e) { stderr.printf (e.message + "\n"); } @@ -414,11 +549,20 @@ namespace SwayNotificationCenter { } } - private void navigate_list (uint i) { - var widget = list_box.get_children ().nth_data (i); + private void navigate_list (int i) { + unowned Gtk.Widget ? widget = list_box.get_children ().nth_data (i); + if (widget == null) { + // Try getting the last widget + if (list_reverse) { + widget = list_box.get_children ().nth_data (0); + } else { + int len = ((int) list_box.get_children ().length ()) - 1; + widget = list_box.get_children ().nth_data (len); + } + } if (widget != null) { - list_box.set_focus_child (widget); widget.grab_focus (); + list_box.set_focus_child (widget); } } @@ -431,20 +575,20 @@ namespace SwayNotificationCenter { if (this.visible) { // Focus the first notification list_position = list_reverse ? - (list_box.get_children ().length () - 1) : 0; + (((int) list_box.get_children ().length ()) - 1) : 0; if (list_position == uint.MAX) list_position = 0; list_box.grab_focus (); navigate_list (list_position); foreach (var w in list_box.get_children ()) { - var noti = (Notification) w; - if (noti != null) noti.set_time (); + var group = (NotificationGroup) w; + if (group != null) group.update (); } } swaync_daemon.subscribe_v2 (notification_count (), - noti_daemon.dnd, - this.visible, - swaync_daemon.inhibited); + noti_daemon.dnd, + this.visible, + swaync_daemon.inhibited); } public bool toggle_visibility () { @@ -463,7 +607,9 @@ namespace SwayNotificationCenter { } public void close_notification (uint32 id, bool dismiss) { - foreach (var w in list_box.get_children ()) { + unowned NotificationGroup group = null; + if (!noti_groups_id.lookup_extended (id, null, out group))return; + foreach (var w in group.get_notifications ()) { var noti = (Notification) w; if (noti != null && noti.param.applied_id == id) { if (!dismiss) { @@ -471,21 +617,37 @@ namespace SwayNotificationCenter { noti.destroy (); } else { noti.close_notification (false); - list_box.remove (w); + group.remove_notification (noti); } + noti_groups_id.remove (id); break; } } + if (group.is_empty ()) { + if (group.name_id.length > 0) { + noti_groups_name.remove (group.name_id); + } + if (expanded_group == group) { + expanded_group = null; + fade_animate (1); + } + group.destroy (); + } } public void replace_notification (uint32 id, NotifyParams new_params) { - foreach (var w in list_box.get_children ()) { - var noti = (Notification) w; - if (noti != null && noti.param.applied_id == id) { - noti.replace_notification (new_params); - // Position the notification in the beginning of the list - list_box.invalidate_sort (); - return; + unowned NotificationGroup group = null; + if (noti_groups_id.lookup_extended (id, null, out group)) { + foreach (var w in group.get_notifications ()) { + var noti = (Notification) w; + if (noti != null && noti.param.applied_id == id) { + noti_groups_id.remove (id); + noti_groups_id.set (new_params.applied_id, group); + noti.replace_notification (new_params); + // Position the notification in the beginning of the list + list_box.invalidate_sort (); + return; + } } } @@ -498,19 +660,60 @@ namespace SwayNotificationCenter { noti_daemon, NotificationType.CONTROL_CENTER); noti.grab_focus.connect ((w) => { - uint i = list_box.get_children ().index (w); + int i = list_box.get_children ().index (w); if (list_position != uint.MAX && list_position != i) { list_position = i; } }); noti.set_time (); - list_box.add (noti); + + NotificationGroup ? group = null; + if (param.name_id.length > 0) { + noti_groups_name.lookup_extended (param.name_id, null, out group); + } + if (group == null) { + group = new NotificationGroup (param.name_id, param.display_name); + // Collapse other groups on expand + group.on_expand_change.connect ((expanded) => { + if (!expanded) { + fade_animate (1); + foreach (unowned Gtk.Widget child in list_box.get_children ()) { + child.set_sensitive (true); + } + return; + } + expanded_group = group; + expanded_group.set_sensitive (true); + fade_animate (0); + int y = expanded_group.get_relative_y (list_box); + if (y > 0) { + scroll_animate (y); + } + foreach (unowned Gtk.Widget child in list_box.get_children ()) { + NotificationGroup g = (NotificationGroup) child; + if (g != null && g != group) { + g.set_expanded (false); + if (g.only_single_notification ()) { + g.set_sensitive (false); + } + } + } + }); + if (param.name_id.length > 0) { + noti_groups_name.set (param.name_id, group); + } + list_box.add (group); + } + noti_groups_id.set (param.applied_id, group); + + group.add_notification (noti); + list_box.invalidate_sort (); scroll_to_start (list_reverse); try { swaync_daemon.subscribe_v2 (notification_count (), - swaync_daemon.get_dnd (), - get_visibility (), - swaync_daemon.inhibited); + swaync_daemon.get_dnd (), + get_visibility (), + swaync_daemon.inhibited); } catch (Error e) { stderr.printf (e.message + "\n"); } @@ -527,8 +730,13 @@ namespace SwayNotificationCenter { /** Forces each notification EventBox to reload its style_context #27 */ private void reload_notifications_style () { foreach (var c in list_box.get_children ()) { - Notification noti = (Notification) c; - if (noti != null) noti.reload_style_context (); + NotificationGroup group = (NotificationGroup) c; + if (group != null) { + foreach (unowned var widget in group.get_notifications ()) { + Notification noti = (Notification) widget; + noti.reload_style_context (); + } + } } } } diff --git a/src/fadedViewport/fadedViewport.vala b/src/fadedViewport/fadedViewport.vala new file mode 100644 index 0000000..cd5dfd4 --- /dev/null +++ b/src/fadedViewport/fadedViewport.vala @@ -0,0 +1,157 @@ +namespace SwayNotificationCenter { + public class FadedViewport : Gtk.Viewport { + private int fade_height = 30; + + private FadedViewportChild container; + + public FadedViewport (int fade_height) { + if (fade_height > 0) this.fade_height = fade_height; + this.container = new FadedViewportChild (this.fade_height); + + base.add (container); + } + + public override void add (Gtk.Widget widget) { + container.add (widget); + } + + public override void remove (Gtk.Widget widget) { + container.remove (widget); + } + + public override bool draw (Cairo.Context cr) { + Gtk.Allocation alloc; + get_allocated_size (out alloc, null); + + Cairo.Pattern top_fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1); + top_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1); + top_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 0); + Cairo.Pattern bottom_fade_gradient = new Cairo.Pattern.linear (0, 0, 0, 1); + bottom_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 0); + bottom_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 1); + + cr.save (); + cr.push_group (); + + // Draw widgets + base.draw (cr); + + /// Draw vertical fade + + // Top fade + cr.save (); + cr.scale (alloc.width, fade_height); + cr.rectangle (0, 0, alloc.width, fade_height); + cr.set_source (top_fade_gradient); + cr.set_operator (Cairo.Operator.DEST_OUT); + cr.fill (); + cr.restore (); + // Bottom fade + cr.save (); + cr.translate (0, alloc.height - fade_height); + cr.scale (alloc.width, fade_height); + cr.rectangle (0, 0, alloc.width, fade_height); + cr.set_source (bottom_fade_gradient); + cr.set_operator (Cairo.Operator.DEST_OUT); + cr.fill (); + cr.restore (); + + cr.pop_group_to_source (); + cr.paint (); + cr.restore (); + return true; + } + } +} + +private class FadedViewportChild : Gtk.Container { + private int y_padding; + + private unowned Gtk.Widget _child; + + public FadedViewportChild (int y_padding) { + base.set_has_window (false); + base.set_can_focus (true); + base.set_redraw_on_allocate (false); + + this.y_padding = (int) (y_padding * 0.5); + this._child = null; + + this.show (); + } + + public override void add (Gtk.Widget widget) { + if (this._child == null) { + widget.set_parent (this); + this._child = widget; + } + } + + public override void remove (Gtk.Widget widget) { + if (this._child == widget) { + widget.unparent (); + this._child = null; + if (this.get_visible () && widget.get_visible ()) { + this.queue_resize_no_redraw (); + } + } + } + + public override void forall_internal (bool include_internals, Gtk.Callback callback) { + if (this._child != null) { + callback (this._child); + } + } + + public override Gtk.SizeRequestMode get_request_mode () { + if (this._child != null) { + return this._child.get_request_mode (); + } else { + return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; + } + } + + public override void size_allocate (Gtk.Allocation allocation) { + Gtk.Allocation child_allocation = Gtk.Allocation (); + uint border_width = this.get_border_width (); + if (this._child != null && this._child.get_visible ()) { + child_allocation.x = allocation.x + (int) border_width; + child_allocation.y = allocation.y + y_padding + (int) border_width; + child_allocation.width = allocation.width - 2 * (int) border_width; + child_allocation.height = allocation.height + y_padding * 2 + - 2 * (int) border_width; + this._child.size_allocate (child_allocation); + if (this.get_realized ()) { + this._child.show (); + } + } + if (this.get_realized ()) { + if (this._child != null) { + this._child.set_child_visible (true); + } + } + base.size_allocate (allocation); + } + + public override void get_preferred_height_for_width (int width, + out int minimum_height, + out int natural_height) { + minimum_height = 0; + natural_height = 0; + + if (_child != null && _child.get_visible ()) { + _child.get_preferred_height_for_width (width, + out minimum_height, + out natural_height); + + minimum_height += y_padding * 2; + natural_height += y_padding * 2; + } + + } + + public override bool draw (Cairo.Context cr) { + base.draw (cr); + return false; + } +} diff --git a/src/functions.vala b/src/functions.vala index 7ba9445..7b46f7b 100644 --- a/src/functions.vala +++ b/src/functions.vala @@ -8,6 +8,10 @@ namespace SwayNotificationCenter { public static void init () { system_css_provider = new Gtk.CssProvider (); user_css_provider = new Gtk.CssProvider (); + + // Init resources + var theme = Gtk.IconTheme.get_default (); + theme.add_resource_path ("/org/erikreider/swaync/icons"); } public static void set_image_path (owned string path, diff --git a/src/meson.build b/src/meson.build index 8a62739..f7bc46f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -50,13 +50,16 @@ widget_sources = [ app_sources = [ 'main.vala', + 'animation/animation.vala', 'orderedHashTable/orderedHashTable.vala', 'configModel/configModel.vala', 'swayncDaemon/swayncDaemon.vala', 'notiDaemon/notiDaemon.vala', 'notiModel/notiModel.vala', + 'fadedViewport/fadedViewport.vala', 'notificationWindow/notificationWindow.vala', 'notification/notification.vala', + 'notificationGroup/notificationGroup.vala', 'controlCenter/controlCenter.vala', widget_sources, 'blankWindow/blankWindow.vala', @@ -119,15 +122,14 @@ args = [ ] sysconfdir = get_option('sysconfdir') -gnome = import('gnome') -app_sources += gnome.compile_resources('sway_notification_center-resources', +app_resources += gnome.compile_resources('sway_notification_center-resources', 'sway_notification_center.gresource.xml', c_name: 'sway_notification_center' ) executable('swaync', - app_sources, + [ app_sources, app_resources ], vala_args: args, dependencies: app_deps, install: true, diff --git a/src/notiModel/notiModel.vala b/src/notiModel/notiModel.vala index e647b71..9b218c1 100644 --- a/src/notiModel/notiModel.vala +++ b/src/notiModel/notiModel.vala @@ -115,6 +115,12 @@ namespace SwayNotificationCenter { public Array actions { get; set; } + public DesktopAppInfo ? desktop_app_info = null; + + public string name_id; + + public string display_name; + public NotifyParams (uint32 applied_id, string app_name, uint32 replaces_id, @@ -139,6 +145,32 @@ namespace SwayNotificationCenter { parse_hints (); parse_actions (actions); + + // Try to get the desktop file + string[] entries = {}; + if (desktop_entry != null) entries += desktop_entry.replace (".desktop", ""); + if (app_name != null) entries += app_name.replace (".desktop", ""); + foreach (string entry in entries) { + var app_info = new DesktopAppInfo ("%s.desktop".printf (entry)); + // Checks if the .desktop file actually exists or not + if (app_info is DesktopAppInfo) { + desktop_app_info = app_info; + break; + } + } + + // Set name_id + this.name_id = this.desktop_entry ?? this.app_name ?? ""; + + // Set display_name and make the first letter upper case + string ? display_name = this.desktop_entry ?? this.app_name; + if (desktop_app_info != null) { + display_name = desktop_app_info.get_display_name (); + } + if (display_name == null || display_name.length == 0) { + display_name = "Unknown"; + } + this.display_name = display_name.splice (0, 1, display_name.up (1)); } private void parse_hints () { diff --git a/src/notification/notification.ui b/src/notification/notification.ui index 7c38125..67e8ed2 100644 --- a/src/notification/notification.ui +++ b/src/notification/notification.ui @@ -281,7 +281,7 @@ True False - window-close-symbolic + swaync-close-symbolic