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:
2026-03-28 11:17:45 +01:00
parent ade5d3aeef
commit 83fa22d84c
15 changed files with 65 additions and 32 deletions

View File

@@ -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)
})
};

View File

@@ -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);

View File

@@ -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 */

View File

@@ -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;

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {