aura-gui 0.2.2

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! El picker filtra al escribir, navega con flechas, confirma con Enter y se cierra con Escape.

use aura_ui::core::{EntityMap, InputEvent};
use aura_ui::view::prelude::*;
use aura_ui::widgets::{Picker, PickerDelegate, PickerEvent, bind_widget_keys};

struct Fruits {
    all: Vec<&'static str>,
    matches: Vec<&'static str>,
    chosen: Option<Handle<Host>>,
}

impl PickerDelegate for Fruits {
    fn placeholder(&self) -> String {
        "Fruta...".into()
    }

    fn match_count(&self) -> usize {
        self.matches.len()
    }

    fn update_matches(&mut self, query: &str, _picker: Handle<Picker<Self>>, _entities: &mut EntityMap) {
        self.matches = self.all.iter().copied().filter(|fruit| fruit.contains(query)).collect();
    }

    fn render_match(&self, index: usize, _selected: bool) -> AnyElement {
        text(self.matches[index]).into_element()
    }

    fn confirm(&mut self, index: usize, entities: &mut EntityMap) {
        let fruit = self.matches[index];
        if let Some(host) = self.chosen {
            entities.update(host, |host, _| host.chosen = Some(fruit));
        }
    }
}

struct Host {
    picker: Option<Handle<Picker<Fruits>>>,
    chosen: Option<&'static str>,
    events: Vec<PickerEvent>,
}

impl View for Host {
    fn view(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        let picker = self.picker.map(|picker| cx.render_child(picker));
        div().size_full().when_some(picker, |root, picker| root.child(picker)).into_element()
    }
}

fn setup() -> (Window, Handle<Host>) {
    let mut window = Window::new(Theme::dark(), Size::new(px(800.0), px(600.0)), 1.0);
    bind_widget_keys(&mut window.keymap);
    let host = window.set_root(Host { picker: None, chosen: None, events: Vec::new() });
    let fruits = Fruits { all: vec!["manzana", "mango", "pera"], matches: Vec::new(), chosen: Some(host) };
    let picker = Picker::open(fruits, &mut window.entities);
    window.entities.subscribe(picker, host, |host: &mut Host, event: &PickerEvent, _| host.events.push(*event));
    window.entities.update(host, |host, _| host.picker = Some(picker));
    window.draw(None);
    (window, host)
}

fn press(window: &mut Window, keystroke: &str, text: Option<&str>) {
    window.handle_event(&InputEvent::KeyDown { keystroke: keystroke.into(), text: text.map(str::to_owned) });
    window.draw(None);
}

#[test]
fn typing_filters_and_enter_confirms_the_selection() {
    let (mut window, host) = setup();
    for letter in ["m", "a"] {
        press(&mut window, letter, Some(letter));
    }
    press(&mut window, "down", None);
    press(&mut window, "enter", None);
    let host = window.entities.read(host).expect("host");
    assert_eq!(host.chosen, Some("mango"), "\"ma\" deja manzana y mango; flecha abajo elige mango");
    assert_eq!(host.events, [PickerEvent::Confirmed]);
}

#[test]
fn escape_dismisses() {
    let (mut window, host) = setup();
    press(&mut window, "escape", None);
    assert_eq!(window.entities.read(host).map(|host| host.events.clone()), Some(vec![PickerEvent::Dismissed]));
}