diff --git a/modules/notification/notification.py b/modules/notification/notification.py index 2377158..6be8a07 100644 --- a/modules/notification/notification.py +++ b/modules/notification/notification.py @@ -180,26 +180,15 @@ class NotificationWidget(Box): ], ), Box(h_expand=True), - Box( - orientation="v", - children=[ - self.create_close_button() if self.show_close_button else Box(), - Box(v_expand=True), - ], + Button( + name="notification-close-button", + image=CustomImage(icon_name="close-symbolic", icon_size=18), + visible=True, # Initially hidden + on_clicked=lambda *_: self.notification.close("dismissed-by-user"), ), ], ) - def create_close_button(self): - if self.close_button is None: - self.close_button = Button( - name="notification-close-button", - visible=False, # Initially hidden - on_clicked=lambda *_: self.notification.close("dismissed-by-user"), - child=Label(label="×", name="close-button-label"), - ) - return self.close_button - def _on_enter_notify(self, widget, event): self._is_hovered = True if self.close_button: @@ -297,18 +286,20 @@ class NotificationRevealer(SlideRevealer): self._is_closing = False # Enhanced swipe detection variables for Android-style animation - self._drag_start_x = 0 self._drag_start_y = 0 + self._drag_start_x = 0 self._is_dragging = False self._swipe_threshold = 80 # Distance to trigger auto-dismiss - self._swipe_velocity_threshold = 150 # Velocity to trigger dismiss even on shorter swipes + self._swipe_velocity_threshold = ( + 150 # Velocity to trigger dismiss even on shorter swipes + ) self._swipe_in_progress = False self._current_offset = 0 self._last_drag_time = 0 self._drag_velocity = 0 self._spring_back_duration = 200 # Duration for spring-back animation self._dismiss_threshold = 0.3 # Dismiss if swiped 30% of width - + # Animation state self._animation_in_progress = False self._spring_timer_id = None @@ -324,15 +315,10 @@ class NotificationRevealer(SlideRevealer): child=self.notif_box, ) - self.event_box.connect("button-press-event", self._on_button_press) - self.event_box.connect("button-release-event", self._on_button_release) - self.event_box.connect("motion-notify-event", self._on_motion) - self.event_box.connect("realize", self._on_realize) - super().__init__( child=self.event_box, direction="right", - duration=350, # Optimized duration for macOS-like feel + duration=600, # Optimized duration for macOS-like feel ) smooth_revealer_animation(self) @@ -340,90 +326,7 @@ class NotificationRevealer(SlideRevealer): # Connect our own handler that manages the slide animation self.notification.connect("closed", self.on_resolved) - def _on_realize(self, widget): - """Setup CSS provider when widget is realized""" - try: - self._css_provider = Gtk.CssProvider() - context = widget.get_style_context() - context.add_provider(self._css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) - except Exception as e: - logger.warning(f"Could not setup CSS provider for notification animation: {e}") - - def _apply_transform(self, offset_x, opacity=1.0, scale=1.0): - """Apply transform to the notification widget for smooth animation""" - if not self.event_box or not self.event_box.get_realized(): - return - - # Clamp values - offset_x = max(0, offset_x) # Only allow rightward movement - opacity = max(0.1, min(1.0, opacity)) # Keep some visibility - scale = max(0.8, min(1.0, scale)) # Subtle scale effect - - try: - if self._css_provider: - # Apply CSS transform using the provider - css_data = f""" - * {{ - margin-left: {int(offset_x)}px; - opacity: {opacity}; - transform: scale({scale}); - }} - """ - self._css_provider.load_from_data(css_data.encode('utf-8')) - else: - # Fallback: use widget margins for offset - current_margin = self.event_box.get_margin_start() - if current_margin != int(offset_x): - self.event_box.set_margin_start(int(offset_x)) - - # Set opacity directly on the widget - self.event_box.set_opacity(opacity) - - except Exception as e: - # Final fallback: just use margin - try: - self.event_box.set_margin_start(int(offset_x)) - self.event_box.set_opacity(opacity) - except: - pass - - def _animate_spring_back(self, start_offset, target_offset=0, duration=None): - """Animate the notification springing back to its original position""" - if duration is None: - duration = self._spring_back_duration - - if self._spring_timer_id: - GLib.source_remove(self._spring_timer_id) - - start_time = GLib.get_monotonic_time() / 1000 - offset_diff = target_offset - start_offset - - def animate_step(): - current_time = GLib.get_monotonic_time() / 1000 - elapsed = current_time - start_time - progress = min(1.0, elapsed / duration) - - # Use easing function for spring-like animation - eased_progress = self._ease_out_back(progress) - current_offset = start_offset + (offset_diff * eased_progress) - - # Calculate opacity and scale based on offset - max_offset = NOTIFICATION_WIDTH * 0.8 - offset_ratio = abs(current_offset) / max_offset - opacity = 1.0 - (offset_ratio * 0.3) # Fade slightly - scale = 1.0 - (offset_ratio * 0.05) # Slight scale down - - self._apply_transform(current_offset, opacity, scale) - - if progress >= 1.0: - self._animation_in_progress = False - self._current_offset = target_offset - return False - - return True - self._animation_in_progress = True - self._spring_timer_id = GLib.timeout_add(16, animate_step) # ~60fps def _ease_out_back(self, t): """Easing function for spring-back animation""" @@ -435,28 +338,30 @@ class NotificationRevealer(SlideRevealer): """Animate the notification sliding out completely - now much faster""" target_offset = NOTIFICATION_WIDTH + 50 # Slide completely out of view duration = 120 # Much faster dismiss animation (was 250) - + if self._spring_timer_id: GLib.source_remove(self._spring_timer_id) - + start_time = GLib.get_monotonic_time() / 1000 offset_diff = target_offset - start_offset - + def animate_step(): current_time = GLib.get_monotonic_time() / 1000 elapsed = current_time - start_time progress = min(1.0, elapsed / duration) - + # Use faster easing for snappier exit - eased_progress = progress * progress # Quadratic ease out - faster than cubic + eased_progress = ( + progress * progress + ) # Quadratic ease out - faster than cubic current_offset = start_offset + (offset_diff * eased_progress) - + # Fade out and scale down during dismiss opacity = 1.0 - (progress * 1.2) # Faster fade out - scale = 1.0 - (progress * 0.15) # Slightly more scale change - + scale = 1.0 - (progress * 0.15) # Slightly more scale change + self._apply_transform(current_offset, opacity, scale) - + if progress >= 1.0: # Trigger the actual notification dismissal immediately try: @@ -464,22 +369,24 @@ class NotificationRevealer(SlideRevealer): except: pass return False - + return True - + self._animation_in_progress = True - self._spring_timer_id = GLib.timeout_add(12, animate_step) # Higher fps for smoother animation + self._spring_timer_id = GLib.timeout_add( + 12, animate_step + ) # Higher fps for smoother animation def _calculate_drag_velocity(self, current_x): """Calculate the velocity of the drag gesture""" current_time = GLib.get_monotonic_time() / 1000 - + if self._last_drag_time > 0: time_diff = current_time - self._last_drag_time if time_diff > 0: distance_diff = current_x - self._drag_start_x - self._current_offset self._drag_velocity = abs(distance_diff / time_diff) - + self._last_drag_time = current_time def _on_animation_complete(self, is_hiding=False): @@ -523,109 +430,6 @@ class NotificationRevealer(SlideRevealer): timeout_duration = 80 if self._swipe_in_progress else (self.duration + 30) GLib.timeout_add(timeout_duration, lambda: self._on_animation_complete(True)) - def _on_button_press(self, _widget, event): - if event.button == 1: - self._drag_start_x = event.x - self._drag_start_y = event.y - self._is_dragging = True - self._swipe_in_progress = False - self._current_offset = 0 - self._last_drag_time = GLib.get_monotonic_time() / 1000 - self._drag_velocity = 0 - - # Stop any ongoing animations - if self._spring_timer_id: - GLib.source_remove(self._spring_timer_id) - self._animation_in_progress = False - return False - - def _on_button_release(self, _widget, event): - if event.button == 3: # Right click - try: - if self.parent_window: - self.parent_window.clear_notification_queue() - # Also dismiss current notification - self.notification.close("dismissed-by-user") - except: - pass # Ignore errors - return True - - elif self._is_dragging and event.button == 1: - self._is_dragging = False - - # Calculate final swipe metrics - dx = event.x - self._drag_start_x - dy = abs(event.y - self._drag_start_y) - - # Calculate dismiss threshold based on notification width - dismiss_distance = NOTIFICATION_WIDTH * self._dismiss_threshold - - # Determine if we should dismiss or spring back - should_dismiss = ( - (dx > dismiss_distance and dy < 60) or # Dragged far enough - (dx > 30 and self._drag_velocity > self._swipe_velocity_threshold and dy < 60) # Fast swipe - ) - - if should_dismiss: - try: - self._swipe_in_progress = True - # For fast dismissal, if already dragged far enough, close immediately - if dx > NOTIFICATION_WIDTH * 0.6: - # Skip animation and close immediately for very large swipes - self.notification.close("dismissed-by-user") - else: - # Animate the dismiss for smaller swipes - self._animate_dismiss(self._current_offset) - logger.debug(f"Notification dismissed by swipe: dx={dx}, velocity={self._drag_velocity}") - except Exception as e: - logger.error(f"Error dismissing notification by swipe: {e}") - else: - # Spring back to original position - if abs(self._current_offset) > 5: # Only animate if there's noticeable displacement - self._animate_spring_back(self._current_offset, 0) - else: - self._apply_transform(0, 1.0, 1.0) - self._current_offset = 0 - return False - - def _on_motion(self, _widget, event): - if self._is_dragging and not self._animation_in_progress: - # Calculate current swipe distance - dx = event.x - self._drag_start_x - dy = abs(event.y - self._drag_start_y) - - # Only respond to primarily horizontal gestures - if dy < 60: # Vertical tolerance - # Calculate velocity for smooth interaction - self._calculate_drag_velocity(event.x) - - # Apply real-time transform - only allow rightward movement - if dx > 0: - self._current_offset = dx - - # Calculate visual feedback based on drag distance - max_offset = NOTIFICATION_WIDTH * 0.8 - offset_ratio = min(1.0, dx / max_offset) - - # Apply diminishing returns for large swipes - adjusted_offset = dx * (1.0 - offset_ratio * 0.3) - - # Calculate opacity and scale - opacity = 1.0 - (offset_ratio * 0.4) # Fade as it's dragged - scale = 1.0 - (offset_ratio * 0.1) # Slight scale down - - self._apply_transform(adjusted_offset, opacity, scale) - - # Visual indication of dismiss threshold - if dx > NOTIFICATION_WIDTH * self._dismiss_threshold: - # Could add visual cue here (like changing color) - pass - else: - # Reset to original position if dragging left - self._current_offset = 0 - self._apply_transform(0, 1.0, 1.0) - return False - def destroy(self): # Clean up CSS provider and timers if self._spring_timer_id: