aura-gui 0.3.5

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.
//! Accesibilidad: el árbol que leen los lectores de pantalla (VoiceOver, NVDA…).
//!
//! Los elementos registran en `prepaint` un nodo con rol, etiqueta y límites, pero solo
//! mientras hay un lector activo (`FrameState::access_enabled`); con la accesibilidad
//! apagada el coste es una comprobación. Los `Text` se registran solos como etiquetas
//! y los widgets (botones, campos, pestañas) declaran su rol con `.role()` y `.label()`.

use crate::core::{Bounds, FocusHandle, FocusId, Point, px};
use crate::view::window::Window;
use accesskit::{Action, ActionRequest, Node, NodeId, Rect, Role, TreeId, TreeInfo, TreeUpdate};
use std::rc::Rc;

/// Un elemento visible para tecnologías de asistencia.
#[derive(Clone, Debug)]
pub struct AccessNode {
    pub role: Role,
    pub label: Option<Rc<str>>,
    pub value: Option<Rc<str>>,
    pub bounds: Bounds,
    /// Si el elemento tiene foco de teclado, su identificador.
    pub focus: Option<FocusId>,
    pub clickable: bool,
}

const ROOT: NodeId = NodeId(0);

fn rect(bounds: Bounds, scale: f32) -> Rect {
    let scale = f64::from(scale);
    Rect {
        x0: f64::from(bounds.origin.x.0) * scale,
        y0: f64::from(bounds.origin.y.0) * scale,
        x1: f64::from(bounds.origin.x.0 + bounds.size.width.0) * scale,
        y1: f64::from(bounds.origin.y.0 + bounds.size.height.0) * scale,
    }
}

impl Window {
    /// Activa o desactiva el registro de nodos (lo pide el lector de pantalla).
    pub fn set_accessibility(&mut self, active: bool) {
        self.accessibility = active;
    }

    pub fn accessibility_active(&self) -> bool {
        self.accessibility
    }

    /// Árbol completo del último frame. Cada nodo es hijo directo de la ventana: es
    /// plano, pero suficiente para navegar y activar; el orden es el de pintado.
    pub fn accessibility_tree(&self) -> TreeUpdate {
        let mut window = Node::new(Role::Window);
        window.set_label("Aura");
        let focused = self.entities.focus.focused();
        let mut nodes = Vec::with_capacity(self.frame.access.len() + 1);
        let mut focus = ROOT;
        let mut children = Vec::with_capacity(self.frame.access.len());
        for (index, entry) in self.frame.access.iter().enumerate() {
            let id = NodeId(index as u64 + 1);
            let mut node = Node::new(entry.role);
            if let Some(label) = &entry.label {
                node.set_label(label.to_string());
            }
            if let Some(value) = &entry.value {
                node.set_value(value.to_string());
            }
            node.set_bounds(rect(entry.bounds, self.scale));
            if entry.clickable {
                node.add_action(Action::Click);
            }
            if entry.focus.is_some() {
                node.add_action(Action::Focus);
                if entry.focus == focused {
                    focus = id;
                }
            }
            children.push(id);
            nodes.push((id, node));
        }
        window.set_children(children);
        nodes.push((ROOT, window));
        TreeUpdate { nodes, tree: Some(TreeInfo::new(ROOT)), tree_id: TreeId::ROOT, focus }
    }

    /// Acción pedida por un lector de pantalla sobre un nodo; `true` si hay que redibujar.
    pub fn accessibility_action(&mut self, request: &ActionRequest) -> bool {
        let Some(index) = (request.target_node.0 as usize).checked_sub(1) else { return false };
        let Some(entry) = self.frame.access.get(index).cloned() else { return false };
        match request.action {
            Action::Focus => match entry.focus.map(|id| FocusHandle { id }) {
                Some(handle) => {
                    self.entities.focus.focus(&handle);
                    true
                }
                None => false,
            },
            Action::Click => {
                // Se pulsa en el centro del elemento, como haría el ratón.
                let center = Point::new(
                    px(entry.bounds.origin.x.0 + entry.bounds.size.width.0 / 2.0),
                    px(entry.bounds.origin.y.0 + entry.bounds.size.height.0 / 2.0),
                );
                use crate::core::{InputEvent, MouseButton};
                let mut redraw = self.handle_event(&InputEvent::MouseMove { position: center });
                redraw |= self.handle_event(&InputEvent::MouseDown { position: center, button: MouseButton::Left });
                redraw | self.handle_event(&InputEvent::MouseUp { position: center, button: MouseButton::Left })
            }
            _ => false,
        }
    }
}