Skip to content
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

Merged
merged 18 commits into from
Oct 16, 2024
Merged

Accessibility for text #615

merged 18 commits into from
Oct 16, 2024

Conversation

mwcampbell
Copy link
Contributor

This adds the necessary AccessKit properties and child nodes to expose detailed information about text widgets. It also supports the SetTextSelection action.

@mwcampbell
Copy link
Contributor Author

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.

@mwcampbell
Copy link
Contributor Author

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.

Copy link
Member

@DJMcNab DJMcNab left a 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/selection.rs Show resolved Hide resolved
masonry/src/widget/prose.rs Show resolved Hide resolved
masonry/src/widget/prose.rs Show resolved Hide resolved
@@ -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) {
Copy link
Member

@DJMcNab DJMcNab Sep 27, 2024

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?)

@mwcampbell
Copy link
Contributor Author

FOrtunately, AccessKit never sends action requests to the individual runs.

I'll address the rest of the comments later.

@mwcampbell
Copy link
Contributor Author

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.

@mwcampbell
Copy link
Contributor Author

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.

@mwcampbell
Copy link
Contributor Author

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 \n, and blank paragraphs have a single run with nothing but the \n. Are there any caveats I need to watch out for here?

@mwcampbell
Copy link
Contributor Author

Taking this out of draft because we agreed we'd land this PR without bounding boxes for runs and positions/widths of characters.

@mwcampbell mwcampbell marked this pull request as ready for review October 3, 2024 16:13
Copy link
Contributor

@PoignardAzur PoignardAzur left a 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 Show resolved Hide resolved
Comment on lines 547 to 548
ctx.tree_update.nodes.push((last_id, last_node.build()));
parent_node.push_child(last_id);
Copy link
Contributor

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>.

Copy link
Contributor Author

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.

masonry/src/text/selection.rs Outdated Show resolved Hide resolved
@mwcampbell
Copy link
Contributor Author

I believe I've now adequately addressed all review feedback.

Copy link
Contributor

@jaredoconnell jaredoconnell left a 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.

@mwcampbell
Copy link
Contributor Author

@PoignardAzur Please review again when you have time. Thanks.

@PoignardAzur
Copy link
Contributor

Sorry, I was low on energy these last few days. Will review tomorrow.

Copy link
Contributor

@PoignardAzur PoignardAzur left a 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.

Comment on lines +276 to +301
if !(range.contains(&offset)
|| (offset == range.end
&& (affinity == Affinity::Upstream
|| line_index == self.layout.layout.len() - 1)))
Copy link
Contributor

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.

masonry/src/text/selection.rs Outdated Show resolved Hide resolved
masonry/src/widget/prose.rs Outdated Show resolved Hide resolved
Comment on lines +291 to +289
node.set_read_only();
self.text_layout.accessibility(ctx.tree_update, node);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@mwcampbell
Copy link
Contributor Author

@PoignardAzur Requesting final approval after my last changes.

Copy link
Contributor

@PoignardAzur PoignardAzur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@mwcampbell mwcampbell added this pull request to the merge queue Oct 16, 2024
Merged via the queue into main with commit c73beee Oct 16, 2024
17 checks passed
@mwcampbell mwcampbell deleted the text-access branch October 16, 2024 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants