cranpose 0.1.150

Cranpose runtime and UI facade
Documentation
use cranpose_core::{NodeId, collections::map::HashMap};

use crate::accessibility::AccessibilityElement;

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub(crate) enum AccessibilityIdentityError {
    #[error("accessibility node IDs exhausted")]
    Exhausted,
    #[error("duplicate accessibility identity: node {0}, canvas key {1:?}")]
    Duplicate(NodeId, Option<u64>),
}

#[derive(Default)]
pub(crate) struct AccessibilitySnapshot {
    pub(crate) elements: Vec<AccessibilityElement>,
    pub(crate) ids: Vec<i32>,
    last_id: i32,
    indices: HashMap<i32, usize>,
}

impl AccessibilitySnapshot {
    pub(crate) fn update(
        &mut self,
        elements: Vec<AccessibilityElement>,
    ) -> Result<(), AccessibilityIdentityError> {
        let mut previous: HashMap<_, _> = self
            .elements
            .iter()
            .zip(&self.ids)
            .map(|(element, id)| (element.identity_key(), *id))
            .collect();
        let mut identities: HashMap<_, _> = HashMap::default();
        let mut indices = HashMap::default();
        let mut ids = Vec::with_capacity(elements.len());
        let mut last_id = self.last_id;
        for (index, element) in elements.iter().enumerate() {
            let identity = (element.node_id, element.canvas_key);
            if identities.insert(identity, ()).is_some() {
                return Err(AccessibilityIdentityError::Duplicate(
                    identity.0, identity.1,
                ));
            }
            let id = match previous.remove(&element.identity_key()) {
                Some(id) => id,
                None => {
                    last_id = last_id
                        .checked_add(1)
                        .ok_or(AccessibilityIdentityError::Exhausted)?;
                    last_id
                }
            };
            ids.push(id);
            indices.insert(id, index);
        }
        self.elements = elements;
        self.ids = ids;
        self.indices = indices;
        self.last_id = last_id;
        Ok(())
    }

    pub(crate) fn element(&self, id: i32) -> Option<&AccessibilityElement> {
        self.elements.get(*self.indices.get(&id)?)
    }

    #[cfg(any(test, target_os = "android"))]
    pub(crate) fn identity(&self, id: i32) -> Option<(NodeId, Option<u64>)> {
        self.element(id)
            .map(|element| (element.node_id, element.canvas_key))
    }
}

#[cfg(test)]
#[path = "tests/accessibility_identity.rs"]
mod tests;