refactor: simplify theme usage and enhance AppsPlugin structure
This commit is contained in:
@@ -9,7 +9,7 @@ use iced::{
|
|||||||
|
|
||||||
use k_launcher_kernel::{Kernel, SearchResult};
|
use k_launcher_kernel::{Kernel, SearchResult};
|
||||||
|
|
||||||
use crate::theme::AeroColors;
|
use crate::theme;
|
||||||
|
|
||||||
static INPUT_ID: std::sync::LazyLock<iced::widget::Id> =
|
static INPUT_ID: std::sync::LazyLock<iced::widget::Id> =
|
||||||
std::sync::LazyLock::new(|| iced::widget::Id::new("search"));
|
std::sync::LazyLock::new(|| iced::widget::Id::new("search"));
|
||||||
@@ -89,7 +89,7 @@ fn update(state: &mut KLauncherApp, message: Message) -> Task<Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn view(state: &KLauncherApp) -> Element<'_, Message> {
|
fn view(state: &KLauncherApp) -> Element<'_, Message> {
|
||||||
let colors = AeroColors::standard();
|
let colors = &*theme::AERO;
|
||||||
|
|
||||||
let search_bar = text_input("Search...", &state.query)
|
let search_bar = text_input("Search...", &state.query)
|
||||||
.id(INPUT_ID.clone())
|
.id(INPUT_ID.clone())
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ pub struct AeroColors {
|
|||||||
pub border_cyan: Color,
|
pub border_cyan: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static AERO: std::sync::LazyLock<AeroColors> =
|
||||||
|
std::sync::LazyLock::new(AeroColors::standard);
|
||||||
|
|
||||||
impl AeroColors {
|
impl AeroColors {
|
||||||
pub fn standard() -> Self {
|
pub fn standard() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -56,15 +56,55 @@ pub trait DesktopEntrySource: Send + Sync {
|
|||||||
fn entries(&self) -> Vec<DesktopEntry>;
|
fn entries(&self) -> Vec<DesktopEntry>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Plugin ---
|
// --- Cached entry (pre-computed at construction) ---
|
||||||
|
|
||||||
pub struct AppsPlugin<S: DesktopEntrySource> {
|
struct CachedEntry {
|
||||||
source: S,
|
name: AppName,
|
||||||
|
name_lc: String,
|
||||||
|
icon: Option<String>,
|
||||||
|
on_execute: Arc<dyn Fn() + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: DesktopEntrySource> AppsPlugin<S> {
|
// --- Plugin ---
|
||||||
pub fn new(source: S) -> Self {
|
|
||||||
Self { source }
|
pub struct AppsPlugin {
|
||||||
|
entries: Vec<CachedEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppsPlugin {
|
||||||
|
pub fn new(source: impl DesktopEntrySource) -> Self {
|
||||||
|
let entries = source
|
||||||
|
.entries()
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| {
|
||||||
|
let name_lc = e.name.as_str().to_lowercase();
|
||||||
|
let icon = e.icon.as_ref().and_then(|p| resolve_icon_path(p.as_str()));
|
||||||
|
let exec = e.exec.clone();
|
||||||
|
CachedEntry {
|
||||||
|
name_lc,
|
||||||
|
icon,
|
||||||
|
on_execute: Arc::new(move || {
|
||||||
|
let parts: Vec<&str> = exec.as_str().split_whitespace().collect();
|
||||||
|
if let Some((cmd, args)) = parts.split_first() {
|
||||||
|
let _ = unsafe {
|
||||||
|
Command::new(cmd)
|
||||||
|
.args(args)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.pre_exec(|| {
|
||||||
|
libc::setsid();
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.spawn()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
name: e.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Self { entries }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,14 +121,12 @@ fn resolve_icon_path(name: &str) -> Option<String> {
|
|||||||
candidates.into_iter().find(|p| Path::new(p).exists())
|
candidates.into_iter().find(|p| Path::new(p).exists())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn score_match(name: &str, query: &str) -> Option<u32> {
|
fn score_match(name_lc: &str, query_lc: &str) -> Option<u32> {
|
||||||
let name_lc = name.to_lowercase();
|
|
||||||
let query_lc = query.to_lowercase();
|
|
||||||
if name_lc == query_lc {
|
if name_lc == query_lc {
|
||||||
Some(100)
|
Some(100)
|
||||||
} else if name_lc.starts_with(&query_lc) {
|
} else if name_lc.starts_with(query_lc) {
|
||||||
Some(80)
|
Some(80)
|
||||||
} else if name_lc.contains(&query_lc) {
|
} else if name_lc.contains(query_lc) {
|
||||||
Some(60)
|
Some(60)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -96,7 +134,7 @@ fn score_match(name: &str, query: &str) -> Option<u32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<S: DesktopEntrySource> Plugin for AppsPlugin<S> {
|
impl Plugin for AppsPlugin {
|
||||||
fn name(&self) -> PluginName {
|
fn name(&self) -> PluginName {
|
||||||
"apps"
|
"apps"
|
||||||
}
|
}
|
||||||
@@ -105,34 +143,17 @@ impl<S: DesktopEntrySource> Plugin for AppsPlugin<S> {
|
|||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
self.source
|
let query_lc = query.to_lowercase();
|
||||||
.entries()
|
self.entries
|
||||||
.into_iter()
|
.iter()
|
||||||
.filter_map(|entry| {
|
.filter_map(|e| {
|
||||||
score_match(entry.name.as_str(), query).map(|score| {
|
score_match(&e.name_lc, &query_lc).map(|score| SearchResult {
|
||||||
let exec = entry.exec.clone();
|
id: ResultId::new(format!("app-{}", e.name.as_str())),
|
||||||
let icon = entry.icon.as_ref().and_then(|p| resolve_icon_path(p.as_str()));
|
title: ResultTitle::new(e.name.as_str()),
|
||||||
SearchResult {
|
|
||||||
id: ResultId::new(format!("app-{}", entry.name.as_str())),
|
|
||||||
title: ResultTitle::new(entry.name.as_str()),
|
|
||||||
description: None,
|
description: None,
|
||||||
icon,
|
icon: e.icon.clone(),
|
||||||
score: Score::new(score),
|
score: Score::new(score),
|
||||||
on_execute: Arc::new(move || {
|
on_execute: Arc::clone(&e.on_execute),
|
||||||
let parts: Vec<&str> = exec.as_str().split_whitespace().collect();
|
|
||||||
if let Some((cmd, args)) = parts.split_first() {
|
|
||||||
let _ = unsafe {
|
|
||||||
Command::new(cmd)
|
|
||||||
.args(args)
|
|
||||||
.stdin(Stdio::null())
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.pre_exec(|| { libc::setsid(); Ok(()) })
|
|
||||||
.spawn()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@@ -188,7 +209,7 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopEntry> {
|
|||||||
let mut name: Option<String> = None;
|
let mut name: Option<String> = None;
|
||||||
let mut exec: Option<String> = None;
|
let mut exec: Option<String> = None;
|
||||||
let mut icon: Option<String> = None;
|
let mut icon: Option<String> = None;
|
||||||
let mut entry_type: Option<String> = None;
|
let mut is_application = false;
|
||||||
let mut no_display = false;
|
let mut no_display = false;
|
||||||
|
|
||||||
for line in content.lines() {
|
for line in content.lines() {
|
||||||
@@ -209,22 +230,27 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopEntry> {
|
|||||||
"Name" if name.is_none() => name = Some(value.trim().to_string()),
|
"Name" if name.is_none() => name = Some(value.trim().to_string()),
|
||||||
"Exec" if exec.is_none() => exec = Some(value.trim().to_string()),
|
"Exec" if exec.is_none() => exec = Some(value.trim().to_string()),
|
||||||
"Icon" if icon.is_none() => icon = Some(value.trim().to_string()),
|
"Icon" if icon.is_none() => icon = Some(value.trim().to_string()),
|
||||||
"Type" if entry_type.is_none() => entry_type = Some(value.trim().to_string()),
|
"Type" if !is_application => is_application = value.trim() == "Application",
|
||||||
"NoDisplay" => no_display = value.trim().eq_ignore_ascii_case("true"),
|
"NoDisplay" => no_display = value.trim().eq_ignore_ascii_case("true"),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry_type.as_deref() != Some("Application") || no_display {
|
if !is_application || no_display {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let exec_clean: String = exec?
|
let exec_clean: String = exec?
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.filter(|s| !s.starts_with('%'))
|
.filter(|s| !s.starts_with('%'))
|
||||||
.collect::<Vec<_>>()
|
.fold(String::new(), |mut acc, s| {
|
||||||
.join(" ");
|
if !acc.is_empty() {
|
||||||
|
acc.push(' ');
|
||||||
|
}
|
||||||
|
acc.push_str(s);
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
Some(DesktopEntry {
|
Some(DesktopEntry {
|
||||||
name: AppName::new(name?),
|
name: AppName::new(name?),
|
||||||
|
|||||||
Reference in New Issue
Block a user