feat(ui): add result highlighting and remove window shadow
Highlighting: - Dynamic plugin results (calculator, converter, websearch, filesearch) get a subtle accent left-border + background tint when auto-detected - Exact name matches (case-insensitive) are highlighted the same way - Exact match on apps gets a higher score boost (50k) than other providers (30k), so apps rank first when names match exactly Shadow: - Removed hardcoded box-shadow from all theme CSS files - Added --owlry-shadow variable in base.css (defaults to none) - Themes can opt into shadow via --owlry-shadow if desired CSS class: .owlry-result-highlight on ResultRow
This commit is contained in:
@@ -686,7 +686,18 @@ impl ProviderManager {
|
||||
base_score.map(|s| {
|
||||
let frecency_score = frecency.get_score_at(&item.id, now);
|
||||
let frecency_boost = (frecency_score * frecency_weight * 10.0) as i64;
|
||||
(item.clone(), s + frecency_boost)
|
||||
|
||||
// Exact name match bonus — apps get a higher boost
|
||||
let exact_match_boost = if item.name.eq_ignore_ascii_case(query) {
|
||||
match &item.provider {
|
||||
ProviderType::Application => 50_000,
|
||||
_ => 30_000,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
(item.clone(), s + frecency_boost + exact_match_boost)
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
background-color: var(--owlry-bg, @theme_bg_color);
|
||||
border-radius: var(--owlry-border-radius, 12px);
|
||||
border: 1px solid var(--owlry-border, @borders);
|
||||
box-shadow: var(--owlry-shadow, none);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
@@ -56,6 +57,16 @@
|
||||
color: var(--owlry-accent-bright, @theme_selected_fg_color);
|
||||
}
|
||||
|
||||
/* Highlighted result row (exact match or auto-detected plugin result) */
|
||||
.owlry-result-highlight {
|
||||
background-color: alpha(var(--owlry-accent, @theme_selected_bg_color), 0.08);
|
||||
border-left: 3px solid var(--owlry-accent, @theme_selected_bg_color);
|
||||
}
|
||||
|
||||
.owlry-result-highlight:selected {
|
||||
border-left: 3px solid var(--owlry-accent-bright, @theme_selected_fg_color);
|
||||
}
|
||||
|
||||
/* Result icon */
|
||||
.owlry-result-icon {
|
||||
color: var(--owlry-text, @theme_fg_color);
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(26, 27, 38, 0.95);
|
||||
border: 1px solid rgba(65, 72, 104, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(224, 175, 104, 0.1);
|
||||
}
|
||||
|
||||
/* Search entry */
|
||||
|
||||
@@ -42,6 +42,8 @@ struct LazyLoadState {
|
||||
all_results: Vec<LaunchItem>,
|
||||
/// Number of items currently displayed
|
||||
displayed_count: usize,
|
||||
/// The query that produced these results (for highlighting in lazy-loaded batches)
|
||||
query: String,
|
||||
}
|
||||
|
||||
/// Number of items to display initially and per batch
|
||||
@@ -528,7 +530,7 @@ impl MainWindow {
|
||||
}
|
||||
|
||||
for item in &actions {
|
||||
let row = ResultRow::new(item);
|
||||
let row = ResultRow::new(item, "");
|
||||
results_list.append(&row);
|
||||
}
|
||||
|
||||
@@ -610,7 +612,7 @@ impl MainWindow {
|
||||
}
|
||||
|
||||
for item in &filtered {
|
||||
let row = ResultRow::new(item);
|
||||
let row = ResultRow::new(item, "");
|
||||
results_list.append(&row);
|
||||
}
|
||||
|
||||
@@ -709,6 +711,7 @@ impl MainWindow {
|
||||
let results_list_cb = results_list.clone();
|
||||
let current_results_cb = current_results.clone();
|
||||
let lazy_state_cb = lazy_state.clone();
|
||||
let query_for_highlight = query_str.clone();
|
||||
|
||||
gtk4::glib::spawn_future_local(async move {
|
||||
if let Ok(result) = rx.await {
|
||||
@@ -726,7 +729,7 @@ impl MainWindow {
|
||||
INITIAL_RESULTS.min(items.len());
|
||||
|
||||
for item in items.iter().take(initial_count) {
|
||||
let row = ResultRow::new(item);
|
||||
let row = ResultRow::new(item, &query_for_highlight);
|
||||
results_list_cb.append(&row);
|
||||
}
|
||||
|
||||
@@ -741,6 +744,7 @@ impl MainWindow {
|
||||
let mut lazy = lazy_state_cb.borrow_mut();
|
||||
lazy.all_results = items;
|
||||
lazy.displayed_count = initial_count;
|
||||
lazy.query = query_for_highlight;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -760,7 +764,7 @@ impl MainWindow {
|
||||
let initial_count = INITIAL_RESULTS.min(results.len());
|
||||
|
||||
for item in results.iter().take(initial_count) {
|
||||
let row = ResultRow::new(item);
|
||||
let row = ResultRow::new(item, &query_str);
|
||||
results_list.append(&row);
|
||||
}
|
||||
|
||||
@@ -772,6 +776,7 @@ impl MainWindow {
|
||||
results[..initial_count].to_vec();
|
||||
let mut lazy = lazy_state.borrow_mut();
|
||||
lazy.all_results = results;
|
||||
lazy.query = query_str;
|
||||
lazy.displayed_count = initial_count;
|
||||
}
|
||||
},
|
||||
@@ -1267,7 +1272,7 @@ impl MainWindow {
|
||||
let initial_count = INITIAL_RESULTS.min(results.len());
|
||||
|
||||
for item in results.iter().take(initial_count) {
|
||||
let row = ResultRow::new(item);
|
||||
let row = ResultRow::new(item, "");
|
||||
results_list.append(&row);
|
||||
}
|
||||
|
||||
@@ -1303,7 +1308,7 @@ impl MainWindow {
|
||||
let initial_count = INITIAL_RESULTS.min(results.len());
|
||||
|
||||
for item in results.iter().take(initial_count) {
|
||||
let row = ResultRow::new(item);
|
||||
let row = ResultRow::new(item, query);
|
||||
self.results_list.append(&row);
|
||||
}
|
||||
|
||||
@@ -1316,6 +1321,7 @@ impl MainWindow {
|
||||
let mut lazy = self.lazy_state.borrow_mut();
|
||||
lazy.all_results = results;
|
||||
lazy.displayed_count = initial_count;
|
||||
lazy.query = query.to_string();
|
||||
}
|
||||
|
||||
/// Set up lazy loading scroll detection
|
||||
@@ -1372,8 +1378,9 @@ impl MainWindow {
|
||||
if displayed < all_count {
|
||||
// Load next batch
|
||||
let new_end = (displayed + LOAD_MORE_BATCH).min(all_count);
|
||||
let query = lazy.query.clone();
|
||||
for item in lazy.all_results[displayed..new_end].iter() {
|
||||
let row = ResultRow::new(item);
|
||||
let row = ResultRow::new(item, &query);
|
||||
results_list.append(&row);
|
||||
}
|
||||
lazy.displayed_count = new_end;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GtkBox, Image, Label, ListBoxRow, Orientation, Widget};
|
||||
use owlry_core::providers::LaunchItem;
|
||||
use owlry_core::providers::{LaunchItem, ProviderType};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct ResultRow {
|
||||
@@ -18,9 +18,31 @@ fn is_emoji_icon(s: &str) -> bool {
|
||||
!first_char.is_ascii() && s.chars().count() <= 8
|
||||
}
|
||||
|
||||
/// Check if this item should be highlighted based on the query.
|
||||
/// Highlighted when:
|
||||
/// - Item is from a dynamic plugin (calculator, converter, websearch, filesearch)
|
||||
/// and the query is non-empty (auto-detect triggered)
|
||||
/// - Item name exactly matches the query (case-insensitive)
|
||||
fn should_highlight(item: &LaunchItem, query: &str) -> bool {
|
||||
if query.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exact name match (case-insensitive)
|
||||
if item.name.eq_ignore_ascii_case(query) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Dynamic plugin auto-detect results
|
||||
matches!(
|
||||
&item.provider,
|
||||
ProviderType::Plugin(id) if matches!(id.as_str(), "calc" | "conv" | "websearch" | "filesearch")
|
||||
)
|
||||
}
|
||||
|
||||
impl ResultRow {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(item: &LaunchItem) -> ListBoxRow {
|
||||
pub fn new(item: &LaunchItem, query: &str) -> ListBoxRow {
|
||||
let row = ListBoxRow::builder()
|
||||
.selectable(true)
|
||||
.activatable(true)
|
||||
@@ -28,6 +50,10 @@ impl ResultRow {
|
||||
|
||||
row.add_css_class("owlry-result-row");
|
||||
|
||||
if should_highlight(item, query) {
|
||||
row.add_css_class("owlry-result-highlight");
|
||||
}
|
||||
|
||||
let hbox = GtkBox::builder()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(12)
|
||||
|
||||
@@ -77,8 +77,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(5, 5, 5, 0.98);
|
||||
border: 1px solid rgba(38, 38, 38, 0.8);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8),
|
||||
0 0 0 1px rgba(255, 0, 68, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(30, 30, 46, 0.95);
|
||||
border: 1px solid rgba(69, 71, 90, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(203, 166, 247, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(40, 42, 54, 0.95);
|
||||
border: 1px solid rgba(98, 114, 164, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(189, 147, 249, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(40, 40, 40, 0.95);
|
||||
border: 1px solid rgba(80, 73, 69, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(254, 128, 25, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(46, 52, 64, 0.95);
|
||||
border: 1px solid rgba(76, 86, 106, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
|
||||
0 0 0 1px rgba(136, 192, 208, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(40, 44, 52, 0.95);
|
||||
border: 1px solid rgba(24, 26, 31, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(97, 175, 239, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(26, 27, 38, 0.95);
|
||||
border: 1px solid rgba(65, 72, 104, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(224, 175, 104, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(25, 23, 36, 0.95);
|
||||
border: 1px solid rgba(38, 35, 58, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(196, 167, 231, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(0, 43, 54, 0.95);
|
||||
border: 1px solid rgba(88, 110, 117, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(38, 139, 210, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
.owlry-main {
|
||||
background-color: rgba(26, 27, 38, 0.95);
|
||||
border: 1px solid rgba(65, 72, 104, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(122, 162, 247, 0.1);
|
||||
}
|
||||
|
||||
.owlry-search {
|
||||
|
||||
Reference in New Issue
Block a user