Files
llimphi/llimphi-ui/src/eventloop/a11y_rt.rs
T
Sergio ccab39f140 refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel
Re-sincroniza las fuentes desde el monorepo (estaba en vello 0.5/wgpu 24 y con la
estructura vieja de eventloop) y suma el 3D:

- bump del workspace a vello 0.7 / wgpu 27 / parley 0.6, + accesskit 0.24 /
  accesskit_winit 0.33 / vello_hybrid 0.0.9.
- nuevos crates: llimphi-3d (voxels ray-march + mallas en un depth compartido,
  montable dentro de un View 2D vía set_viewport+scissor) y llimphi-voxel
  (world-gen, personajes, director de escenas) + shared/foreign-vox (puente .vox).
- README: sección "Not just 2D — a 3D voxel engine" + GIF (docs/llimphi_voxel.gif).
- excluido modules/allichay (arrastra deps fuera del alcance del front-door).
- cargo check --workspace: verde.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:40:00 +00:00

82 lines
4.1 KiB
Rust

// a11y_rt.rs — Integración con AccessKit en tiempo de ejecución.
// Empuja el árbol de accesibilidad al SO y atiende las acciones del lector
// de pantalla (focus, click). Separado para no contaminar el flujo de input
// con detalles de la API de accesskit_winit.
use super::super::*;
use super::push_a11y_tree;
impl<A: App> Runtime<A> {
/// Recibe un `accesskit_winit::Event` (ruteado vía `EventLoopProxy` como
/// `UserEvent::A11y(...)`) y reacciona:
/// - `InitialTreeRequested`: el lector pidió el árbol inicial → empujamos
/// uno desde `last_render` si lo hay, o pedimos un redraw que lo creará.
/// - `ActionRequested(req)`: el lector quiere ejecutar una acción sobre un
/// `NodeId`. v1 soporta `Action::Focus` (mueve `state.focused` + dispara
/// `App::on_focus`) y `Action::Click` (ejecuta el `on_click` del nodo).
/// - `AccessibilityDeactivated`: nada que hacer; el siguiente paint dejará
/// de construir trees (el `update_if_active` se autoinhibe).
pub(super) fn handle_a11y_event(&mut self, ev: accesskit_winit::Event) {
use accesskit_winit::WindowEvent as AkWinEvent;
let Some(state) = self.state.as_mut() else { return };
match ev.window_event {
AkWinEvent::InitialTreeRequested => {
// Si ya pintamos un frame, ese mounted sirve para el árbol
// inicial. Si no, forzamos un redraw — el path normal llamará
// a `push_a11y_tree::<A>` al final.
if state.last_render.is_some() {
push_a11y_tree::<A>(state);
} else {
state.window.request_redraw();
}
}
AkWinEvent::ActionRequested(req) => {
let Some(idx) = crate::a11y::mounted_idx_for(req.target_node) else {
return;
};
let Some(cache) = state.last_render.as_ref() else {
return;
};
let Some(node) = cache.mounted.nodes.get(idx) else {
return;
};
match req.action {
accesskit::Action::Focus => {
// Si el nodo es focusable, movemos el foco a su id
// opaco; si no, lo limpiamos. La app recibe la
// transición vía `App::on_focus`.
let new_focus = node.focusable;
state.focused = new_focus;
let model = state.model.as_ref().expect("model");
if let Some(msg) = A::on_focus(model, new_focus) {
let m = state.model.take().expect("model");
state.model = Some(A::update(m, msg, &self.handle));
}
state.last_render = None;
state.window.request_redraw();
}
accesskit::Action::Click => {
// Sólo soportamos `on_click` (Msg directo) en v1. Los
// handlers `*_at` necesitan una posición sintética
// coherente que no tenemos — los ignoramos.
if let Some(msg) = node.on_click.clone() {
let m = state.model.take().expect("model");
state.model = Some(A::update(m, msg, &self.handle));
state.last_render = None;
state.window.request_redraw();
}
}
_ => {
// Otras acciones (Expand/Collapse/Increment/Decrement/
// SetValue/ScrollIntoView/etc.) se sumarán cuando un
// widget concreto lo pida — el modelo `SemanticsSpec`
// ya tiene los flags relevantes; solo falta cablear el
// efecto inverso (acción → mutación de Model).
}
}
}
AkWinEvent::AccessibilityDeactivated => {}
}
}
}