Added Close Button instead of swipe
This commit is contained in:
1 parent
fd1b77b59f
commit
00e6ddb1a3
1 file changed
+29
-225
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user