tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 229TOML 101Markdown 82Shell 24052 Definitions

⎇ 168cd187519c0ad575d3ce63baee2f164c38d9d1 llimphi-ui/src/eventloop/a11y_rt.rs Rust · 98 lines · 5.1 KB History Who touched what raw

chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros d251eb32b · Sergio ·

1// a11y_rt.rs — Integración con AccessKit en tiempo de ejecución.
2// Empuja el árbol de accesibilidad al SO y atiende las acciones del lector
3// de pantalla (focus, click). Separado para no contaminar el flujo de input
4// con detalles de la API de accesskit_winit.
5
6use super::super::*;
7use super::push_a11y_tree;
8
9impl<A: App> Runtime<A> {
10    /// Recibe un `accesskit_winit::Event` (ruteado vía `EventLoopProxy` como
11    /// `UserEvent::A11y(...)`) y reacciona:
12    /// - `InitialTreeRequested`: el lector pidió el árbol inicial → empujamos
13    ///   uno desde `last_render` si lo hay, o pedimos un redraw que lo creará.
14    /// - `ActionRequested(req)`: el lector quiere ejecutar una acción sobre un
15    ///   `NodeId`. v1 soporta `Action::Focus` (mueve `state.focused` + dispara
16    ///   `App::on_focus`) y `Action::Click` (ejecuta el `on_click` del nodo).
17    /// - `AccessibilityDeactivated`: nada que hacer; el siguiente paint dejará
18    ///   de construir trees (el `update_if_active` se autoinhibe).
19    pub(super) fn handle_a11y_event(&mut self, ev: accesskit_winit::Event) {
20        use accesskit_winit::WindowEvent as AkWinEvent;
21        let Some(state) = self.state.as_mut() else { return };
22        match ev.window_event {
23            AkWinEvent::InitialTreeRequested => {
24                // Si ya pintamos un frame, ese mounted sirve para el árbol
25                // inicial. Si no, forzamos un redraw — el path normal llamará
26                // a `push_a11y_tree::<A>` al final.
27                if state.last_render.is_some() {
28                    push_a11y_tree::<A>(state);
29                } else {
30                    state.window.request_redraw();
31                }
32            }
33            AkWinEvent::ActionRequested(req) => {
34                let Some(idx) = crate::a11y::mounted_idx_for(req.target_node) else {
35                    return;
36                };
37                let Some(cache) = state.last_render.as_ref() else {
38                    return;
39                };
40                let Some(node) = cache.mounted.nodes.get(idx) else {
41                    return;
42                };
43                match req.action {
44                    accesskit::Action::Focus => {
45                        // Si el nodo es focusable, movemos el foco a su id
46                        // opaco; si no, lo limpiamos. La app recibe la
47                        // transición vía `App::on_focus`.
48                        let new_focus = node.focusable;
49                        state.focused = new_focus;
50                        let model = state.model.as_ref().expect("model");
51                        if let Some(msg) = A::on_focus(model, new_focus) {
52                            let m = state.model.take().expect("model");
53                            state.model = Some(super::vigia::aplicar_update::<A>(m, msg, &self.handle));
54                        }
55                        state.invalidar_render();
56                        state.window.request_redraw();
57                    }
58                    accesskit::Action::Click => {
59                        // Dos cosas que v1 no hacía:
60                        //
61                        // - Delegar al hijo clickeable cuando el rol está en el
62                        //   contenedor (fila de árbol/tabla). Lo resuelve
63                        //   `a11y::click_target`.
64                        // - Atender `on_click_at`: la posición sintética SÍ
65                        //   existe — el centro del rect del nodo en el layout
66                        //   del frame, que es lo que produce un click "en el
67                        //   medio del control". Es la misma convención que usa
68                        //   `llimphi_test::query::NodeRef::click`.
69                        let msg = crate::a11y::click_target(&cache.mounted, idx).and_then(|t| {
70                            let destino = &cache.mounted.nodes[t];
71                            match &destino.on_click_at {
72                                Some(f) => cache
73                                    .computed
74                                    .get(destino.id)
75                                    .and_then(|r| f(r.w * 0.5, r.h * 0.5, r.w, r.h)),
76                                None => destino.on_click.clone(),
77                            }
78                        });
79                        if let Some(msg) = msg {
80                            let m = state.model.take().expect("model");
81                            state.model = Some(super::vigia::aplicar_update::<A>(m, msg, &self.handle));
82                            state.invalidar_render();
83                            state.window.request_redraw();
84                        }
85                    }
86                    _ => {
87                        // Otras acciones (Expand/Collapse/Increment/Decrement/
88                        // SetValue/ScrollIntoView/etc.) se sumarán cuando un
89                        // widget concreto lo pida — el modelo `SemanticsSpec`
90                        // ya tiene los flags relevantes; solo falta cablear el
91                        // efecto inverso (acción → mutación de Model).
92                    }
93                }
94            }
95            AkWinEvent::AccessibilityDeactivated => {}
96        }
97    }
98}