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.
//! Árbol de despacho de teclado, reconstruido en cada prepaint (como el de GPUI).

use crate::core::{Action, FocusId};
use crate::view::interactivity::{ActionHandler, KeyDownHandler, TextInputHandler};
use crate::view::key_context::KeyContext;

#[derive(Default)]
pub struct DispatchNode {
    pub parent: Option<usize>,
    pub focus: Option<FocusId>,
    pub tab_index: Option<i32>,
    pub context: Option<KeyContext>,
    pub actions: Vec<(Action, ActionHandler)>,
    pub key_down: Option<KeyDownHandler>,
    pub text_input: Option<TextInputHandler>,
}

#[derive(Default)]
pub struct DispatchTree {
    nodes: Vec<DispatchNode>,
    stack: Vec<usize>,
}

impl DispatchTree {
    /// Deja solo un nodo raíz vacío, para que siempre haya una ruta.
    pub fn clear(&mut self) {
        self.nodes.clear();
        self.nodes.push(DispatchNode::default());
        self.stack = vec![0];
    }

    pub fn push(&mut self, mut node: DispatchNode) {
        node.parent = self.stack.last().copied();
        self.nodes.push(node);
        self.stack.push(self.nodes.len() - 1);
    }

    pub fn pop(&mut self) {
        if self.stack.len() > 1 {
            self.stack.pop();
        }
    }

    pub fn node(&self, index: usize) -> Option<&DispatchNode> {
        self.nodes.get(index)
    }

    /// Ruta de índices desde la raíz hasta el nodo enfocado. Sin foco, la ruta llega
    /// al primer nodo registrado (el de la vista raíz), como en GPUI.
    pub fn path_to_focus(&self, focused: Option<FocusId>) -> Vec<usize> {
        let target = focused.and_then(|id| self.nodes.iter().position(|node| node.focus == Some(id)));
        let fallback = if self.nodes.len() > 1 {
            Some(1)
        } else if self.nodes.is_empty() {
            None
        } else {
            Some(0)
        };
        let mut path = Vec::new();
        let mut current = target.or(fallback);
        while let Some(index) = current {
            path.push(index);
            current = self.nodes.get(index).and_then(|node| node.parent);
        }
        path.reverse();
        path
    }

    pub fn focus_path(&self, path: &[usize]) -> Vec<FocusId> {
        path.iter().filter_map(|index| self.nodes.get(*index)?.focus).collect()
    }

    /// Contextos de la ruta, de la raíz al nodo enfocado.
    pub fn context_stack(&self, path: &[usize]) -> Vec<KeyContext> {
        path.iter().filter_map(|index| self.nodes.get(*index)?.context.clone()).collect()
    }

    /// Elementos enfocables con `tab_index`, en orden de tabulación.
    pub fn tab_order(&self) -> Vec<FocusId> {
        let mut focusable: Vec<(i32, usize, FocusId)> = self
            .nodes
            .iter()
            .enumerate()
            .filter_map(|(order, node)| Some((node.tab_index?, order, node.focus?)))
            .collect();
        focusable.sort_by_key(|(tab_index, order, _)| (*tab_index, *order));
        focusable.into_iter().map(|(_, _, focus)| focus).collect()
    }
}