✨ feat(dock): switch notification popup to click-based, load history in background
This commit is contained in:
1 file changed
+120
-59
@@ -5,7 +5,6 @@ from datetime import datetime
|
|||||||
from fabric.widgets.box import Box
|
from fabric.widgets.box import Box
|
||||||
from fabric.widgets.button import Button
|
from fabric.widgets.button import Button
|
||||||
from fabric.widgets.centerbox import CenterBox
|
from fabric.widgets.centerbox import CenterBox
|
||||||
from fabric.widgets.eventbox import EventBox
|
|
||||||
from fabric.widgets.label import Label
|
from fabric.widgets.label import Label
|
||||||
from fabric.widgets.revealer import Revealer
|
from fabric.widgets.revealer import Revealer
|
||||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||||
@@ -32,7 +31,7 @@ from utils.wayland import WaylandWindow as Window
|
|||||||
|
|
||||||
|
|
||||||
class NotificationHistoryPopup(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):
|
def __init__(self, dock_component=None, **kwargs):
|
||||||
# Get dock position for proper anchoring
|
# Get dock position for proper anchoring
|
||||||
@@ -147,26 +146,28 @@ class NotificationHistoryPopup(Window):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def on_button_press(self, widget, event):
|
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
|
return False
|
||||||
|
|
||||||
def on_enter_notify(self, widget, event):
|
def on_enter_notify(self, widget, event):
|
||||||
"""Handle mouse entering popup"""
|
"""Handle mouse entering popup"""
|
||||||
# Cancel any pending hide from dock component
|
# No action needed for click-based popup
|
||||||
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
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def on_leave_notify(self, widget, event):
|
def on_leave_notify(self, widget, event):
|
||||||
"""Handle mouse leaving popup"""
|
"""Handle mouse leaving popup"""
|
||||||
# Hide popup when mouse leaves
|
# No action needed for click-based popup
|
||||||
self.hide_popup()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def show_popup(self):
|
def show_popup(self):
|
||||||
"""Show the notification history popup"""
|
"""Show the notification history popup"""
|
||||||
|
# Ensure notification history is loaded before showing
|
||||||
|
self.notification_history._ensure_history_loaded()
|
||||||
|
|
||||||
self.set_visible(True)
|
self.set_visible(True)
|
||||||
self.show_all()
|
self.show_all()
|
||||||
# Use GLib.idle_add to ensure the window is shown before revealing
|
# 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))
|
GLib.timeout_add(350, lambda: self.set_visible(False))
|
||||||
|
|
||||||
|
|
||||||
class NotificationIndicator(EventBox):
|
class NotificationIndicator(Box):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.notification_icon = Label(
|
self.notification_icon = Label(
|
||||||
name="notification-icon", markup=icons.notifications
|
name="notification-icon", markup=icons.notifications
|
||||||
@@ -195,77 +196,89 @@ class NotificationIndicator(EventBox):
|
|||||||
self.icon_button = Button(
|
self.icon_button = Button(
|
||||||
name="button-bar-notifications",
|
name="button-bar-notifications",
|
||||||
child=self.notification_icon,
|
child=self.notification_icon,
|
||||||
|
on_clicked=self.on_button_clicked,
|
||||||
)
|
)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name="notification-indicator",
|
name="notification-indicator",
|
||||||
child=self.icon_button,
|
children=[self.icon_button],
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set up event box for proper event handling
|
# Initialize popup as None, will be created in background
|
||||||
self.set_visible_window(False)
|
self.popup = None
|
||||||
self.set_above_child(True)
|
self.popup_visible = False
|
||||||
|
self.popup_creating = False
|
||||||
# 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
|
|
||||||
|
|
||||||
self._connect_to_dnd_state()
|
self._connect_to_dnd_state()
|
||||||
|
|
||||||
# Connect to destroy signal for cleanup
|
# Connect to destroy signal for cleanup
|
||||||
self.connect("destroy", self.on_destroy)
|
self.connect("destroy", self.on_destroy)
|
||||||
|
|
||||||
def on_destroy(self, widget):
|
# Start background creation of popup after a short delay
|
||||||
"""Clean up popup when component is destroyed"""
|
GLib.timeout_add(100, self._create_popup_in_background)
|
||||||
try:
|
|
||||||
# Remove hover timeout
|
|
||||||
if hasattr(self, "_hover_timeout") and self._hover_timeout:
|
|
||||||
GLib.source_remove(self._hover_timeout)
|
|
||||||
self._hover_timeout = None
|
|
||||||
|
|
||||||
# Clean up popup (popup is always created now)
|
def on_destroy(self, widget):
|
||||||
if self.popup:
|
"""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:
|
try:
|
||||||
self.popup.destroy()
|
self.popup.hide_popup()
|
||||||
self.popup = None
|
self.popup_visible = False
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_enter_notify(self, widget, event):
|
def _create_popup_in_background(self):
|
||||||
"""Handle mouse entering the notification component"""
|
"""Create popup in background for instant access"""
|
||||||
print("Notification hover detected!") # Debug print
|
if self.popup is None and not self.popup_creating:
|
||||||
# Cancel any pending hover timeout
|
print("Creating notification popup in background...") # Debug print
|
||||||
if self._hover_timeout:
|
self.popup_creating = True
|
||||||
GLib.source_remove(self._hover_timeout)
|
|
||||||
self._hover_timeout = None
|
|
||||||
|
|
||||||
# Show popup immediately on hover (popup is pre-created)
|
# Use idle_add to create popup without blocking UI
|
||||||
if not self.popup.get_visible():
|
GLib.idle_add(self._do_background_creation)
|
||||||
self.popup.show_popup()
|
|
||||||
return False
|
|
||||||
|
|
||||||
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
|
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):
|
def _connect_to_dnd_state(self):
|
||||||
try:
|
try:
|
||||||
self.notification_history = get_shared_notification_history()
|
self.notification_history = get_shared_notification_history()
|
||||||
@@ -365,14 +378,20 @@ class NotificationHistory(Box):
|
|||||||
self.persistent_notifications = []
|
self.persistent_notifications = []
|
||||||
self.add(self.history_header)
|
self.add(self.history_header)
|
||||||
self.add(self.scrolled_window)
|
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.schedule_midnight_update()
|
||||||
|
|
||||||
self._connect_to_shared_history()
|
self._connect_to_shared_history()
|
||||||
|
|
||||||
self._load_and_sync_dnd_state()
|
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):
|
def _connect_to_shared_history(self):
|
||||||
try:
|
try:
|
||||||
shared_history = get_shared_notification_history()
|
shared_history = get_shared_notification_history()
|
||||||
@@ -422,6 +441,48 @@ class NotificationHistory(Box):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Could not sync DND state: {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):
|
def schedule_midnight_update(self):
|
||||||
schedule_midnight_update(self.on_midnight)
|
schedule_midnight_update(self.on_midnight)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user