ftui_a11y/lib.rs
1#![forbid(unsafe_code)]
2
3//! Accessibility layer for FrankenTUI.
4//!
5//! # Role in FrankenTUI
6//!
7//! `ftui-a11y` defines the accessibility tree infrastructure that lets
8//! widgets describe themselves to screen readers and other assistive
9//! technology. It is **Phase 1** of the accessibility initiative: the
10//! core types and trait contract.
11//!
12//! # Architecture
13//!
14//! ```text
15//! Widget ──impl Accessible──> Vec<A11yNodeInfo>
16//! │
17//! A11yTreeBuilder::add_node()
18//! │
19//! A11yTreeBuilder::build()
20//! │
21//! A11yTree (immutable snapshot)
22//! │
23//! A11yTree::diff(&prev)
24//! │
25//! A11yTreeDiff
26//! │
27//! screen-reader mirror + live announcements
28//! │
29//! (future: platform bridge)
30//! ```
31//!
32//! # How it fits in the system
33//!
34//! - **`ftui-core`**: provides `Rect` for node bounding boxes.
35//! - **`ftui-widgets`**: widgets will `impl Accessible` in a future phase.
36//! - **`ftui-render`**: the render pass will collect nodes via the builder.
37//! - **Platform bridges** (future Phase 3): consume `A11yTreeDiff`,
38//! screen-reader mirror text, and bounded live announcements to push
39//! updates to AccessKit / platform APIs.
40//!
41//! # Quick start
42//!
43//! ```rust
44//! use ftui_a11y::node::{A11yNodeInfo, A11yRole};
45//! use ftui_a11y::tree::A11yTreeBuilder;
46//! use ftui_core::geometry::Rect;
47//!
48//! // Build a small tree.
49//! let mut builder = A11yTreeBuilder::new();
50//! let root = A11yNodeInfo::new(1, A11yRole::Window, Rect::new(0, 0, 80, 24))
51//! .with_name("My App")
52//! .with_children(vec![2]);
53//! let button = A11yNodeInfo::new(2, A11yRole::Button, Rect::new(10, 5, 8, 1))
54//! .with_name("OK")
55//! .with_parent(1);
56//! builder.add_node(root);
57//! builder.add_node(button);
58//! builder.set_root(1);
59//! builder.set_focused(Some(2));
60//! let tree = builder.build();
61//!
62//! assert_eq!(tree.node_count(), 2);
63//! assert_eq!(tree.focused().unwrap().name.as_deref(), Some("OK"));
64//! ```
65
66pub mod node;
67pub mod preferences;
68pub mod tree;
69
70pub use preferences::{
71 AccessibilityPreferences, ContrastProfile, MotionFilteredAnnouncements, MotionProfile,
72};
73
74use ftui_core::geometry::Rect;
75use node::A11yNodeInfo;
76
77// ── Accessible trait ───────────────────────────────────────────────────
78
79/// Trait for widgets that provide accessibility metadata.
80///
81/// Implementing this trait is **opt-in**. Widgets that do not implement
82/// it are invisible to screen readers (treated as presentational /
83/// decorative).
84///
85/// # Contract
86///
87/// - `accessibility_nodes` must return at least one node for the widget.
88/// - The first node in the returned `Vec` is the widget's "primary"
89/// node; additional nodes represent internal structure (e.g. a table
90/// widget returns the table node plus row/cell nodes).
91/// - Node IDs must be unique within a single render pass. Use a
92/// deterministic scheme (e.g. widget identity hash) to keep IDs
93/// stable across frames for efficient diffing.
94/// - `area` is the bounding rectangle the widget was laid out into.
95///
96/// # Example
97///
98/// ```rust
99/// use ftui_core::geometry::Rect;
100/// use ftui_a11y::Accessible;
101/// use ftui_a11y::node::{A11yNodeInfo, A11yRole};
102///
103/// struct MyButton {
104/// label: String,
105/// id: u64,
106/// }
107///
108/// impl Accessible for MyButton {
109/// fn accessibility_nodes(&self, area: Rect) -> Vec<A11yNodeInfo> {
110/// vec![
111/// A11yNodeInfo::new(self.id, A11yRole::Button, area)
112/// .with_name(&self.label)
113/// ]
114/// }
115/// }
116/// ```
117pub trait Accessible {
118 /// Return accessibility node(s) for this widget at the given bounds.
119 ///
120 /// Container widgets should return a parent node with children IDs,
121 /// plus the child nodes themselves.
122 fn accessibility_nodes(&self, area: Rect) -> Vec<A11yNodeInfo>;
123}