From aebb9748107bac6d3ebc3bfbce5b783c8b71e1ea Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Mon, 21 Jul 2025 10:26:22 +0545 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(dock):=20switch=20notification?= =?UTF-8?q?=20popup=20to=20click-based,=20load=20history=20in=20background?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/dock/components/notifications.py | 179 +++++++++++++++-------- 1 file changed, 120 insertions(+), 59 deletions(-) diff --git a/modules/dock/components/notifications.py b/modules/dock/components/notifications.py index 6f8fee7..a5398d8 100644 --- a/modules/dock/components/notifications.py +++ b/modules/dock/components/notifications.py @@ -5,7 +5,6 @@ from datetime import datetime from fabric.widgets.box import Box from fabric.widgets.button import Button from fabric.widgets.centerbox import CenterBox -from fabric.widgets.eventbox import EventBox from fabric.widgets.label import Label from fabric.widgets.revealer import Revealer from fabric.widgets.scrolledwindow import ScrolledWindow @@ -32,7 +31,7 @@ from utils.wayland import WaylandWindow as Window class NotificationHistoryPopup(Window): - """Popup window that shows notification history with hover-based reveal""" + """Popup window that shows notification history with click-based reveal""" def __init__(self, dock_component=None, **kwargs): # Get dock position for proper anchoring @@ -147,26 +146,28 @@ class NotificationHistoryPopup(Window): return False def on_button_press(self, widget, event): - """Handle button press events""" + """Handle button press events - close popup when clicking outside""" + # Close popup when clicking outside + if self.dock_component: + self.dock_component.popup_visible = False + self.hide_popup() return False def on_enter_notify(self, widget, event): """Handle mouse entering popup""" - # Cancel any pending hide from dock component - if self.dock_component and hasattr(self.dock_component, '_hover_timeout'): - if self.dock_component._hover_timeout: - GLib.source_remove(self.dock_component._hover_timeout) - self.dock_component._hover_timeout = None + # No action needed for click-based popup return False def on_leave_notify(self, widget, event): """Handle mouse leaving popup""" - # Hide popup when mouse leaves - self.hide_popup() + # No action needed for click-based popup return False def show_popup(self): """Show the notification history popup""" + # Ensure notification history is loaded before showing + self.notification_history._ensure_history_loaded() + self.set_visible(True) self.show_all() # Use GLib.idle_add to ensure the window is shown before revealing @@ -185,7 +186,7 @@ class NotificationHistoryPopup(Window): GLib.timeout_add(350, lambda: self.set_visible(False)) -class NotificationIndicator(EventBox): +class NotificationIndicator(Box): def __init__(self, **kwargs): self.notification_icon = Label( name="notification-icon", markup=icons.notifications @@ -195,77 +196,89 @@ class NotificationIndicator(EventBox): self.icon_button = Button( name="button-bar-notifications", child=self.notification_icon, + on_clicked=self.on_button_clicked, ) super().__init__( name="notification-indicator", - child=self.icon_button, + children=[self.icon_button], **kwargs ) - # Set up event box for proper event handling - self.set_visible_window(False) - self.set_above_child(True) - - # Connect hover events - self.connect("enter-notify-event", self.on_enter_notify) - self.connect("leave-notify-event", self.on_leave_notify) - self.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK) - - # Pre-create popup for immediate display on hover - self.popup = NotificationHistoryPopup(dock_component=self) - self._hover_timeout = None + # Initialize popup as None, will be created in background + self.popup = None + self.popup_visible = False + self.popup_creating = False self._connect_to_dnd_state() # Connect to destroy signal for cleanup self.connect("destroy", self.on_destroy) - def on_destroy(self, widget): - """Clean up popup when component is destroyed""" - try: - # Remove hover timeout - if hasattr(self, "_hover_timeout") and self._hover_timeout: - GLib.source_remove(self._hover_timeout) - self._hover_timeout = None + # Start background creation of popup after a short delay + GLib.timeout_add(100, self._create_popup_in_background) - # Clean up popup (popup is always created now) - if self.popup: + def on_destroy(self, widget): + """Hide popup when component is destroyed""" + try: + # Hide popup instead of destroying it (only if it was created) + if self.popup is not None: try: - self.popup.destroy() - self.popup = None + self.popup.hide_popup() + self.popup_visible = False except Exception: pass except Exception: pass - def on_enter_notify(self, widget, event): - """Handle mouse entering the notification component""" - print("Notification hover detected!") # Debug print - # Cancel any pending hover timeout - if self._hover_timeout: - GLib.source_remove(self._hover_timeout) - self._hover_timeout = None + def _create_popup_in_background(self): + """Create popup in background for instant access""" + if self.popup is None and not self.popup_creating: + print("Creating notification popup in background...") # Debug print + self.popup_creating = True - # Show popup immediately on hover (popup is pre-created) - if not self.popup.get_visible(): - self.popup.show_popup() - return False + # Use idle_add to create popup without blocking UI + GLib.idle_add(self._do_background_creation) - def on_leave_notify(self, widget, event): - """Handle mouse leaving the notification component""" - # Hide popup with a small delay to prevent flickering - self._hover_timeout = GLib.timeout_add(100, self._hide_popup_delayed) - return False - - def _hide_popup_delayed(self): - """Hide popup after delay""" - if self.popup and self.popup.get_visible(): - self.popup.hide_popup() - self._hover_timeout = None return False # Don't repeat timeout + def _do_background_creation(self): + """Actually create the popup in background""" + try: + self.popup = NotificationHistoryPopup(dock_component=self) + print("Notification popup created in background!") # Debug print + except Exception as e: + print(f"Error creating popup in background: {e}") + finally: + self.popup_creating = False + + return False # Don't repeat + + def on_button_clicked(self, button): + """Handle button click to toggle notification popup""" + print("Notification button clicked!") # Debug print + + # If popup is still being created, create it synchronously + if self.popup is None: + if self.popup_creating: + print("Popup still creating, waiting...") # Debug print + # Force synchronous creation if user clicked while background creation is happening + self.popup = NotificationHistoryPopup(dock_component=self) + self.popup_creating = False + else: + print("Creating notification popup immediately...") # Debug print + self.popup = NotificationHistoryPopup(dock_component=self) + + if self.popup_visible: + # Hide popup if currently visible + self.popup.hide_popup() + self.popup_visible = False + else: + # Show popup if currently hidden + self.popup.show_popup() + self.popup_visible = True + def _connect_to_dnd_state(self): try: self.notification_history = get_shared_notification_history() @@ -365,14 +378,20 @@ class NotificationHistory(Box): self.persistent_notifications = [] self.add(self.history_header) self.add(self.scrolled_window) - self._load_persistent_history() - cleanup_orphan_cached_images(self.persistent_notifications) + + # Initialize history loading state + self.history_loaded = False + self.history_loading = False + self.schedule_midnight_update() self._connect_to_shared_history() self._load_and_sync_dnd_state() + # Start background loading of history after a short delay + GLib.timeout_add(200, self._load_history_in_background) + def _connect_to_shared_history(self): try: shared_history = get_shared_notification_history() @@ -422,6 +441,48 @@ class NotificationHistory(Box): except Exception as e: print(f"Could not sync DND state: {e}") + def _load_history_in_background(self): + """Start loading notification history in background""" + if not self.history_loaded and not self.history_loading: + print("Starting background loading of notification history...") # Debug print + self.history_loading = True + + # Use idle_add to load history without blocking UI + GLib.idle_add(self._do_background_history_loading) + + return False # Don't repeat timeout + + def _do_background_history_loading(self): + """Actually load the history in background""" + try: + print("Loading notification history in background...") # Debug print + self._load_persistent_history() + cleanup_orphan_cached_images(self.persistent_notifications) + self.history_loaded = True + print("Notification history loaded in background!") # Debug print + except Exception as e: + print(f"Error loading history in background: {e}") + finally: + self.history_loading = False + + return False # Don't repeat + + def _ensure_history_loaded(self): + """Ensure notification history is loaded (force if needed)""" + if not self.history_loaded: + if self.history_loading: + print("History still loading in background, forcing synchronous load...") # Debug print + # Force synchronous loading if user opened popup while background loading + self._load_persistent_history() + cleanup_orphan_cached_images(self.persistent_notifications) + self.history_loaded = True + self.history_loading = False + else: + print("Loading notification history synchronously...") # Debug print + self._load_persistent_history() + cleanup_orphan_cached_images(self.persistent_notifications) + self.history_loaded = True + def schedule_midnight_update(self): schedule_midnight_update(self.on_midnight)