-
Notifications
You must be signed in to change notification settings - Fork 117
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
Accessibility for text #615
Conversation
I'm doing this as a draft for now because it's a bit quick and dirty, doesn't yet expose the bounding rectangles of runs and the relative positions and widths of characters (though AccessKit is designed to degrade gracefully in the absence of this information), isn't optimized, and might violate layering in a way we don't want. |
Forgot to mention: This is known to work on Windows, when editing or moving in a textbox and when using NVDA's review commands within a prose widget like the one in the mason example. |
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 not run this, but at first glance this code looks really good!
One thing which does concern me is that any accessibility events targeted at the individual text runs won't get routed correctly. I think it is reasonable to not have a one-to-one widget to accessibility node mapping, but I don't know how to represent that in our architecture (without a side-channel mapping of accessibility ids to widgets).
masonry/src/text/layout.rs
Outdated
@@ -501,6 +516,89 @@ impl<T: AsRef<str> + Eq> TextLayout<T> { | |||
&self.layout, | |||
); | |||
} | |||
|
|||
pub fn accessibility(&mut self, ctx: &mut AccessCtx, parent_node: &mut NodeBuilder) { |
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.
As you predicted, this using AccessCtx
is a slight layering break. I think for expedience this is fine, although I think we could also pass in the individual fields (I think only tree_update
is used?)
FOrtunately, AccessKit never sends action requests to the individual runs. I'll address the rest of the comments later. |
Also to clarify a previous comment, this code should be as functional on macOS and Linux as it is on Windows. I just haven't tested on those platforms yet. |
I wonder if I should move this out of draft and plan to land it without filling in the positions of the runs and characters for now. FWIW, one non-Rust toolkit, and its flagship application, has been using AccessKit without providing that info, and that has been in the hands of real users for a full year now. |
Also, I left in a TODO comment speculating about adding a trailing newline to the run node if it's at the end of a line that's not the last line in the layout. But if the prose in the mason example is a reliable indication, the last (or only) run in the last line of a paragraph that's not the last paragraph in the text already does include the trailing |
Taking this out of draft because we agreed we'd land this PR without bounding boxes for runs and positions/widths of characters. |
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.
Overall, I'm not very comfortable with this code being added as-is. Aside from the layering break, which shouldn't be too hard to address, this is very logic-intensive code, so to speak, with what seems like a lot of implicit invariants and no comments documenting them.
I'm especially uncomfortable with the three hashmap fields. They seem like the could be prone to cache invalidation errors if we're not careful.
I'm not sure if there's another solution, though. As I understand it, this code performs a one-to-may mapping of "text widget" to "accesskit nodes" that can't really be avoided. Although character_lengths_by_access_id
seems like information that accesskit should already store for us?
masonry/src/text/layout.rs
Outdated
ctx.tree_update.nodes.push((last_id, last_node.build())); | ||
parent_node.push_child(last_id); |
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.
This could probably be done in the caller. TextLayout::Accessibility
could return a Vec<NodeBuilder>
.
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 don't like returning a Vec
because that requires an intermediate allocation. I'll implement Daniel's suggestion of passing in the TreeUpdate
rather than the AccessCtx
.
1775a4e
to
a5264d3
Compare
I believe I've now adequately addressed all review feedback. |
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'm not going to leave an approval since I'll let the prior reviewers determine if their concerns were addressed. VoiceOver on mac seems to work fine with this branch, but I am not an expert on screen readers. Test selection seems to be properly handled by VoiceOver.
@PoignardAzur Please review again when you have time. Thanks. |
Sorry, I was low on energy these last few days. Will review tomorrow. |
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.
Overall, I'm still uncomfortable with the architecture and the number of invariants it seems to maintain, but I don't have any alternatives.
I don't see any blockers. This can be merged as soon as the code is rebased.
if !(range.contains(&offset) | ||
|| (offset == range.end | ||
&& (affinity == Affinity::Upstream | ||
|| line_index == self.layout.layout.len() - 1))) |
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 wonder if we could add a function in Parley which does this check.
node.set_read_only(); | ||
self.text_layout.accessibility(ctx.tree_update, node); |
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.
Any reason the same code isn't used in Label::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.
Yes, we ought to do this for labels too. I'd like to wait and do that after I do an AccessKit refactor so we don't have to also set the name property on the label. I don't think that should block merging this PR.
…indows adapter apparently doesn't check whether the node claims to support the action.
…is supported. Support that action in Prose.
…end of the text. That was especially fragile because it assumed that the parent node has the run nodes as its direct children, and those were its only children. These things might change soon.
…, as we do in response to a mouse event
d0107d2
to
627b27b
Compare
Co-authored-by: Olivier FAURE <[email protected]>
@PoignardAzur Requesting final approval after my last changes. |
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.
LGTM.
This adds the necessary AccessKit properties and child nodes to expose detailed information about text widgets. It also supports the
SetTextSelection
action.