feat: restructure k-launcher workspace and add core functionality

- Updated Cargo.toml to include a new k-launcher crate and reorganized workspace members.
- Introduced a README.md file detailing the project philosophy, architecture, and technical specifications.
- Implemented a new Kernel struct in k-launcher-kernel for managing plugins and search functionality.
- Created a Plugin trait for plugins to implement, allowing for asynchronous search operations.
- Developed k-launcher-ui with an Iced-based UI for user interaction, including search input and result display.
- Added AppsPlugin and CalcPlugin to handle application launching and basic calculations, respectively.
- Established a theme module for UI styling, focusing on an Aero aesthetic.
- Removed unnecessary main.rs files from plugin crates, streamlining the project structure.
This commit is contained in:
2026-03-15 16:20:36 +01:00
parent 4c3be17b77
commit 1ac9dde347
19 changed files with 6077 additions and 28 deletions

View File

@@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
async-trait = { workspace = true }
futures = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true }

View File

@@ -1,14 +1,168 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
use std::sync::Arc;
use async_trait::async_trait;
use futures::future::join_all;
pub type PluginName = &'static str;
// --- Newtypes ---
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct ResultId(String);
impl ResultId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ResultTitle(String);
impl ResultTitle {
pub fn new(title: impl Into<String>) -> Self {
Self(title.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
pub struct Score(u32);
impl Score {
pub fn new(value: u32) -> Self {
Self(value)
}
pub fn value(self) -> u32 {
self.0
}
}
// --- SearchResult ---
pub struct SearchResult {
pub id: ResultId,
pub title: ResultTitle,
pub description: Option<String>,
pub icon: Option<String>,
pub score: Score,
pub on_execute: Arc<dyn Fn() + Send + Sync>,
}
impl std::fmt::Debug for SearchResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SearchResult")
.field("id", &self.id)
.field("title", &self.title)
.field("icon", &self.icon)
.field("score", &self.score)
.finish_non_exhaustive()
}
}
// --- Plugin trait ---
#[async_trait]
pub trait Plugin: Send + Sync {
fn name(&self) -> PluginName;
async fn search(&self, query: &str) -> Vec<SearchResult>;
}
// --- Kernel (Application use case) ---
pub struct Kernel {
plugins: Vec<Arc<dyn Plugin>>,
}
impl Kernel {
pub fn new(plugins: Vec<Arc<dyn Plugin>>) -> Self {
Self { plugins }
}
pub async fn search(&self, query: &str) -> Vec<SearchResult> {
let futures = self.plugins.iter().map(|p| p.search(query));
let nested: Vec<Vec<SearchResult>> = join_all(futures).await;
let mut flat: Vec<SearchResult> = nested.into_iter().flatten().collect();
flat.sort_by(|a, b| b.score.cmp(&a.score));
flat
}
}
// --- Tests ---
#[cfg(test)]
mod tests {
use super::*;
struct MockPlugin {
results: Vec<(&'static str, u32)>,
}
impl MockPlugin {
fn returns(results: Vec<(&'static str, u32)>) -> Self {
Self { results }
}
}
#[async_trait]
impl Plugin for MockPlugin {
fn name(&self) -> PluginName {
"mock"
}
async fn search(&self, _query: &str) -> Vec<SearchResult> {
self.results
.iter()
.enumerate()
.map(|(i, (title, score))| SearchResult {
id: ResultId::new(format!("id-{i}")),
title: ResultTitle::new(*title),
description: None,
icon: None,
score: Score::new(*score),
on_execute: Arc::new(|| {}),
})
.collect()
}
}
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
fn newtype_result_id() {
assert_eq!(ResultId::new("x").as_str(), "x");
}
#[test]
fn newtype_score() {
assert_eq!(Score::new(42).value(), 42);
}
#[test]
fn newtype_title() {
assert_eq!(ResultTitle::new("hello").as_str(), "hello");
}
#[tokio::test]
async fn empty_kernel_returns_empty() {
let k = Kernel::new(vec![]);
assert!(k.search("x").await.is_empty());
}
#[tokio::test]
async fn kernel_sorts_by_score_desc() {
let plugin = Arc::new(MockPlugin::returns(vec![
("lower", 5),
("higher", 10),
("middle", 7),
]));
let k = Kernel::new(vec![plugin]);
let results = k.search("q").await;
assert_eq!(results[0].score.value(), 10);
assert_eq!(results[1].score.value(), 7);
assert_eq!(results[2].score.value(), 5);
}
}