open-gpui 0.1.0

Open GPUI's GPU-accelerated UI framework forked from Zed GPUI.
Documentation
//! # Accessibility in GPUI
//!
//! "Accessibility" refers to the ability of your application to be used by all
//! users, regardless of disability status. There are many aspects, all important, including:
//! - Ensuring sufficient text contrast.
//! - Providing a mechanism to disable animations.
//! - Providing a mechanism to increase text sizes.
//! - etc.
//!
//! This guide is focused on **programmatic accessibility**. This allows
//! assistive technology, such as screen readers or Braille displays, to inspect
//! and interact with your app.
//!
//! GPUI integrates with [AccessKit] to provide programmatic accessibility
//! features (referred to as simply "accessibility" for the rest of this guide).
//!
//! A minimal example can be found in the `examples/a11y` directory.
//!
//! ## Background
//!
//! Accessibility support is based on two key capabilities:
//! - Exposing information about the current UI state to assistive technology.
//! - Responding to actions requested by assistive technology.
//!
//! For example, a screen reader might want to announce to the user that a new
//! button has appeared. The user may then want to use a voice control program
//! to press that button.
//!
//! ### IDs in GPUI - [`ElementId`] and [`GlobalElementId`]
//!
//! In GPUI, each [`Element`] can have an [`id`][Element::id]:
//! ```rust
//! # use open_gpui::*;
//! let div_with_id = div().id("my-id").child(text!("hello"));
//!
//! // IDs are optional
//! let div_without_id = div().child(text!("hello"));
//! ```
//!
//! [`Element`]s with IDs are also assigned a [`GlobalElementId`]. This global
//! ID is formed by composing all the non-`None` IDs of its ancestors. For
//! example:
//! ```rust
//! # use open_gpui::*;
//! let inner = div().id("inner-id");
//! let middle = div().child(inner);  // no ID
//! let outer = div().id("outer-id").child(middle);
//! ```
//! In this example, `inner`s global ID is (roughly speaking) `["outer-id",
//! "inner-id"]`.
//!
//! Since `middle` doesn't have an ID itself, it has no global ID.
//!
//! [`GlobalElementId`]s should be unique per-frame. Duplicate global IDs in the
//! same frame will likely cause bugs.
//!
//! ### IDs and accessibility
//!
//! When GPUI renders a frame, it walks your UI tree, and finds nodes with
//! global IDs, and informs assistive technology about this node.
//!
//! In order for nodes to be reported, they must also have a non-`None`
//! [`role`][Element::a11y_role]. This is used to inform assistive technology
//! what *sort* of node it is (button, label, table, etc.). You can use
//! [`div().id(...).role()`][StatefulInteractiveElement::role] to set the role.
//!
//! Nodes with the same global ID *across frames* are considered to be "the
//! same" node. For example:
//! ```rust
//! # use open_gpui::*;
//! // The UI in frame 1
//! let frame_1 = div()
//!     .id("parent")
//!     .role(Role::Button)
//!     .child(
//!       div()
//!         .id("id-1")
//!         .role(Role::Label)
//!         .child(text!("hello"))
//!     );
//!
//! // The UI on the next frame
//! let frame_2 = div()
//!     .id("parent")
//!     .role(Role::Button)
//!     .child(
//!       div()
//!         .id("id-2")  // <- different ID
//!         .role(Role::Label)
//!         .child(text!("hello"))
//!     );
//! ```
//! Logically, the UI has not changed. But the screen reader has no way of
//! knowing that both child [`div`]s are "the same". So assistive technology
//! will interpret this as one node being removed, and another node being added.
//! This can be very disorienting for users, since announcements typically only
//! happen when something has *meaningfully* changed.
//!
//! In other words, by controlling the ID of an element, you can control whether
//! a change to a UI element is considered meaningful. You can also control
//! whether elements are reported to assistive technology *at all* by setting
//! the [`role`][Element::a11y_role], since nodes with no role are not reported.
//!
//! #### IDs and text
//!
//! Special care must be taken when dealing with text.
//!
//! GPUI provides the [`text!`] macro, which wraps strings in the [`Text`] type,
//! but automatically derives an ID. Usually, this is what you want. However,
//! the way it generates its ID is subtle and perhaps surprising.
//!
//! The ID of an invocation of the [`text!`] macro is derived from the
//! **location in the source code of that invocation**. For example:
//!
//! ```rust
//! # use open_gpui::*;
//! let a = text!("a");
//! let b = text!("b");
//!
//! // Different source locations, different IDs
//! assert_ne!(a.id(), b.id());
//!
//! // However:
//!
//! fn make_text(s: &str) -> Text { text!(s) }
//!
//! let a = make_text("a");
//! let b = make_text("b");
//!
//! // Both `a` and `b` are produced by the same `text!` invocation, so the IDs
//! // are the same
//! assert_eq!(a.id(), b.id());
//! ```
//! This can produce surprising behaviour. For example, this footgun:
//! ```rust
//! # use open_gpui::*;
//! let todos = vec!["eat lunch", "drink water", "go to gym"];
//! let todo_divs = todos.into_iter().map(|todo| {
//!     text!(todo)
//! });
//!
//! div()
//!     .id("todo-list")
//!     .role(Role::Document)
//!     .children(todo_divs);  // ERROR: multiple nodes with the same global ID
//! ```
//!
//! Here, when we map the iterator, since we have only written [`text!`] once,
//! there is only one ID. And since they have the same ancestors and the same
//! ID, they will have the same global ID. In release builds, this will mean
//! some nodes get silently dropped!
//!
//! To fix this, you can set an ID:
//! ```rust
//! # use open_gpui::*;
//! let todos = vec!["eat lunch", "drink water", "go to gym"];
//! let todo_divs = todos.into_iter().enumerate().map(|(index, todo)| {
//!     text!(todo).with_id(index)  // OR `text(id = index, todo)`
//! });
//!
//! div()
//!     .id("todo-list")
//!     .role(Role::Document)
//!     .children(todo_divs);
//! ```
//! Another possible solution is to wrap the [`text!`] in another node that
//! *does* have a unique global ID. For example:
//! ```rust
//! # use open_gpui::*;
//! let todos = vec!["eat lunch", "drink water", "go to gym"];
//! let todo_divs = todos.into_iter().enumerate().map(|(index, todo)| {
//!     div().id(index).child(text!(todo))
//! });
//!
//! div()
//!     .id("todo-list")
//!     .role(Role::Document)
//!     .children(todo_divs);
//! ```
//! Since the AccessKit [`NodeId`][accesskit::NodeId] is derived from the global
//! ID, and the global ID takes into account the IDs of all ancestors, this
//! works too.
//!
//! Occasionally, you will need to create a [`Text`] element with *no* ID. You
//! can achieve this with [`Text::new_inaccessible`]. If you are creating a
//! custom UI component (e.g. a button), you may want this so that you can set a
//! label property on a parent [`div`] without duplicating the text in the
//! accessibility tree.
//!
//! ### Handling actions
//!
//! Assistive technology can dispatch actions to the UI. While many users of
//! assistive technology use traditional input devices (e.g. a keyboard), some
//! use more specialized systems. For example, users with limited mobility may
//! use voice control to interact with your app.
//!
//! When a user dispatches an action, it is dispatched *to a specific node*. It
//! is your responsibility to tell the UI elements how they should respond when
//! a request comes in.
//!
//! Note, these actions are **totally unrelated** to GPUI's [`Action`] trait.
//! AccessKit exposes [`accesskit::Action`]. In GPUI, this is re-exported as
//! [`AccessibleAction`].
//!
//! To respond to an accessible action, use
//! [`div().on_a11y_action()`][InteractiveElement::on_a11y_action]:
//! ```rust,ignore
//! div()
//!     .id("my-slider")
//!     .role(Role::Slider)
//!     .on_a11y_action(AccessibleAction::Increment, |_extra, _window, _cx| {
//!         position += 1;
//!         cx.notify();
//!     })
//!     .child(my_cool_slider());
//! ```
//!
//! Note that some common actions are automatically registered. For example,
//! [`.on_click()`][StatefulInteractiveElement::on_click] adds an
//! [`AccessibleAction::Click`] handler that calls the click handler.
//!
//! ## Further reading
//!
//! Designing high-quality accessible interfaces can be challenging, in the same
//! way that designing high-quality traditional interfaces can be. The
//! following pages have useful information:
//!
//! - [AccessKit]: The cross-platform accessibility toolkit GPUI uses
//!   internally.
//! - [MDN WAI-ARIA basics][mdn-aria]: Introduction to roles, properties, and
//!   states.
//! - [ARIA Authoring Practices Guide][apg]: W3C patterns for accessible
//!   widgets.
//!
//! Note that, while GPUI mimics web APIs, it doesn't necessarily behave
//! *exactly* as a web browser would with the same attributes.
//!
//! [AccessKit]: https://accesskit.dev/
//! [mdn-aria]: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/WAI-ARIA_basics
//! [apg]: https://www.w3.org/WAI/ARIA/apg/

#[cfg(doc)]
use crate::*; // so I don't have to qualify every type :)