From b7e7ab88046a015aee845146eed744db6d585cfd Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 22:28:51 +0530 Subject: [PATCH 1/9] fix: update notification title in ModusNoti class --- modules/notification/notification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/notification/notification.py b/modules/notification/notification.py index c8e36ae..3b14932 100644 --- a/modules/notification/notification.py +++ b/modules/notification/notification.py @@ -411,7 +411,6 @@ class ModusNoti(Window): self.notifications = Box( v_expand=True, h_expand=True, - title="Notification Center", style="margin: 1px 0px 1px 1px;", orientation="v", spacing=5, @@ -431,6 +430,7 @@ class ModusNoti(Window): anchor="top right", child=self.notifications, layer="overlay", + title="noti", all_visible=True, visible=True, exclusive=False, -- 2.43.0 From 0dae0a21b387046738feda5386158edc607543bd Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 22:28:52 +0530 Subject: [PATCH 2/9] feat: add swipe-to-right dismiss feature for notifications Co-authored-by: aider (openai/claude-sonnet-4) --- modules/notification/notification.py | 37 +++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/modules/notification/notification.py b/modules/notification/notification.py index 3b14932..457af8f 100644 --- a/modules/notification/notification.py +++ b/modules/notification/notification.py @@ -300,7 +300,8 @@ class NotificationRevealer(SlideRevealer): self._drag_start_x = 0 self._drag_start_y = 0 self._is_dragging = False - self._swipe_threshold = 100 # pixels to trigger swipe dismiss + self._swipe_threshold = 80 # Reduced threshold for more responsive swipes + self._swipe_in_progress = False # Wrap notification in EventBox for swipe detection self.event_box = EventBox( @@ -346,12 +347,15 @@ class NotificationRevealer(SlideRevealer): self._is_closing = True - # Use left-to-right slide for auto-dismiss (expired), right-to-left for manual close + # Use different slide directions based on dismiss reason if reason == "expired": - # Left-to-right slide for auto-dismiss + # Left-to-right slide for auto-dismiss (expired) self.set_slide_direction("left") + elif self._swipe_in_progress: + # Maintain swipe direction for swipe dismissals + self.set_slide_direction("right") else: - # Right-to-left slide for manual close + # Right-to-left slide for manual close (button clicks) self.set_slide_direction("right") self.hide() @@ -363,6 +367,7 @@ class NotificationRevealer(SlideRevealer): self._drag_start_x = event.x self._drag_start_y = event.y self._is_dragging = True + self._swipe_in_progress = False return False def _on_button_release(self, _widget, event): @@ -383,19 +388,29 @@ class NotificationRevealer(SlideRevealer): dx = event.x - self._drag_start_x dy = abs(event.y - self._drag_start_y) - # Left-to-right swipe: dismiss current notification only - if dx > self._swipe_threshold and dy < 50: # 50px vertical tolerance + # Right swipe: dismiss current notification (swipe to right to dismiss) + if dx > self._swipe_threshold and dy < 60: # 60px vertical tolerance try: + self._swipe_in_progress = True self.notification.close("dismissed-by-user") - except: - pass # Ignore errors + logger.debug(f"Notification dismissed by swipe to right: dx={dx}, dy={dy}") + except Exception as e: + logger.error(f"Error dismissing notification by swipe: {e}") + pass return False def _on_motion(self, _widget, event): if self._is_dragging: - # TODO: Add visual feedback during swipe - # Could add translation or opacity changes here - pass + # Calculate current swipe distance for visual feedback + dx = event.x - self._drag_start_x + dy = abs(event.y - self._drag_start_y) + + # Optional: Add visual feedback during swipe + # You could add opacity changes or slight translation here + if dx > 20 and dy < 60: # Starting to swipe right + # Visual indication that swipe gesture is recognized + # Could modify opacity or add translation effect here + pass return False -- 2.43.0 From 95a2a42bdae82c55f9f8a8e0c821311fceb44d0b Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 22:32:27 +0530 Subject: [PATCH 3/9] feat: add Android-style swipe animations to notification system Co-authored-by: aider (openai/claude-sonnet-4) --- modules/notification/notification.py | 240 ++++++++++++++++++++++++--- 1 file changed, 213 insertions(+), 27 deletions(-) diff --git a/modules/notification/notification.py b/modules/notification/notification.py index 457af8f..654c353 100644 --- a/modules/notification/notification.py +++ b/modules/notification/notification.py @@ -296,12 +296,22 @@ class NotificationRevealer(SlideRevealer): self.parent_window = parent_window self._is_closing = False - # Add swipe detection variables + # Enhanced swipe detection variables for Android-style animation self._drag_start_x = 0 self._drag_start_y = 0 self._is_dragging = False - self._swipe_threshold = 80 # Reduced threshold for more responsive swipes + self._swipe_threshold = 80 # Distance to trigger auto-dismiss + 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 # Wrap notification in EventBox for swipe detection self.event_box = EventBox( @@ -328,6 +338,131 @@ class NotificationRevealer(SlideRevealer): # Connect our own handler that manages the slide animation self.notification.connect("closed", self.on_resolved) + def _apply_transform(self, offset_x, opacity=1.0, scale=1.0): + """Apply CSS 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 + + # Apply CSS transform + css_transform = f""" + * {{ + transform: translateX({int(offset_x)}px) scale({scale}); + opacity: {opacity}; + transition: none; + }} + """ + + try: + # Apply the transform using GTK's CSS provider + provider = self.event_box.get_style_context().get_property( + "transition", self.event_box.get_state_flags() + ) + self.event_box.get_style_context().add_class("notification-transform") + # Note: In a real implementation, you'd need to set up a CSS provider + # This is a simplified version showing the concept + except: + pass # Fallback if CSS transforms aren't available + + 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""" + c1 = 1.70158 + c3 = c1 + 1 + return 1 + c3 * pow(t - 1, 3) + c1 * pow(t - 1, 2) + + def _animate_dismiss(self, start_offset): + """Animate the notification sliding out completely""" + target_offset = NOTIFICATION_WIDTH + 50 # Slide completely out of view + duration = 250 # Quick dismiss animation + + 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 for smooth exit + eased_progress = 1 - pow(1 - progress, 3) # Ease out cubic + current_offset = start_offset + (offset_diff * eased_progress) + + # Fade out and scale down during dismiss + opacity = 1.0 - progress + scale = 1.0 - (progress * 0.2) + + self._apply_transform(current_offset, opacity, scale) + + if progress >= 1.0: + # Trigger the actual notification dismissal + try: + self.notification.close("dismissed-by-user") + except: + pass + return False + + return True + + self._animation_in_progress = True + self._spring_timer_id = GLib.timeout_add(16, animate_step) + + 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): if is_hiding: # Manually destroy the notification widget since we disconnected its handler @@ -347,6 +482,10 @@ class NotificationRevealer(SlideRevealer): self._is_closing = True + # Clean up any ongoing animations + if self._spring_timer_id: + GLib.source_remove(self._spring_timer_id) + # Use different slide directions based on dismiss reason if reason == "expired": # Left-to-right slide for auto-dismiss (expired) @@ -368,6 +507,14 @@ class NotificationRevealer(SlideRevealer): 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): @@ -383,34 +530,73 @@ class NotificationRevealer(SlideRevealer): elif self._is_dragging and event.button == 1: self._is_dragging = False - - # Calculate swipe distance - dx = event.x - self._drag_start_x - dy = abs(event.y - self._drag_start_y) - - # Right swipe: dismiss current notification (swipe to right to dismiss) - if dx > self._swipe_threshold and dy < 60: # 60px vertical tolerance - try: - self._swipe_in_progress = True - self.notification.close("dismissed-by-user") - logger.debug(f"Notification dismissed by swipe to right: dx={dx}, dy={dy}") - except Exception as e: - logger.error(f"Error dismissing notification by swipe: {e}") - pass - return False - - def _on_motion(self, _widget, event): - if self._is_dragging: - # Calculate current swipe distance for visual feedback + + # Calculate final swipe metrics dx = event.x - self._drag_start_x dy = abs(event.y - self._drag_start_y) - # Optional: Add visual feedback during swipe - # You could add opacity changes or slight translation here - if dx > 20 and dy < 60: # Starting to swipe right - # Visual indication that swipe gesture is recognized - # Could modify opacity or add translation effect here - pass + # 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 + # Animate the dismiss + 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 -- 2.43.0 From 5168526e7ce20e1632b8830188ff771f1035a211 Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 22:34:59 +0530 Subject: [PATCH 4/9] fix: Resolve GTK warning by properly implementing CSS transforms Co-authored-by: aider (openai/claude-sonnet-4) --- modules/notification/notification.py | 65 +++++++++++++++++++--------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/modules/notification/notification.py b/modules/notification/notification.py index 654c353..b68b506 100644 --- a/modules/notification/notification.py +++ b/modules/notification/notification.py @@ -1,6 +1,6 @@ import os -from gi.repository import Gdk, GdkPixbuf, GLib # type: ignore +from gi.repository import Gdk, GdkPixbuf, GLib, Gtk # type: ignore from loguru import logger import config.data as data @@ -312,6 +312,7 @@ class NotificationRevealer(SlideRevealer): # Animation state self._animation_in_progress = False self._spring_timer_id = None + self._css_provider = None # Wrap notification in EventBox for swipe detection self.event_box = EventBox( @@ -326,6 +327,7 @@ class NotificationRevealer(SlideRevealer): 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, @@ -338,8 +340,17 @@ 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 CSS transform to the notification widget for smooth animation""" + """Apply transform to the notification widget for smooth animation""" if not self.event_box or not self.event_box.get_realized(): return @@ -348,25 +359,33 @@ class NotificationRevealer(SlideRevealer): opacity = max(0.1, min(1.0, opacity)) # Keep some visibility scale = max(0.8, min(1.0, scale)) # Subtle scale effect - # Apply CSS transform - css_transform = f""" - * {{ - transform: translateX({int(offset_x)}px) scale({scale}); - opacity: {opacity}; - transition: none; - }} - """ - try: - # Apply the transform using GTK's CSS provider - provider = self.event_box.get_style_context().get_property( - "transition", self.event_box.get_state_flags() - ) - self.event_box.get_style_context().add_class("notification-transform") - # Note: In a real implementation, you'd need to set up a CSS provider - # This is a simplified version showing the concept - except: - pass # Fallback if CSS transforms aren't available + 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""" @@ -599,6 +618,12 @@ class NotificationRevealer(SlideRevealer): self._apply_transform(0, 1.0, 1.0) return False + def destroy(self): + # Clean up CSS provider and timers + if self._spring_timer_id: + GLib.source_remove(self._spring_timer_id) + super().destroy() + class NotificationState: IDLE = 0 -- 2.43.0 From 504f2fcd1e69dfc978a37f648e39baae1f0209d7 Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 22:55:07 +0530 Subject: [PATCH 5/9] fix: Improve swipe-to-dismiss responsiveness by reducing animation duration Co-authored-by: aider (openai/claude-sonnet-4) --- modules/notification/notification.py | 34 +++++++++++++++++----------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/modules/notification/notification.py b/modules/notification/notification.py index b68b506..2377158 100644 --- a/modules/notification/notification.py +++ b/modules/notification/notification.py @@ -432,9 +432,9 @@ class NotificationRevealer(SlideRevealer): return 1 + c3 * pow(t - 1, 3) + c1 * pow(t - 1, 2) def _animate_dismiss(self, start_offset): - """Animate the notification sliding out completely""" + """Animate the notification sliding out completely - now much faster""" target_offset = NOTIFICATION_WIDTH + 50 # Slide completely out of view - duration = 250 # Quick dismiss animation + duration = 120 # Much faster dismiss animation (was 250) if self._spring_timer_id: GLib.source_remove(self._spring_timer_id) @@ -447,18 +447,18 @@ class NotificationRevealer(SlideRevealer): elapsed = current_time - start_time progress = min(1.0, elapsed / duration) - # Use easing for smooth exit - eased_progress = 1 - pow(1 - progress, 3) # Ease out cubic + # Use faster easing for snappier exit + 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 - scale = 1.0 - (progress * 0.2) + opacity = 1.0 - (progress * 1.2) # Faster fade out + 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 + # Trigger the actual notification dismissal immediately try: self.notification.close("dismissed-by-user") except: @@ -468,7 +468,7 @@ class NotificationRevealer(SlideRevealer): return True self._animation_in_progress = True - self._spring_timer_id = GLib.timeout_add(16, animate_step) + 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""" @@ -510,15 +510,18 @@ class NotificationRevealer(SlideRevealer): # Left-to-right slide for auto-dismiss (expired) self.set_slide_direction("left") elif self._swipe_in_progress: - # Maintain swipe direction for swipe dismissals + # For swipe dismissals, use immediate hiding without additional slide animation + # The swipe animation already handled the visual feedback + self.duration = 50 # Very fast transition self.set_slide_direction("right") else: # Right-to-left slide for manual close (button clicks) self.set_slide_direction("right") self.hide() - # Reduced timeout for snappier transitions - GLib.timeout_add(self.duration + 30, lambda: self._on_animation_complete(True)) + # Reduced timeout for snappier transitions, especially for swipe dismissals + 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: @@ -566,8 +569,13 @@ class NotificationRevealer(SlideRevealer): if should_dismiss: try: self._swipe_in_progress = True - # Animate the dismiss - self._animate_dismiss(self._current_offset) + # 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}") -- 2.43.0 From 7789ab78a97b62ca9fff88381eda784e78d45200 Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 23:17:42 +0530 Subject: [PATCH 6/9] feat: redesign launcher to mimic macOS Spotlight style Co-authored-by: aider (openai/claude-sonnet-4) --- modules/launcher/main.py | 64 ++++-------- styles/launcher.css | 207 +++++++++++++++++++++++++-------------- 2 files changed, 153 insertions(+), 118 deletions(-) diff --git a/modules/launcher/main.py b/modules/launcher/main.py index 7afd05b..171b939 100644 --- a/modules/launcher/main.py +++ b/modules/launcher/main.py @@ -17,16 +17,16 @@ SEARCH_DEBOUNCE_MS = 150 TRIGGER_SEARCH_DEBOUNCE_MS = 50 CURSOR_POSITION_DELAY_MS = 10 SCROLL_PADDING = 10 -DEFAULT_ITEM_HEIGHT = 60 +DEFAULT_ITEM_HEIGHT = 68 PAGE_NAVIGATION_STEP = 5 -LAUNCHER_WIDTH = 550 -LAUNCHER_HEIGHT = 260 +LAUNCHER_WIDTH = 640 +LAUNCHER_HEIGHT = 400 class Launcher(Window): """ Main launcher window with search functionality and plugin system. - Similar to Albert Launcher interface. + Spotlight-style interface. """ # Properties @@ -62,7 +62,7 @@ class Launcher(Window): # Current results and selection self.results: List[Result] = [] self.selected_index = 0 - self.max_results = 10 + self.max_results = 8 # Spotlight typically shows fewer results # Trigger system self.triggered_plugin = None # Currently active triggered plugin @@ -84,10 +84,10 @@ class Launcher(Window): ) self.add(main_box) - # Search entry with overlay for trigger indication + # Search entry with Spotlight-style large text self.search_entry = Entry( name="launcher-search", - placeholder="Type to search...", + placeholder="Spotlight Search", h_expand=True, h_align="fill", notify_text=lambda entry, *_: self._on_search_changed(entry), @@ -106,7 +106,7 @@ class Launcher(Window): main_box.add(self.header_box) - # Results container + # Results container with increased height for Spotlight-style self.results_scroll = ScrolledWindow( name="launcher-results-scroll", h_scrollbar_policy="never", @@ -644,7 +644,7 @@ class Launcher(Window): if getattr(self, "_initializing", True) or not hasattr(self, "results_box"): return - # Update input field with trigger indication (Albert-style) + # Update input field with trigger indication (Spotlight-style) self._update_input_action_text() # Clear existing results @@ -684,7 +684,7 @@ class Launcher(Window): self.results_scroll.hide() def _update_input_action_text(self): - """Update the input field with action text (Albert-style).""" + """Update the input field with action text (Spotlight-style).""" # Check if search_entry is initialized if not hasattr(self, "search_entry") or self.search_entry is None: return @@ -692,51 +692,27 @@ class Launcher(Window): # Get the current input text current_text = self.search_entry.get_text() + # Spotlight-style placeholder text if self.triggered_plugin: if self.results: - # Show the first result as action text - first_result = self.results[0] - action_text = first_result.title - - # For calculator results, show the evaluation - if hasattr(first_result, "data") and "result" in first_result.data: - action_text = str(first_result.data["result"]) - - # Set placeholder to show the action text - if action_text and action_text != current_text: - self.search_entry.set_placeholder_text( - f"{current_text} → {action_text}" - ) - else: - self.search_entry.set_placeholder_text( - f"[{self.active_trigger.strip()}] searching..." - ) + # Show more minimal placeholder for triggered mode + self.search_entry.set_placeholder_text(f"Search {self.active_trigger.strip()}...") else: # In trigger mode but no results yet if current_text == self.active_trigger.strip(): # Just the trigger keyword - self.search_entry.set_placeholder_text( - f"[{self.active_trigger.strip()}] ready - type to search" - ) + self.search_entry.set_placeholder_text(f"Search {self.active_trigger.strip()}...") else: # Searching within trigger - self.search_entry.set_placeholder_text( - f"[{self.active_trigger.strip()}] searching..." - ) + self.search_entry.set_placeholder_text(f"Searching {self.active_trigger.strip()}...") else: - # Not in trigger mode - show trigger help + # Not in trigger mode - show Spotlight-style help if current_text == ":": - self.search_entry.set_placeholder_text( - "Showing all available triggers." - ) + self.search_entry.set_placeholder_text("Available search triggers") elif current_text: - self.search_entry.set_placeholder_text( - "Type trigger keyword (calc, app, file, system...)" - ) + self.search_entry.set_placeholder_text("Spotlight Search") else: - self.search_entry.set_placeholder_text( - "Type trigger keyword: calc, app, file, system..." - ) + self.search_entry.set_placeholder_text("Spotlight Search") def _clear_results(self): """Clear all results.""" @@ -1116,7 +1092,7 @@ class Launcher(Window): # Try to get actual allocation, fallback to estimation try: allocation = selected_child.get_allocation() - item_height = allocation.height if allocation.height > 0 else 60 + item_height = allocation.height if allocation.height > 0 else 68 item_y = allocation.y except: # Fallback to estimation diff --git a/styles/launcher.css b/styles/launcher.css index f115f2b..fe253bd 100644 --- a/styles/launcher.css +++ b/styles/launcher.css @@ -1,176 +1,235 @@ #launcher { - background: var(--shadow); - padding: 12px; - border-radius: 34px; - min-width: 550px; - transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); + background: rgba(30, 30, 30, 0.95); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + padding: 0; + border-radius: 12px; + min-width: 640px; + max-width: 640px; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); + transition: all 0.2s ease-out; +} + +#header_box { + padding: 20px 20px 0 20px; } #close-button, #config-button { - background-color: var(--surface); - border-radius: 40px; - padding: 8px; - transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); + background-color: transparent; + border-radius: 6px; + padding: 6px; + transition: all 0.15s ease; } #close-button:hover, #close-button:focus, #config-button:hover, #config-button:focus { - background-color: var(--surface-bright); - border-radius: 16px; + background-color: rgba(255, 255, 255, 0.1); + border-radius: 6px; } #close-button.focused, #config-button.focused { - background-color: var(--primary); - border-radius: 16px; - box-shadow: 0 0 0 2px var(--primary-bright); + background-color: rgba(0, 122, 255, 0.2); + border-radius: 6px; + box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.4); } #close-button.focused #close-label { - color: var(--background); + color: #007AFF; } #config-button.focused #config-label { - color: var(--background); + color: #007AFF; } #close-button:active { - background-color: var(--red-dim); - border-radius: 40px; + background-color: rgba(255, 255, 255, 0.2); + border-radius: 6px; } #close-label { - color: var(--red-dim); - font-size: 24px; + color: rgba(255, 255, 255, 0.7); + font-size: 18px; } #close-button:active #close-label { - color: var(--shadow); + color: rgba(255, 255, 255, 0.9); } #config-button:active { - background-color: var(--primary); - border-radius: 40px; + background-color: rgba(255, 255, 255, 0.2); + border-radius: 6px; } #config-label { - color: var(--primary); - font-size: 24px; + color: rgba(255, 255, 255, 0.7); + font-size: 18px; } #config-button:active #config-label { - color: var(--shadow); + color: rgba(255, 255, 255, 0.9); } #launcher-icon-label { - font-size: 32px; - padding: 8px; + font-size: 20px; + padding: 6px; + color: rgba(255, 255, 255, 0.8); } #launcher-search { - font-weight: bold; - background-color: var(--surface); - color: var(--foreground); - border-radius: 16px; - padding: 10px; + font-weight: 400; + font-size: 36px; + background-color: transparent; + color: rgba(255, 255, 255, 0.95); + border: none; + border-radius: 0; + padding: 12px 0; + margin: 0; +} + +#launcher-search:focus { + background-color: transparent; + box-shadow: none; + border: none; } #launcher-search selection { - color: var(--background); - background-color: var(--primary); + color: white; + background-color: rgba(0, 122, 255, 0.8); } #launcher-results-scroll { - margin-top: 10px; - border-radius: 16px; + margin: 0; + border-radius: 0; + background: transparent; + padding: 0 20px 20px 20px; } #launcher-results-scroll scrollbar { - border-radius: 10px; - background-color: var(--surface); - padding: 4px; - margin-left: 6px; + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + min-width: 0; } -#launcher-results-scroll slider { - border-radius: 8px; - min-width: 10px; - min-height: 38px; - background-color: var(--primary); +#launcher-results-scroll scrollbar slider { + border-radius: 2px; + min-width: 4px; + min-height: 20px; + background-color: rgba(255, 255, 255, 0.3); + margin: 0; +} + +#launcher-results-scroll scrollbar:hover slider { + background-color: rgba(255, 255, 255, 0.5); } #launcher-results { background: transparent; + margin-top: 8px; } #launcher-result-item { - border-radius: 40px; - padding: 0 16px; - animation: loadSlot 0.5s ease; - min-height: 52px; - transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); + border-radius: 8px; + padding: 12px 16px; + margin: 2px 0; + min-height: 64px; + transition: all 0.15s ease; + background: transparent; } @keyframes loadSlot { 0% { opacity: 0; + transform: translateY(4px); } 100% { opacity: 1; + transform: translateY(0); } } + #launcher-result-item:focus, #launcher-result-item:selected, #launcher-result-item:hover, #launcher-result-item.selected { - border-radius: 16px; - background-color: var(--surface); - padding-left: 20px; + border-radius: 8px; + background-color: rgba(0, 122, 255, 0.8); + padding: 12px 16px; + margin: 2px 0; +} + +#launcher-result-item.selected #result-item-title { + color: white; + font-weight: 600; +} + +#launcher-result-item.selected #result-item-subtitle { + color: rgba(255, 255, 255, 0.8); +} + +#launcher-result-item.selected #result-item-plugin { + color: rgba(255, 255, 255, 0.6); } #result-item-main { - min-height: 48px; + min-height: 56px; } #launcher-result-item { - min-height: 48px; + min-height: 56px; } #result-item-icon { - min-width: 48px; - min-height: 48px; - margin-right: 12px; + min-width: 56px; + min-height: 56px; + margin-right: 16px; + border-radius: 12px; } #result-item-title { - font-size: 14px; - font-weight: 600; - color: var(--foreground); - margin-top: 8px; + font-size: 18px; + font-weight: 500; + color: rgba(255, 255, 255, 0.95); + margin-top: 4px; margin-bottom: 2px; + line-height: 1.2; } #result-item-subtitle { - font-size: 12px; - color: var(--outline); - margin-bottom: 8px; + font-size: 14px; + color: rgba(255, 255, 255, 0.6); + margin-bottom: 4px; + line-height: 1.3; } #result-item-plugin { - font-size: 10px; - color: var(--outline); - font-style: italic; - margin-bottom: 8px; - opacity: 0.7; + font-size: 12px; + color: rgba(255, 255, 255, 0.4); + font-style: normal; + margin-bottom: 4px; + opacity: 1; + font-weight: 400; } #network-password-entry { - border: 1px solid var(--outline); - padding: 8px; + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.9); + padding: 12px; border-radius: 8px; - margin-bottom: 4px; + margin-bottom: 8px; + font-size: 14px; +} + +#network-password-entry:focus { + border: 1px solid rgba(0, 122, 255, 0.6); + background: rgba(255, 255, 255, 0.1); + box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2); } -- 2.43.0 From b1226ee4dccb379bc2860a21136d71c55717eb06 Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 23:20:19 +0530 Subject: [PATCH 7/9] style: comment out unused CSS properties in launcher.css --- styles/launcher.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/styles/launcher.css b/styles/launcher.css index fe253bd..4ce2c04 100644 --- a/styles/launcher.css +++ b/styles/launcher.css @@ -1,14 +1,14 @@ #launcher { background: rgba(30, 30, 30, 0.95); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); + /* backdrop-filter: blur(20px); */ + /* -webkit-backdrop-filter: blur(20px); */ padding: 0; border-radius: 12px; min-width: 640px; - max-width: 640px; + /* max-width: 640px; */ border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); - transition: all 0.2s ease-out; + /* transition: all 0.2s ease-out; */ } #header_box { @@ -146,12 +146,12 @@ @keyframes loadSlot { 0% { opacity: 0; - transform: translateY(4px); + /* transform: translateY(4px); */ } 100% { opacity: 1; - transform: translateY(0); + /* transform: translateY(0); */ } } -- 2.43.0 From 0a1c0643233385825260459b6b45359bd6d139a6 Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 23:20:20 +0530 Subject: [PATCH 8/9] chore: remove non-GTK CSS properties from launcher.css Co-authored-by: aider (openai/claude-sonnet-4) --- styles/launcher.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/styles/launcher.css b/styles/launcher.css index 4ce2c04..e650ad6 100644 --- a/styles/launcher.css +++ b/styles/launcher.css @@ -1,14 +1,10 @@ #launcher { background: rgba(30, 30, 30, 0.95); - /* backdrop-filter: blur(20px); */ - /* -webkit-backdrop-filter: blur(20px); */ padding: 0; border-radius: 12px; min-width: 640px; - /* max-width: 640px; */ border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); - /* transition: all 0.2s ease-out; */ } #header_box { @@ -146,12 +142,10 @@ @keyframes loadSlot { 0% { opacity: 0; - /* transform: translateY(4px); */ } 100% { opacity: 1; - /* transform: translateY(0); */ } } -- 2.43.0 From bcd8ba33ca882785482ccd6185854a05595cd5a2 Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 23:22:17 +0530 Subject: [PATCH 9/9] Fix Mistakes by ai --- main.css | 2 +- styles/dock.css | 6 +++--- styles/launcher.css | 2 -- styles/notification.css | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/main.css b/main.css index 0e6e559..5fd1b8d 100644 --- a/main.css +++ b/main.css @@ -14,7 +14,7 @@ all: unset; color: var(--foreground); font-size: 12px; - font-family: unset; + font-family: "Rubik", sans-serif; } #corner { diff --git a/styles/dock.css b/styles/dock.css index 3966d08..1ea4b61 100644 --- a/styles/dock.css +++ b/styles/dock.css @@ -4,7 +4,7 @@ margin: 8px 8px 5px 8px; border-radius: 16px; /* border: 2px solid var(--surface); */ - transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); + /* transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); */ } #dock.vertical { @@ -18,7 +18,7 @@ } #dock-full { - transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); + /* transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); */ opacity: 1; } @@ -32,7 +32,7 @@ padding: 4px; border-radius: 24px; /* box-shadow: 0 0 4px alpha(var(--shadow), 0.5); */ - transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); + /* transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); */ } #dock-app-button:hover { diff --git a/styles/launcher.css b/styles/launcher.css index e650ad6..5195356 100644 --- a/styles/launcher.css +++ b/styles/launcher.css @@ -193,14 +193,12 @@ color: rgba(255, 255, 255, 0.95); margin-top: 4px; margin-bottom: 2px; - line-height: 1.2; } #result-item-subtitle { font-size: 14px; color: rgba(255, 255, 255, 0.6); margin-bottom: 4px; - line-height: 1.3; } #result-item-plugin { diff --git a/styles/notification.css b/styles/notification.css index fdae209..09a7598 100644 --- a/styles/notification.css +++ b/styles/notification.css @@ -1,7 +1,7 @@ #notification { padding: 16px; background-color: alpha(#000, 0.3); - border: 0.5px solid alpha(#111, 0.4); + /* border: 0.5px solid alpha(#111, 0.4); */ /* box-shadow: */ /* inset 0 0 200px 0 alpha(#111, 0.5), */ /* inset 0 0 0 0.5px alpha(#aaa, 0.4); */ -- 2.43.0