feat: add Dioxus and GPUI UI prototypes for framework comparison
Dioxus (ui-dioxus/): 2,169 lines, WebView mode (same wry as Tauri), Catppuccin theme, 12 components, agor-core integration, compiles clean. Evolution path — keeps xterm.js, gradual migration from Tauri. GPUI (ui-gpui/): 2,490 lines, GPU-accelerated rendering, alacritty_terminal for native terminal, 17 files, Catppuccin palette, demo data. Revolution path — pure Rust UI, 120fps target, no WebView. Both are standalone (not in workspace), share agor-core backend. Created for side-by-side comparison to inform framework decision.
This commit is contained in:
parent
90c7315336
commit
f3d2ca78ba
34 changed files with 17467 additions and 0 deletions
85
ui-dioxus/src/components/command_palette.rs
Normal file
85
ui-dioxus/src/components/command_palette.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/// CommandPalette — Ctrl+K overlay with search and command list.
|
||||
///
|
||||
/// Mirrors the Svelte app's CommandPalette.svelte: Spotlight-style overlay
|
||||
/// with search input and filtered command list.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::state::{palette_commands, PaletteCommand};
|
||||
|
||||
#[component]
|
||||
pub fn CommandPalette(
|
||||
visible: Signal<bool>,
|
||||
) -> Element {
|
||||
let mut search = use_signal(|| String::new());
|
||||
let commands = palette_commands();
|
||||
|
||||
// Filter commands by search text
|
||||
let search_text = search.read().to_lowercase();
|
||||
let filtered: Vec<&PaletteCommand> = if search_text.is_empty() {
|
||||
commands.iter().collect()
|
||||
} else {
|
||||
commands
|
||||
.iter()
|
||||
.filter(|c| c.label.to_lowercase().contains(&search_text))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let close = move |_| {
|
||||
visible.set(false);
|
||||
search.set(String::new());
|
||||
};
|
||||
|
||||
let on_key = move |e: KeyboardEvent| {
|
||||
if e.key() == Key::Escape {
|
||||
visible.set(false);
|
||||
search.set(String::new());
|
||||
}
|
||||
};
|
||||
|
||||
if !*visible.read() {
|
||||
return rsx! {};
|
||||
}
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "palette-overlay",
|
||||
onclick: close,
|
||||
onkeydown: on_key,
|
||||
|
||||
div {
|
||||
class: "palette-box",
|
||||
// Stop click propagation so clicking inside doesn't close
|
||||
onclick: move |e| e.stop_propagation(),
|
||||
|
||||
input {
|
||||
class: "palette-input",
|
||||
r#type: "text",
|
||||
placeholder: "Type a command...",
|
||||
value: "{search}",
|
||||
oninput: move |e| search.set(e.value()),
|
||||
autofocus: true,
|
||||
}
|
||||
|
||||
div { class: "palette-results",
|
||||
for cmd in filtered.iter() {
|
||||
div { class: "palette-item",
|
||||
span { class: "palette-item-icon", "{cmd.icon}" }
|
||||
span { class: "palette-item-label", "{cmd.label}" }
|
||||
if let Some(ref shortcut) = cmd.shortcut {
|
||||
span { class: "palette-item-shortcut", "{shortcut}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filtered.is_empty() {
|
||||
div {
|
||||
style: "padding: 1rem; text-align: center; color: var(--ctp-overlay0); font-size: 0.8125rem;",
|
||||
"No matching commands"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue