Final planned refactor: a more flexible approach to hierarchy
This commit is contained in:
parent
ae3a982f47
commit
87d3a90718
6 changed files with 115 additions and 77 deletions
|
@ -111,6 +111,7 @@ impl ContextImpl {
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if self.is_accesskit_enabled {
|
if self.is_accesskit_enabled {
|
||||||
|
use crate::frame_state::AccessKitFrameState;
|
||||||
let id = crate::accesskit_root_id();
|
let id = crate::accesskit_root_id();
|
||||||
let node = Box::new(accesskit::Node {
|
let node = Box::new(accesskit::Node {
|
||||||
role: accesskit::Role::Window,
|
role: accesskit::Role::Window,
|
||||||
|
@ -121,7 +122,10 @@ impl ContextImpl {
|
||||||
});
|
});
|
||||||
let mut nodes = IdMap::default();
|
let mut nodes = IdMap::default();
|
||||||
nodes.insert(id, node);
|
nodes.insert(id, node);
|
||||||
self.frame_state.accesskit_nodes = Some(nodes);
|
self.frame_state.accesskit_state = Some(AccessKitFrameState {
|
||||||
|
nodes,
|
||||||
|
parent_stack: vec![id],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,8 +156,9 @@ impl ContextImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
fn accesskit_node(&mut self, id: Id, parent_id: Option<Id>) -> &mut accesskit::Node {
|
fn accesskit_node(&mut self, id: Id) -> &mut accesskit::Node {
|
||||||
let nodes = self.frame_state.accesskit_nodes.as_mut().unwrap();
|
let state = self.frame_state.accesskit_state.as_mut().unwrap();
|
||||||
|
let nodes = &mut state.nodes;
|
||||||
// We have to override clippy's map_entry lint here, because the
|
// We have to override clippy's map_entry lint here, because the
|
||||||
// insertion path also modifies another entry, to establish
|
// insertion path also modifies another entry, to establish
|
||||||
// the parent/child relationship. Using `HashMap::entry` here
|
// the parent/child relationship. Using `HashMap::entry` here
|
||||||
|
@ -161,8 +166,8 @@ impl ContextImpl {
|
||||||
#[allow(clippy::map_entry)]
|
#[allow(clippy::map_entry)]
|
||||||
if !nodes.contains_key(&id) {
|
if !nodes.contains_key(&id) {
|
||||||
nodes.insert(id, Default::default());
|
nodes.insert(id, Default::default());
|
||||||
let parent_id = parent_id.unwrap_or_else(crate::accesskit_root_id);
|
let parent_id = state.parent_stack.last().unwrap();
|
||||||
let parent = nodes.get_mut(&parent_id).unwrap();
|
let parent = nodes.get_mut(parent_id).unwrap();
|
||||||
parent.children.push(id.accesskit_id());
|
parent.children.push(id.accesskit_id());
|
||||||
}
|
}
|
||||||
nodes.get_mut(&id).unwrap()
|
nodes.get_mut(&id).unwrap()
|
||||||
|
@ -476,7 +481,7 @@ impl Context {
|
||||||
// Make sure anything that can receive focus has an AccessKit node.
|
// Make sure anything that can receive focus has an AccessKit node.
|
||||||
// TODO(mwcampbell): For nodes that are filled from widget info,
|
// TODO(mwcampbell): For nodes that are filled from widget info,
|
||||||
// some information is written to the node twice.
|
// some information is written to the node twice.
|
||||||
if let Some(mut node) = self.accesskit_node(id, None) {
|
if let Some(mut node) = self.accesskit_node(id) {
|
||||||
response.fill_accesskit_node_common(&mut node);
|
response.fill_accesskit_node_common(&mut node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1038,12 +1043,13 @@ impl Context {
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
{
|
{
|
||||||
let nodes = self.frame_state().accesskit_nodes.take();
|
let state = self.frame_state().accesskit_state.take();
|
||||||
if let Some(nodes) = nodes {
|
if let Some(state) = state {
|
||||||
let has_focus = self.input().raw.has_focus;
|
let has_focus = self.input().raw.has_focus;
|
||||||
let root_id = crate::accesskit_root_id().accesskit_id();
|
let root_id = crate::accesskit_root_id().accesskit_id();
|
||||||
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
|
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
|
||||||
nodes: nodes
|
nodes: state
|
||||||
|
.nodes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(id, node)| (id.accesskit_id(), Arc::from(node)))
|
.map(|(id, node)| (id.accesskit_id(), Arc::from(node)))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -1575,20 +1581,43 @@ impl Context {
|
||||||
|
|
||||||
/// ## Accessibility
|
/// ## Accessibility
|
||||||
impl Context {
|
impl Context {
|
||||||
|
/// Call the provided function with the given ID pushed on the stack of
|
||||||
|
/// parent IDs for accessibility purposes. If the `accesskit` feature
|
||||||
|
/// is disabled or if AccessKit support is not active for this frame,
|
||||||
|
/// the function is still called, but with no other effect.
|
||||||
|
pub fn with_accessibility_parent(&self, id: Id, f: impl FnOnce()) {
|
||||||
|
#[cfg(feature = "accesskit")]
|
||||||
|
{
|
||||||
|
let mut frame_state = self.frame_state();
|
||||||
|
if let Some(state) = frame_state.accesskit_state.as_mut() {
|
||||||
|
state.parent_stack.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "accesskit"))]
|
||||||
|
{
|
||||||
|
let _ = id;
|
||||||
|
}
|
||||||
|
f();
|
||||||
|
#[cfg(feature = "accesskit")]
|
||||||
|
{
|
||||||
|
let mut frame_state = self.frame_state();
|
||||||
|
if let Some(state) = frame_state.accesskit_state.as_mut() {
|
||||||
|
assert_eq!(state.parent_stack.pop(), Some(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// If AccessKit support is active for the current frame, get or create
|
/// If AccessKit support is active for the current frame, get or create
|
||||||
/// a node with the specified ID and return a mutable reference to it.
|
/// a node with the specified ID and return a mutable reference to it.
|
||||||
/// `parent_id` is ignored if the node already exists.
|
/// For newly crated nodes, the parent is the node with the ID at the top
|
||||||
|
/// of the stack managed by [`Context::with_accessibility_parent`].
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
pub fn accesskit_node(
|
pub fn accesskit_node(&self, id: Id) -> Option<RwLockWriteGuard<'_, accesskit::Node>> {
|
||||||
&self,
|
|
||||||
id: Id,
|
|
||||||
parent_id: Option<Id>,
|
|
||||||
) -> Option<RwLockWriteGuard<'_, accesskit::Node>> {
|
|
||||||
let ctx = self.write();
|
let ctx = self.write();
|
||||||
ctx.frame_state
|
ctx.frame_state
|
||||||
.accesskit_nodes
|
.accesskit_state
|
||||||
.is_some()
|
.is_some()
|
||||||
.then(move || RwLockWriteGuard::map(ctx, |c| c.accesskit_node(id, parent_id)))
|
.then(move || RwLockWriteGuard::map(ctx, |c| c.accesskit_node(id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable generation of AccessKit tree updates in all future frames.
|
/// Enable generation of AccessKit tree updates in all future frames.
|
||||||
|
|
|
@ -9,6 +9,13 @@ pub(crate) struct TooltipFrameState {
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "accesskit")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct AccessKitFrameState {
|
||||||
|
pub(crate) nodes: IdMap<Box<accesskit::Node>>,
|
||||||
|
pub(crate) parent_stack: Vec<Id>,
|
||||||
|
}
|
||||||
|
|
||||||
/// State that is collected during a frame and then cleared.
|
/// State that is collected during a frame and then cleared.
|
||||||
/// Short-term (single frame) memory.
|
/// Short-term (single frame) memory.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -43,7 +50,7 @@ pub(crate) struct FrameState {
|
||||||
pub(crate) scroll_target: [Option<(RangeInclusive<f32>, Option<Align>)>; 2],
|
pub(crate) scroll_target: [Option<(RangeInclusive<f32>, Option<Align>)>; 2],
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
pub(crate) accesskit_nodes: Option<IdMap<Box<accesskit::Node>>>,
|
pub(crate) accesskit_state: Option<AccessKitFrameState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FrameState {
|
impl Default for FrameState {
|
||||||
|
@ -57,7 +64,7 @@ impl Default for FrameState {
|
||||||
scroll_delta: Vec2::ZERO,
|
scroll_delta: Vec2::ZERO,
|
||||||
scroll_target: [None, None],
|
scroll_target: [None, None],
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
accesskit_nodes: None,
|
accesskit_state: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +80,7 @@ impl FrameState {
|
||||||
scroll_delta,
|
scroll_delta,
|
||||||
scroll_target,
|
scroll_target,
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
accesskit_nodes,
|
accesskit_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
used_ids.clear();
|
used_ids.clear();
|
||||||
|
@ -85,7 +92,7 @@ impl FrameState {
|
||||||
*scroll_target = [None, None];
|
*scroll_target = [None, None];
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
{
|
{
|
||||||
*accesskit_nodes = None;
|
*accesskit_state = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -529,7 +529,7 @@ impl Response {
|
||||||
self.output_event(event);
|
self.output_event(event);
|
||||||
} else {
|
} else {
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if let Some(mut node) = self.ctx.accesskit_node(self.id, None) {
|
if let Some(mut node) = self.ctx.accesskit_node(self.id) {
|
||||||
self.fill_accesskit_node_from_widget_info(&mut node, make_info());
|
self.fill_accesskit_node_from_widget_info(&mut node, make_info());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -537,7 +537,7 @@ impl Response {
|
||||||
|
|
||||||
pub fn output_event(&self, event: crate::output::OutputEvent) {
|
pub fn output_event(&self, event: crate::output::OutputEvent) {
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if let Some(mut node) = self.ctx.accesskit_node(self.id, None) {
|
if let Some(mut node) = self.ctx.accesskit_node(self.id) {
|
||||||
self.fill_accesskit_node_from_widget_info(&mut node, event.widget_info().clone());
|
self.fill_accesskit_node_from_widget_info(&mut node, event.widget_info().clone());
|
||||||
}
|
}
|
||||||
self.ctx.output().events.push(event);
|
self.ctx.output().events.push(event);
|
||||||
|
@ -606,7 +606,7 @@ impl Response {
|
||||||
/// Associate a label with a control for accessibility.
|
/// Associate a label with a control for accessibility.
|
||||||
pub fn labelled_by(self, id: Id) -> Self {
|
pub fn labelled_by(self, id: Id) -> Self {
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if let Some(mut node) = self.ctx.accesskit_node(self.id, None) {
|
if let Some(mut node) = self.ctx.accesskit_node(self.id) {
|
||||||
node.labelled_by.push(id.accesskit_id());
|
node.labelled_by.push(id.accesskit_id());
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "accesskit"))]
|
#[cfg(not(feature = "accesskit"))]
|
||||||
|
|
|
@ -541,7 +541,7 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
response.widget_info(|| WidgetInfo::drag_value(value));
|
response.widget_info(|| WidgetInfo::drag_value(value));
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if let Some(mut node) = ui.ctx().accesskit_node(response.id, None) {
|
if let Some(mut node) = ui.ctx().accesskit_node(response.id) {
|
||||||
use accesskit::Action;
|
use accesskit::Action;
|
||||||
// If either end of the range is unbounded, it's better
|
// If either end of the range is unbounded, it's better
|
||||||
// to leave the corresponding AccessKit field set to None,
|
// to leave the corresponding AccessKit field set to None,
|
||||||
|
|
|
@ -740,7 +740,7 @@ impl<'a> Slider<'a> {
|
||||||
response.widget_info(|| WidgetInfo::slider(value, self.text.text()));
|
response.widget_info(|| WidgetInfo::slider(value, self.text.text()));
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if let Some(mut node) = ui.ctx().accesskit_node(response.id, None) {
|
if let Some(mut node) = ui.ctx().accesskit_node(response.id) {
|
||||||
use accesskit::Action;
|
use accesskit::Action;
|
||||||
node.min_numeric_value = Some(*self.range.start());
|
node.min_numeric_value = Some(*self.range.start());
|
||||||
node.max_numeric_value = Some(*self.range.end());
|
node.max_numeric_value = Some(*self.range.end());
|
||||||
|
|
|
@ -659,7 +659,7 @@ impl<'t> TextEdit<'t> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if let Some(mut node) = ui.ctx().accesskit_node(response.id, None) {
|
if let Some(mut node) = ui.ctx().accesskit_node(response.id) {
|
||||||
use accesskit::{Role, TextDirection, TextPosition, TextSelection};
|
use accesskit::{Role, TextDirection, TextPosition, TextSelection};
|
||||||
|
|
||||||
let parent_id = response.id;
|
let parent_id = response.id;
|
||||||
|
@ -683,9 +683,10 @@ impl<'t> TextEdit<'t> {
|
||||||
|
|
||||||
drop(node);
|
drop(node);
|
||||||
|
|
||||||
|
ui.ctx().with_accessibility_parent(parent_id, || {
|
||||||
for (i, row) in galley.rows.iter().enumerate() {
|
for (i, row) in galley.rows.iter().enumerate() {
|
||||||
let id = parent_id.with(i);
|
let id = parent_id.with(i);
|
||||||
let mut node = ui.ctx().accesskit_node(id, Some(parent_id)).unwrap();
|
let mut node = ui.ctx().accesskit_node(id).unwrap();
|
||||||
node.role = Role::InlineTextBox;
|
node.role = Role::InlineTextBox;
|
||||||
let rect = row.rect.translate(text_draw_pos.to_vec2());
|
let rect = row.rect.translate(text_draw_pos.to_vec2());
|
||||||
node.bounds = Some(accesskit::kurbo::Rect {
|
node.bounds = Some(accesskit::kurbo::Rect {
|
||||||
|
@ -738,6 +739,7 @@ impl<'t> TextEdit<'t> {
|
||||||
node.character_widths = Some(character_widths.into());
|
node.character_widths = Some(character_widths.into());
|
||||||
node.word_lengths = word_lengths.into();
|
node.word_lengths = word_lengths.into();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEditOutput {
|
TextEditOutput {
|
||||||
|
|
Loading…
Reference in a new issue