oxiui-accessibility 0.1.2

OxiUI accessibility bridge — builds an accesskit a11y node tree from the OxiUI widget graph
Documentation

oxiui-accessibility — AccessKit a11y tree builder for OxiUI

Crates.io License

oxiui-accessibility is the accessibility bridge of the COOLJAPAN OxiUI toolkit. It converts an OxiUI widget graph — expressed as a tree of A11yNodes — into an [accesskit::TreeUpdate] that can be pushed to any AccessKit platform adapter (Windows UIA, macOS, AT-SPI, etc.). On top of the basic tree build it provides incremental tree diffing, focus tracking, a multi-window tree registry, tab-order navigation, action mapping/dispatch, text-run synthesis for screen readers, table-structure helpers, a node-recycling pool, and lazy/dirty-tracked recomputation.

The crate is intentionally headless: no windowing toolkit or platform adapter is imported, so the entire tree-building logic is exercisable in plain unit tests without a display server. It is #![forbid(unsafe_code)] and 100% Pure Rust; its dependencies are oxiui-core and accesskit.

Installation

[dependencies]
oxiui-accessibility = "0.1.2"

Quick Start

use accesskit::NodeId;
use oxiui_accessibility::tree::{A11yNode, A11yTree, WidgetRole};

let root = A11yNode::simple(NodeId(1), WidgetRole::Window, Some("My App".to_string()));
let update = A11yTree::build(&root);
assert_eq!(update.nodes.len(), 1);

Building rich nodes

use accesskit::NodeId;
use oxiui_accessibility::{A11yNodeBuilder, tree::{A11yTree, WidgetRole}, props::CheckedState};

let node = A11yNodeBuilder::new(NodeId(2), WidgetRole::Checkbox)
    .label("Enable notifications")
    .description("Toggles desktop notifications")
    .checked(CheckedState::True)
    .tab_index(1)
    .build();

let update = A11yTree::build(&node);
assert_eq!(update.nodes.len(), 1);

Incremental updates

use accesskit::NodeId;
use oxiui_accessibility::tree::{A11yNode, A11yTree, WidgetRole};

let mut old = A11yTree::default();
old.build_and_store(&A11yNode::simple(NodeId(1), WidgetRole::Button, Some("Old".into())));

let mut new = A11yTree::default();
new.build_and_store(&A11yNode::simple(NodeId(1), WidgetRole::Button, Some("New".into())));

// Only the changed node appears in the delta.
let delta = A11yTree::diff(&old, &new);
assert_eq!(delta.nodes.len(), 1);

API Overview

tree module — node tree & build

Item Description
A11yNode A widget node: id, role, label, children, props, text_content. simple(id, role, label), content_hash
A11yTree The tree + a stored snapshot. build(root) -> TreeUpdate, build_and_store, set_focus/focus, focus_update, announce(text, urgency), diff(old, new) -> TreeUpdate
WidgetRole 30 roles mapping to accesskit::Role (see below). Implements Display and From<WidgetRole> for Role
build_table_a11y(rows, cols, headers) Synthesize a full table subtree (column headers + rows + cells)
table_row_node / table_cell_node / column_header_node Build individual table a11y nodes (carry row/col descriptions)
synthesize_text_run_children(text, selection) Split text into selected/unselected TextRunChild segments for screen readers

WidgetRole variants: Window, Group, Button, Label, TextInput, TableRow, TableCell, ScrollView, Image, Unknown, Checkbox, Slider, ProgressBar, Tab, TabPanel, Menu, MenuItem, Dialog, Alert, Tooltip, Tree, TreeItem, ListItem, Link, ColumnHeader, Banner, Navigation, Main, Complementary, ContentInfo.

builder module — fluent node builder

A11yNodeBuilder::new(id, role) then chain: label, description, placeholder, key_shortcut, disabled, expanded(bool), selected(bool), checked(CheckedState), value(now, min, max, step), text, text_selection, relationships (labelled_by, described_by, controlled_by, owns), tab_index, child; finish with build() or build_with_children(children).

props module — node properties

Item Description
A11yNodeProps The rich property bag: description, placeholder, key shortcut, disabled, expanded, selected, checked, value range, tab index, relationship id lists, …
CheckedState (= Toggled3) False, True, Mixed (tri-state)
LiveSetting Live-region politeness (Polite, Assertive, …) → accesskit::Live
TextCaret Caret/selection over text; lo, hi, is_caret
TextSelection anchor/focus selection; is_caret
TextRunChild One synthesized text-run segment (text, char/byte offset, is_selected)
character_lengths_utf8(text) Per-character UTF-8 byte lengths (AccessKit text-run encoding)
byte_offset_to_char_index(text, byte) Byte → character-index conversion

action module — action mapping

A11yActionClick, Focus, ScrollIntoView, SetValue(String), Increment, Decrement, Custom(String). map_action(&ActionRequest) -> Option<A11yAction> maps AccessKit requests; ActionDispatcher (on_action, dispatch) fans them out to registered handlers.

focus module

FocusIndicator — tracks the focused node and its focus ring: set_focus, focused_node, ring, with_ring. FocusRing describes the visual ring.

nav module — tab order

TabOrder::compute(root) builds the tab sequence; next_focus/prev_focus walk it. Free functions tab_next(order, current) and tab_prev(order, current).

dirty module — incremental recomputation

Item Description
Lazy<T> Dirty-tracked memoized value: get_or_compute, invalidate, set, get_if_clean, into_inner
DirtyTracker Per-window dirty flags + a monotonic generation counter: mark_dirty, is_dirty, clear, generation

pool module — node recycling

NodePool — recycles A11yNode allocations across frames: alloc, alloc_recycled, get/get_mut, recycle, active_count, free_count, iter_active.

text_a11y module — text-input a11y

TextSelection (text-position selection; cursor, range, start, end, is_collapsed), TextPosition, build_text_input_a11y(...), and update_text_cursor(node, selection).

Crate-root items

Item Description
OsA11yPrefs OS accessibility preferences (high_contrast, reduced_motion); query() reads OXIUI_HIGH_CONTRAST / OXIUI_REDUCED_MOTION, query_from(lookup) for testable injection
WindowA11yId(u64) A per-window tree identifier (distinct from accesskit::NodeId)
A11yForest Registry of one A11yTree per window: insert/register, get/get_mut, remove/unregister, iter, windows

Re-exports

These are re-exported at the crate root for convenience:

  • From tree: A11yNode, A11yTree, WidgetRole, build_table_a11y, table_row_node, table_cell_node, column_header_node, synthesize_text_run_children
  • From builder: A11yNodeBuilder
  • From props: A11yNodeProps, CheckedState, LiveSetting, TextCaret, TextSelection, TextRunChild, Toggled3, character_lengths_utf8, byte_offset_to_char_index
  • From action: A11yAction, ActionDispatcher, map_action
  • From focus: FocusIndicator, FocusRing
  • From nav: TabOrder, tab_next, tab_prev
  • From dirty: DirtyTracker, Lazy
  • From pool: NodePool

AccessKit version

Built against accesskit 0.24. WidgetRole maps onto accesskit::Role, CheckedState/LiveSetting onto accesskit::Toggled/Live. Only the platform-independent accesskit core crate is required — no accesskit_winit, so this crate stays headless.

Related crates

  • oxiui-core — supplies the widget graph this crate mirrors
  • oxiui — the OxiUI facade
  • oxiui-table — the table widget whose structure build_table_a11y mirrors
  • oxiui-theme — high-contrast palettes paired with OsA11yPrefs

License

Apache-2.0 — COOLJAPAN OU (Team Kitasan)