-
Notifications
You must be signed in to change notification settings - Fork 6k
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
[fuchsia] HitTesting for fuchsia a11y #15570
Changes from 7 commits
1405b2b
3b38ae2
36a8681
b30e145
358ae6c
55d9b9c
4648271
a318f41
b80421e
2d1de23
c502973
f541a08
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 |
---|---|---|
|
@@ -99,8 +99,8 @@ std::unordered_set<int32_t> AccessibilityBridge::GetDescendants( | |
|
||
auto it = nodes_.find(id); | ||
if (it != nodes_.end()) { | ||
auto const& children = it->second; | ||
for (const auto& child : children) { | ||
const auto& node = it->second; | ||
for (const auto& child : node.children_in_hit_test_order) { | ||
if (descendents.find(child) == descendents.end()) { | ||
to_process.push_back(child); | ||
} else { | ||
|
@@ -180,10 +180,18 @@ void AccessibilityBridge::AddSemanticsNodeUpdate( | |
for (const auto& value : update) { | ||
size_t this_node_size = sizeof(fuchsia::accessibility::semantics::Node); | ||
const auto& flutter_node = value.second; | ||
nodes_[flutter_node.id] = | ||
std::vector<int32_t>(flutter_node.childrenInTraversalOrder); | ||
// Store the nodes for later hit testing. | ||
nodes_[flutter_node.id] = { | ||
.id = flutter_node.id, | ||
.flags = flutter_node.flags, | ||
.rect = flutter_node.rect, | ||
.transform = flutter_node.transform, | ||
.children_in_hit_test_order = flutter_node.childrenInHitTestOrder, | ||
}; | ||
fuchsia::accessibility::semantics::Node fuchsia_node; | ||
std::vector<uint32_t> child_ids; | ||
// Send the nodes in traversal order, so the manager can figure out | ||
// traversal. | ||
for (int32_t flutter_child_id : flutter_node.childrenInTraversalOrder) { | ||
child_ids.push_back(FlutterIdToFuchsiaId(flutter_child_id)); | ||
} | ||
|
@@ -221,13 +229,58 @@ void AccessibilityBridge::AddSemanticsNodeUpdate( | |
} | ||
|
||
PruneUnreachableNodes(); | ||
UpdateScreenRects(); | ||
|
||
tree_ptr_->UpdateSemanticNodes(std::move(nodes)); | ||
// TODO(dnfield): Implement the callback here | ||
// https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=35718. | ||
tree_ptr_->CommitUpdates([]() {}); | ||
} | ||
|
||
void AccessibilityBridge::UpdateScreenRects() { | ||
std::unordered_set<int32_t> visited_nodes; | ||
UpdateScreenRects(kRootNodeId, SkMatrix44::I(), &visited_nodes); | ||
} | ||
|
||
void AccessibilityBridge::UpdateScreenRects( | ||
int32_t node_id, | ||
SkMatrix44 parent_transform, | ||
std::unordered_set<int32_t>* visited_nodes) { | ||
auto it = nodes_.find(node_id); | ||
if (it == nodes_.end()) { | ||
FML_LOG(ERROR) << "UpdateScreenRects called on unknown node"; | ||
} | ||
auto& node = it->second; | ||
const auto& current_transform = parent_transform * node.transform; | ||
|
||
const auto& rect = node.rect; | ||
FML_LOG(ERROR) << "nodeid: " << node_id; | ||
SkMScalar quad[] = { | ||
rect.left(), rect.top(), // | ||
rect.right(), rect.top(), // | ||
rect.right(), rect.bottom(), // | ||
rect.left(), rect.bottom(), // | ||
}; | ||
SkMScalar dst[4 * 4]; | ||
current_transform.map2(quad, 4, dst); | ||
node.screen_rect.setLTRB(dst[0], dst[1], dst[8], dst[9]); | ||
node.screen_rect.sort(); | ||
std::vector<SkVector4> points = { | ||
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. This can all be done in a single call to 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. Nice. Done ... I think. Am I picking the right indices? 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. lol, I am confused too. But these look right. |
||
current_transform * SkVector4(rect.left(), rect.top(), 0, 1), | ||
current_transform * SkVector4(rect.right(), rect.top(), 0, 1), | ||
current_transform * SkVector4(rect.right(), rect.bottom(), 0, 1), | ||
current_transform * SkVector4(rect.left(), rect.bottom(), 0, 1), | ||
}; | ||
|
||
visited_nodes->emplace(node_id); | ||
|
||
for (uint32_t child_id : node.children_in_hit_test_order) { | ||
if (visited_nodes->find(child_id) == visited_nodes->end()) { | ||
UpdateScreenRects(child_id, current_transform, visited_nodes); | ||
} | ||
} | ||
} | ||
|
||
// |fuchsia::accessibility::semantics::SemanticListener| | ||
void AccessibilityBridge::OnAccessibilityActionRequested( | ||
uint32_t node_id, | ||
|
@@ -239,7 +292,34 @@ void AccessibilityBridge::OnAccessibilityActionRequested( | |
void AccessibilityBridge::HitTest( | ||
fuchsia::math::PointF local_point, | ||
fuchsia::accessibility::semantics::SemanticListener::HitTestCallback | ||
callback) {} | ||
callback) { | ||
auto hit_node_id = GetHitNode(kRootNodeId, local_point.x, local_point.y); | ||
FML_DCHECK(hit_node_id.has_value()); | ||
fuchsia::accessibility::semantics::Hit hit; | ||
hit.set_node_id(hit_node_id.value_or(kRootNodeId)); | ||
callback(std::move(hit)); | ||
} | ||
|
||
std::optional<int32_t> AccessibilityBridge::GetHitNode(int32_t node_id, | ||
float x, | ||
float y) { | ||
auto it = nodes_.find(node_id); | ||
if (it == nodes_.end()) { | ||
FML_LOG(ERROR) << "Attempted to hit test unkonwn node id: " << node_id; | ||
return {}; | ||
} | ||
auto const& node = it->second; | ||
if (node.flags & | ||
static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden) || // | ||
!node.screen_rect.contains(x, y)) { | ||
return {}; | ||
} | ||
auto hit = node_id; | ||
for (int32_t child_id : node.children_in_hit_test_order) { | ||
hit = GetHitNode(child_id, x, y).value_or(hit); | ||
} | ||
return hit; | ||
} | ||
|
||
// |fuchsia::accessibility::semantics::SemanticListener| | ||
void AccessibilityBridge::OnSemanticsModeChanged( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,7 +85,24 @@ class AccessibilityBridge | |
// Notifies the bridge of a 'hover move' touch exploration event. | ||
zx_status_t OnHoverMove(double x, double y); | ||
|
||
// |fuchsia::accessibility::semantics::SemanticListener| | ||
void HitTest( | ||
fuchsia::math::PointF local_point, | ||
fuchsia::accessibility::semantics::SemanticListener::HitTestCallback | ||
callback) override; | ||
|
||
private: | ||
// Holds only the fields we need for hit testing. | ||
// In particular, it adds a screen_rect field to flutter::SemanticsNode. | ||
struct SemanticsNode { | ||
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. Why not just reuse 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. This started as an attempt to avoid holding onto more than we need, but I agree it's less helpful now. I'll change it. 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. Ah. The reason I can't do this is I need the screen_rect, which 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. Thanks for clarifying. |
||
int32_t id; | ||
int32_t flags; | ||
SkRect rect; | ||
SkRect screen_rect; | ||
SkMatrix44 transform; | ||
std::vector<int32_t> children_in_hit_test_order; | ||
}; | ||
|
||
AccessibilityBridge::Delegate& delegate_; | ||
|
||
static constexpr int32_t kRootNodeId = 0; | ||
|
@@ -95,8 +112,8 @@ class AccessibilityBridge | |
fuchsia::accessibility::semantics::SemanticTreePtr tree_ptr_; | ||
bool semantics_enabled_; | ||
// This is the cache of all nodes we've sent to Fuchsia's SemanticsManager. | ||
// Assists with pruning unreachable nodes. | ||
std::unordered_map<int32_t, std::vector<int32_t>> nodes_; | ||
// Assists with pruning unreachable nodes and hit testing. | ||
std::unordered_map<int32_t, SemanticsNode> nodes_; | ||
|
||
// Derives the BoundingBox of a Flutter semantics node from its | ||
// rect and elevation. | ||
|
@@ -127,19 +144,33 @@ class AccessibilityBridge | |
// May result in a call to FuchsiaAccessibility::Commit(). | ||
void PruneUnreachableNodes(); | ||
|
||
// Updates the on-screen positions of accessibility elements, | ||
// starting from the root element with an identity matrix. | ||
// | ||
// This should be called from Update. | ||
void UpdateScreenRects(); | ||
|
||
// Updates the on-screen positions of accessibility elements, starting | ||
// from node_id and using the specified transform. | ||
// | ||
// Update calls this via UpdateScreenRects(). | ||
void UpdateScreenRects(int32_t node_id, | ||
SkMatrix44 parent_transform, | ||
std::unordered_set<int32_t>* visited_nodes); | ||
|
||
// Traverses the semantics tree to find the node_id hit by the given x,y | ||
// point. | ||
// | ||
// Assumes that SemanticsNode::screen_rect is up to date. | ||
std::optional<int32_t> GetHitNode(int32_t node_id, float x, float y); | ||
|
||
// |fuchsia::accessibility::semantics::SemanticListener| | ||
void OnAccessibilityActionRequested( | ||
uint32_t node_id, | ||
fuchsia::accessibility::semantics::Action action, | ||
fuchsia::accessibility::semantics::SemanticListener:: | ||
OnAccessibilityActionRequestedCallback callback) override; | ||
|
||
// |fuchsia::accessibility::semantics::SemanticListener| | ||
void HitTest( | ||
fuchsia::math::PointF local_point, | ||
fuchsia::accessibility::semantics::SemanticListener::HitTestCallback | ||
callback) override; | ||
|
||
// |fuchsia::accessibility::semantics::SemanticListener| | ||
void OnSemanticsModeChanged(bool enabled, | ||
OnSemanticsModeChangedCallback callback) override; | ||
|
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.
Aren't you missing an early return here?
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 - added.