Aspirational examples for app developer contract.
This commit is contained in:
116
aspirational_examples/02_widget_refs_and_commands.rs
Normal file
116
aspirational_examples/02_widget_refs_and_commands.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
//! Aspirational RUIN app API for imperative widget refs, typed shortcuts, and overlay layers.
|
||||
//!
|
||||
//! Intentionally non-compiling; this is a design sketch.
|
||||
|
||||
use ruin::prelude::*;
|
||||
|
||||
#[ruin_runtime::async_main]
|
||||
async fn main() -> ruin::Result<()> {
|
||||
App::new()
|
||||
.window(Window::new().title("RUIN Search").size(1100.0, 760.0))
|
||||
.mount::<CommandSearchApp>()
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn CommandSearchApp() -> impl View {
|
||||
let query = use_signal(String::new);
|
||||
let selected_index = use_signal(|| 0usize);
|
||||
|
||||
let search_input = use_widget_ref::<TextInput>();
|
||||
let results_list = use_widget_ref::<ScrollArea>();
|
||||
let toast_region = use_widget_ref::<ToastRegion>();
|
||||
|
||||
let results = use_memo(move || fuzzy::search(COMMANDS, &query.get()));
|
||||
|
||||
use_mount({
|
||||
let search_input = search_input.clone();
|
||||
move || search_input.focus()
|
||||
});
|
||||
|
||||
use_shortcut(
|
||||
Shortcut::new(Key::Character('K')).with_ctrl(),
|
||||
ShortcutScope::Application,
|
||||
{
|
||||
let search_input = search_input.clone();
|
||||
move || search_input.focus()
|
||||
},
|
||||
);
|
||||
|
||||
use_shortcut(
|
||||
Shortcut::new(Key::ArrowDown),
|
||||
ShortcutScope::FocusedWithin(results_list.focus_scope()),
|
||||
move || {
|
||||
selected_index.update(|index| {
|
||||
*index = (*index + 1).min(results.with(|items| items.len().saturating_sub(1)));
|
||||
});
|
||||
results_list.scroll_selected_into_view();
|
||||
},
|
||||
);
|
||||
|
||||
use_shortcut(
|
||||
Shortcut::new(Key::Enter),
|
||||
ShortcutScope::FocusedItem(results_list.focus_scope()),
|
||||
{
|
||||
let toast_region = toast_region.clone();
|
||||
move || {
|
||||
if let Some(command) = results.with(|items| items.get(selected_index.get()).cloned()) {
|
||||
toast_region.show(command.title.to_string());
|
||||
command.run();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
view! {
|
||||
layers(fill = true) {
|
||||
layer(kind = LayerKind::Content) {
|
||||
column(fill = true, gap = 16, padding = 20) {
|
||||
text(role = TextRole::Heading(1), size = 28, weight = FontWeight::Semibold) {
|
||||
"Command search"
|
||||
}
|
||||
|
||||
text_input(
|
||||
ref = search_input,
|
||||
value = query,
|
||||
placeholder = "Search commands...",
|
||||
) {}
|
||||
|
||||
scroll_area(ref = results_list, fill = true) {
|
||||
list(gap = 8) {
|
||||
for (row_index, command) in results.iter().enumerate() keyed by command.id {
|
||||
CommandRow(
|
||||
selected = row_index == selected_index.get(),
|
||||
title = command.title,
|
||||
subtitle = command.subtitle,
|
||||
on_press = move |_| command.run(),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer(
|
||||
kind = LayerKind::Overlay,
|
||||
anchor = Anchor::bottom_right(20.0, 20.0),
|
||||
pointer_events = PointerEvents::PassThrough,
|
||||
) {
|
||||
ToastRegion(ref = toast_region) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static COMMANDS: &[Command] = &[];
|
||||
|
||||
struct Command {
|
||||
id: &'static str,
|
||||
title: &'static str,
|
||||
subtitle: &'static str,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
fn run(&self) {}
|
||||
}
|
||||
Reference in New Issue
Block a user