-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement paint and accessibility passes #522
Changes from all commits
8135172
6975765
2a433d1
936584e
9b057a6
805fec0
94233fe
fba1554
7555bc4
54ba10c
8d9266b
4127c98
b33e09e
44f77ff
4343e3f
2129655
2f0bf41
85daf06
d722aca
1d4f30d
61cfb07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
// Copyright 2024 the Xilem Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use crate::render_root::RenderRoot; | ||
use crate::tree_arena::ArenaMutChildren; | ||
use accesskit::{NodeBuilder, NodeId, TreeUpdate}; | ||
use tracing::debug; | ||
use tracing::info_span; | ||
use tracing::trace; | ||
use vello::kurbo::Rect; | ||
|
||
use crate::passes::recurse_on_children; | ||
use crate::render_root::RenderRootState; | ||
use crate::tree_arena::ArenaMut; | ||
use crate::{AccessCtx, Widget, WidgetState}; | ||
|
||
fn build_accessibility_tree( | ||
global_state: &mut RenderRootState, | ||
tree_update: &mut TreeUpdate, | ||
mut widget: ArenaMut<'_, Box<dyn Widget>>, | ||
mut state: ArenaMut<'_, WidgetState>, | ||
rebuild_all: bool, | ||
scale_factor: f64, | ||
) { | ||
let _span = widget.item.make_trace_span().entered(); | ||
let id = state.item.id; | ||
|
||
if !rebuild_all && !state.item.needs_accessibility { | ||
return; | ||
} | ||
|
||
if rebuild_all || state.item.request_accessibility { | ||
trace!( | ||
"Building accessibility node for widget '{}' #{}", | ||
widget.item.short_type_name(), | ||
id.to_raw(), | ||
); | ||
let current_node = build_access_node( | ||
widget.item, | ||
state.item, | ||
state.children.reborrow_mut(), | ||
scale_factor, | ||
); | ||
|
||
let mut ctx = AccessCtx { | ||
global_state, | ||
widget_state: state.item, | ||
widget_state_children: state.children.reborrow_mut(), | ||
widget_children: widget.children.reborrow_mut(), | ||
tree_update, | ||
current_node, | ||
rebuild_all, | ||
scale_factor, | ||
}; | ||
widget.item.accessibility(&mut ctx); | ||
|
||
let id: NodeId = ctx.widget_state.id.into(); | ||
trace!( | ||
"Built node #{} with role={:?}, default_action={:?}", | ||
id.0, | ||
ctx.current_node.role(), | ||
ctx.current_node.default_action_verb(), | ||
); | ||
ctx.tree_update.nodes.push((id, ctx.current_node.build())); | ||
} | ||
|
||
state.item.request_accessibility = false; | ||
state.item.needs_accessibility = false; | ||
Comment on lines
+67
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose that since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume that by "these methods" you mean "the methods that gave me problems in the layout PR"? To be honest, I haven't decided on (or documented) a principled solution for that class of problem. I'm mostly deferring it until all passes are implemented. But yeah, your reasoning makes sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, I meant |
||
|
||
let id = state.item.id; | ||
let parent_state = state.item; | ||
recurse_on_children( | ||
id, | ||
widget.reborrow_mut(), | ||
state.children, | ||
|widget, mut state| { | ||
// TODO - We skip updating stashed items. | ||
// This may have knock-on effects we'd need to document. | ||
if state.item.is_stashed { | ||
return; | ||
} | ||
build_accessibility_tree( | ||
global_state, | ||
tree_update, | ||
widget, | ||
state.reborrow_mut(), | ||
rebuild_all, | ||
scale_factor, | ||
); | ||
parent_state.merge_up(state.item); | ||
}, | ||
); | ||
} | ||
|
||
fn build_access_node( | ||
widget: &dyn Widget, | ||
state: &WidgetState, | ||
state_children: ArenaMutChildren<'_, WidgetState>, | ||
scale_factor: f64, | ||
) -> NodeBuilder { | ||
let mut node = NodeBuilder::new(widget.accessibility_role()); | ||
node.set_bounds(to_accesskit_rect(state.window_layout_rect(), scale_factor)); | ||
|
||
// TODO - We skip listing stashed items. | ||
// This may have knock-on effects we'd need to document. | ||
node.set_children( | ||
widget | ||
.children_ids() | ||
.iter() | ||
.copied() | ||
.filter(|id| { | ||
!state_children | ||
.get_child(id.to_raw()) | ||
.unwrap() | ||
.item | ||
.is_stashed | ||
}) | ||
.map(|id| id.into()) | ||
.collect::<Vec<NodeId>>(), | ||
); | ||
|
||
if state.is_hot { | ||
node.set_hovered(); | ||
} | ||
if state.is_disabled() { | ||
node.set_disabled(); | ||
} | ||
if state.is_stashed { | ||
node.set_hidden(); | ||
} | ||
if state.clip.is_some() { | ||
node.set_clips_children(); | ||
} | ||
|
||
node | ||
} | ||
|
||
fn to_accesskit_rect(r: Rect, scale_factor: f64) -> accesskit::Rect { | ||
let s = scale_factor; | ||
accesskit::Rect::new(s * r.x0, s * r.y0, s * r.x1, s * r.y1) | ||
} | ||
|
||
// ---------------- | ||
|
||
pub(crate) fn root_accessibility( | ||
root: &mut RenderRoot, | ||
rebuild_all: bool, | ||
scale_factor: f64, | ||
) -> TreeUpdate { | ||
let _span = info_span!("accessibility").entered(); | ||
|
||
let mut tree_update = TreeUpdate { | ||
nodes: vec![], | ||
tree: None, | ||
focus: root.state.focused_widget.unwrap_or(root.root.id()).into(), | ||
}; | ||
|
||
let (root_widget, root_state) = { | ||
let widget_id = root.root.id(); | ||
let widget = root | ||
.widget_arena | ||
.widgets | ||
.find_mut(widget_id.to_raw()) | ||
.expect("root_accessibility: root not in widget tree"); | ||
let state = root | ||
.widget_arena | ||
.widget_states | ||
.find_mut(widget_id.to_raw()) | ||
.expect("root_accessibility: root state not in widget tree"); | ||
(widget, state) | ||
}; | ||
|
||
if rebuild_all { | ||
debug!("Running ACCESSIBILITY pass with rebuild_all"); | ||
} | ||
|
||
build_accessibility_tree( | ||
&mut root.state, | ||
&mut tree_update, | ||
root_widget, | ||
root_state, | ||
rebuild_all, | ||
scale_factor, | ||
); | ||
|
||
tree_update | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen this repeated at least three times. It may make sense to create a function just for marking the state change for needing paint and accessibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to keep these kinds of refactor for last, especially if we're only factoring three lines or less. I think there's a benefit to being able to see directly what the code does instead of jumping to another function.