From 3c82c35226814bf4638f85069893636602f765ee Mon Sep 17 00:00:00 2001 From: Alain Date: Sat, 8 Feb 2025 07:03:38 -0500 Subject: [PATCH 1/3] first commit --- demo/GraniteDemo.vala | 2 + demo/Views/ToastOverlayView.vala | 75 +++++++++++++ demo/meson.build | 1 + lib/Widgets/NotifyToast.vala | 26 +++++ lib/Widgets/NotifyToastOverlay.vala | 161 ++++++++++++++++++++++++++++ lib/meson.build | 2 + 6 files changed, 267 insertions(+) create mode 100644 demo/Views/ToastOverlayView.vala create mode 100644 lib/Widgets/NotifyToast.vala create mode 100644 lib/Widgets/NotifyToastOverlay.vala diff --git a/demo/GraniteDemo.vala b/demo/GraniteDemo.vala index 9e4d8d305..d656bdc57 100644 --- a/demo/GraniteDemo.vala +++ b/demo/GraniteDemo.vala @@ -25,6 +25,7 @@ public class Granite.Demo : Gtk.Application { var mode_button_view = new ModeButtonView (); var overlaybar_view = new OverlayBarView (); var toast_view = new ToastView (); + var toast_overlay_view = new ToastOverlayView (); var settings_uris_view = new SettingsUrisView (); var utils_view = new UtilsView (); var placeholder = new WelcomeView (); @@ -42,6 +43,7 @@ public class Granite.Demo : Gtk.Application { main_stack.add_titled (overlaybar_view, "overlaybar", "OverlayBar"); main_stack.add_titled (settings_uris_view, "settings_uris", "Settings URIs"); main_stack.add_titled (toast_view, "toasts", "Toast"); + main_stack.add_titled (toast_overlay_view, "toast_overlay", "Toast Overlay"); main_stack.add_titled (utils_view, "utils", "Utils"); main_stack.add_titled (dialogs_view, "dialogs", "Dialogs"); main_stack.add_titled (application_view, "application", "Application"); diff --git a/demo/Views/ToastOverlayView.vala b/demo/Views/ToastOverlayView.vala new file mode 100644 index 000000000..a6fef5875 --- /dev/null +++ b/demo/Views/ToastOverlayView.vala @@ -0,0 +1,75 @@ +/* + * Copyright 2011-2021 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +public class ToastOverlayView : Gtk.Box { + private Granite.NotifyToastOverlay toast_overlay; + + construct { + var button = new Gtk.Button.with_label (_("Press Me")) { + halign = START + }; + + var basic_box = new Gtk.Box (VERTICAL, 12); + basic_box.append (new Granite.HeaderLabel (_("Basic"))); + basic_box.append (button); + + var top_left_button = new Gtk.Button.with_label (_("Top Left")); + var bottom_left_button = new Gtk.Button.with_label (_("Bottom Left")); + var bottom_right_button = new Gtk.Button.with_label (_("Bottom Right")); + + var position_button_box = new Gtk.Box (HORIZONTAL, 6) { + halign = START + }; + position_button_box.append (top_left_button); + position_button_box.append (bottom_left_button); + position_button_box.append (bottom_right_button); + + var position_box = new Gtk.Box (VERTICAL, 12); + position_box.append (new Granite.HeaderLabel (_("Position")) { + secondary_text = _("Location of the toast is customized with the position property. Valid values are 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center' and 'bottom-right'") + }); + position_box.append (position_button_box); + + var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 24) { + margin_start = 24, + margin_end = 24, + margin_top = 24, + margin_bottom = 24 + }; + + box.append (basic_box); + box.append (position_box); + + toast_overlay = new Granite.NotifyToastOverlay () { + child = box + }; + + append (toast_overlay); + + button.clicked.connect (() => { + send_toast (_("Button was pressed!")); + }); + + top_left_button.clicked.connect (() => { + send_toast (_("Top Left"), Granite.NotifyToastPosition.TOP_LEFT); + }); + + bottom_left_button.clicked.connect (() => { + send_toast (_("Bottom Left"), Granite.NotifyToastPosition.BOTTOM_LEFT); + }); + + bottom_right_button.clicked.connect (() => { + send_toast (_("Bottom Right"), Granite.NotifyToastPosition.BOTTOM_RIGHT); + }); + } + + private void send_toast (string title, Granite.NotifyToastPosition position = Granite.NotifyToastPosition.TOP_CENTER) { + var toast = new Granite.NotifyToast (title) { + position = position + }; + + toast_overlay.add_toast (toast); + } +} diff --git a/demo/meson.build b/demo/meson.build index 744f89193..86da61352 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -14,6 +14,7 @@ executable( 'Views/OverlayBarView.vala', 'Views/SettingsUrisView.vala', 'Views/ToastView.vala', + 'Views/ToastOverlayView.vala', 'Views/UtilsView.vala', 'Views/WelcomeView.vala', diff --git a/lib/Widgets/NotifyToast.vala b/lib/Widgets/NotifyToast.vala new file mode 100644 index 000000000..35df7773a --- /dev/null +++ b/lib/Widgets/NotifyToast.vala @@ -0,0 +1,26 @@ +/* + * Copyright 2012–2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +public enum Granite.NotifyToastPosition { + TOP_LEFT, + TOP_CENTER, + TOP_RIGHT, + BOTTOM_LEFT, + BOTTOM_CENTER, + BOTTOM_RIGHT +} + +public class Granite.NotifyToast : GLib.Object { + public string title { get; set construct; } + public NotifyToastPosition position { get; set; default = NotifyToastPosition.TOP_CENTER; } + + public NotifyToast (string title) { + Object (title: title); + } + + construct { + + } +} \ No newline at end of file diff --git a/lib/Widgets/NotifyToastOverlay.vala b/lib/Widgets/NotifyToastOverlay.vala new file mode 100644 index 000000000..7eae08ec1 --- /dev/null +++ b/lib/Widgets/NotifyToastOverlay.vala @@ -0,0 +1,161 @@ +/* + * Copyright 2012–2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +public class Granite.NotifyToastOverlay : Gtk.Widget { + private Gtk.Overlay overlay; + + private Gtk.Widget? _child; + public Gtk.Widget? child { + get { + return _child; + } + + set { + if (value != null && value.get_parent () != null) { + critical ("Tried to set a widget as child that already has a parent."); + return; + } + + if (_child != null) { + _child.unparent (); + } + + _child = value; + + if (_child != null) { + _child.set_parent (overlay); + } + } + } + + private Gee.HashMap position_map; + + public NotifyToastOverlay () { + Object ( + hexpand: true, + vexpand: true + ); + } + + class construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + + construct { + position_map = new Gee.HashMap (); + + position_map[NotifyToastPosition.TOP_LEFT] = new Gtk.Box (VERTICAL, 6) { + halign = START, + valign = START, + }; + + position_map[NotifyToastPosition.TOP_CENTER] = new Gtk.Box (VERTICAL, 6) { + halign = CENTER, + valign = START, + }; + + position_map[NotifyToastPosition.TOP_RIGHT] = new Gtk.Box (VERTICAL, 6) { + halign = END, + valign = START, + }; + + position_map[NotifyToastPosition.BOTTOM_LEFT] = new Gtk.Box (VERTICAL, 6) { + halign = START, + valign = END, + }; + + position_map[NotifyToastPosition.BOTTOM_CENTER] = new Gtk.Box (VERTICAL, 6) { + halign = CENTER, + valign = END, + }; + + position_map[NotifyToastPosition.BOTTOM_RIGHT] = new Gtk.Box (VERTICAL, 6) { + halign = END, + valign = END, + }; + + overlay = new Gtk.Overlay (); + overlay.set_parent (this); + overlay.add_overlay (position_map[NotifyToastPosition.TOP_LEFT]); + overlay.add_overlay (position_map[NotifyToastPosition.TOP_CENTER]); + overlay.add_overlay (position_map[NotifyToastPosition.TOP_RIGHT]); + overlay.add_overlay (position_map[NotifyToastPosition.BOTTOM_LEFT]); + overlay.add_overlay (position_map[NotifyToastPosition.BOTTOM_CENTER]); + overlay.add_overlay (position_map[NotifyToastPosition.BOTTOM_RIGHT]); + } + + public void add_toast (NotifyToast toast) { + var notification = new NotifyToastWidget (toast); + position_map[toast.position].append (notification); + } + + private class NotifyToastWidget : Granite.Bin { + public NotifyToast toast { get; construct; } + + private Gtk.Label notification_label; + private Gtk.Button default_action_button; + private Gtk.Revealer revealer; + + private uint timeout_id; + + public NotifyToastWidget (NotifyToast toast) { + Object ( + toast: toast + ); + } + + construct { + default_action_button = new Gtk.Button () { + visible = false + }; + + var close_button = new Gtk.Button.from_icon_name ("window-close-symbolic") { + valign = Gtk.Align.CENTER + }; + close_button.add_css_class (Granite.STYLE_CLASS_CIRCULAR); + + notification_label = new Gtk.Label (toast.title) { + wrap = true, + wrap_mode = Pango.WrapMode.WORD, + natural_wrap_mode = Gtk.NaturalWrapMode.NONE + }; + + var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); + box.add_css_class (Granite.STYLE_CLASS_OSD); + box.append (close_button); + box.append (notification_label); + box.append (default_action_button); + + revealer = new Gtk.Revealer () { + child = box + }; + + child = revealer; + + Timeout.add (revealer.transition_duration, () => { + revealer.reveal_child = true; + start_timeout (); + return GLib.Source.REMOVE; + }); + } + + private void start_timeout () { + uint duration; + + if (default_action_button.visible) { + duration = 3500; + } else { + duration = 2000; + } + + timeout_id = GLib.Timeout.add (duration, () => { + revealer.reveal_child = false; + // dismissed (DismissReason.EXPIRED); + timeout_id = 0; + return GLib.Source.REMOVE; + }); + } + } +} \ No newline at end of file diff --git a/lib/meson.build b/lib/meson.build index e92fd7881..2aa6c68e3 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -23,6 +23,8 @@ libgranite_sources = files( 'Widgets/HyperTextView.vala', 'Widgets/MessageDialog.vala', 'Widgets/ModeSwitch.vala', + 'Widgets/NotifyToast.vala', + 'Widgets/NotifyToastOverlay.vala', 'Widgets/OverlayBar.vala', 'Widgets/Placeholder.vala', 'Widgets/SettingsSidebarRow.vala', From 0be6bdbb69c57634aacb0f09eda6a9fbf39e77ab Mon Sep 17 00:00:00 2001 From: Alain Date: Sat, 8 Feb 2025 07:15:04 -0500 Subject: [PATCH 2/3] fix overlay child --- lib/Widgets/NotifyToastOverlay.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Widgets/NotifyToastOverlay.vala b/lib/Widgets/NotifyToastOverlay.vala index 7eae08ec1..bdf21fa92 100644 --- a/lib/Widgets/NotifyToastOverlay.vala +++ b/lib/Widgets/NotifyToastOverlay.vala @@ -25,7 +25,7 @@ public class Granite.NotifyToastOverlay : Gtk.Widget { _child = value; if (_child != null) { - _child.set_parent (overlay); + overlay.child = _child; } } } @@ -136,7 +136,7 @@ public class Granite.NotifyToastOverlay : Gtk.Widget { Timeout.add (revealer.transition_duration, () => { revealer.reveal_child = true; - start_timeout (); + // start_timeout (); return GLib.Source.REMOVE; }); } From 44b91386a5301c5d8b2e3391b99b8f55c8e9a15a Mon Sep 17 00:00:00 2001 From: Alain Date: Sat, 8 Feb 2025 07:16:07 -0500 Subject: [PATCH 3/3] add timeout --- lib/Widgets/NotifyToastOverlay.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Widgets/NotifyToastOverlay.vala b/lib/Widgets/NotifyToastOverlay.vala index bdf21fa92..59faefcae 100644 --- a/lib/Widgets/NotifyToastOverlay.vala +++ b/lib/Widgets/NotifyToastOverlay.vala @@ -136,7 +136,7 @@ public class Granite.NotifyToastOverlay : Gtk.Widget { Timeout.add (revealer.transition_duration, () => { revealer.reveal_child = true; - // start_timeout (); + start_timeout (); return GLib.Source.REMOVE; }); }