feat: redesign launcher to mimic macOS Spotlight style

This commit is contained in:
tr1x_em committed 2025-08-02 23:17:42 +05:30
1 parent 6bd4f2d93c
commit 232d9d33bd
2 files changed
+153 -118

No files matched your search

+20 -44
View File
@@ -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
+133 -74
View File
@@ -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);
}