From 232d9d33bd958658d03b144f3e7b704517a8860b Mon Sep 17 00:00:00 2001 From: tr1x_em Date: Sat, 2 Aug 2025 23:17:42 +0530 Subject: [PATCH] feat: redesign launcher to mimic macOS Spotlight style --- 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); }