| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ui/a11y/lib/view/flatland_accessibility_view.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/ui/scenic/cpp/view_creation_tokens.h> |
| #include <lib/ui/scenic/cpp/view_identity.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "fuchsia/math/cpp/fidl.h" |
| #include "fuchsia/ui/composition/cpp/fidl.h" |
| #include "fuchsia/ui/views/cpp/fidl.h" |
| #include "lib/fidl/cpp/clone.h" |
| #include "lib/fidl/cpp/interface_request.h" |
| #include "src/ui/a11y/lib/util/util.h" |
| #include "src/ui/a11y/lib/view/view_coordinate_converter.h" |
| |
| namespace a11y { |
| namespace { |
| |
| using fuchsia::ui::composition::ContentId; |
| using fuchsia::ui::composition::LayoutInfo; |
| using fuchsia::ui::composition::PresentArgs; |
| using fuchsia::ui::composition::TransformId; |
| using fuchsia::ui::composition::ViewportProperties; |
| |
| // IDs for the flatland resources. |
| // |
| // The final scene topology is: |
| // a11y view: |
| // a11y view root transform (id=11) |
| // -->magnifier transform (id=12) |
| // -->highlight view holder transform (id=13) {content: highlight viewport id=14} |
| // |
| // highlight view: |
| // highlight view root transform (id=21) |
| // -->proxy viewport transform (id=22) {content: proxy viewport id=23} |
| // -->highlight transform (id=24) [not always attached to the graph!] |
| // -->rectangle transform 0 (id=25) {content: filled rect id=29} [top] |
| // -->rectangle transform 1 (id=26) {content: filled rect id=30} [bottom] |
| // -->rectangle transform 2 (id=27) {content: filled rect id=31} [right] |
| // -->rectangle transform 3 (id=28) {content: filled rect id=32} [left] |
| |
| // Color for accessibility highlights. (0.9131, 0, 0.2423) in linear color space, which is near to |
| // #F50057 (Pink A400) in sRGB space. |
| const fuchsia::ui::composition::ColorRgba kHighlightColor = { |
| .red = 0.9131f, .green = 0.0f, .blue = 0.2423f, .alpha = 1.0f}; |
| |
| // Multiply by 2 to get the width (in logical pixels) of the four rectangles that |
| // constitute the boundaries of the highlight. |
| constexpr int32_t kHighlightHalfThickness = 3; |
| constexpr int32_t kHighlightThickness = kHighlightHalfThickness * 2; |
| |
| constexpr uint64_t kA11yViewRootTransformId = 11; |
| constexpr uint64_t kMagnifierTransformId = 12; |
| constexpr uint64_t kHighlightViewportTransformId = 13; |
| constexpr uint64_t kHighlightViewportContentId = 14; |
| constexpr uint64_t kHighlightViewRootTransformId = 21; |
| constexpr uint64_t kProxyViewportTransformId = 22; |
| constexpr uint64_t kProxyViewportContentId = 23; |
| constexpr uint64_t kHighlightTransformId = 24; |
| |
| constexpr size_t kTopRect = 0; |
| constexpr size_t kBottomRect = 1; |
| constexpr size_t kLeftRect = 2; |
| constexpr size_t kRightRect = 3; |
| constexpr uint64_t kRectangleTransformIds[] = {25, 26, 27, 28}; |
| constexpr uint64_t kRectangleContentIds[] = {29, 30, 31, 32}; |
| |
| fuchsia::math::RectF SizeUToRectFAtOrigin(fuchsia::math::SizeU size) { |
| return fuchsia::math::RectF{.x = 0., |
| .y = 0., |
| .width = static_cast<float>(size.width), |
| .height = static_cast<float>(size.height)}; |
| } |
| |
| // Setup that does not require LayoutInfo. |
| fuchsia::ui::views::ViewRef InitialA11yViewSetup( |
| fuchsia::ui::composition::Flatland* flatland_a11y, |
| fuchsia::ui::views::ViewCreationToken a11y_view_token, fuchsia::ui::views::FocuserPtr& focuser, |
| fidl::InterfaceRequest<fuchsia::ui::pointer::TouchSource> touch_source_server_end, |
| fuchsia::ui::composition::ParentViewportWatcherPtr& parent_watcher) { |
| FX_DCHECK(flatland_a11y); |
| |
| auto view_identity = scenic::NewViewIdentityOnCreation(); |
| // Save its ViewRef to return. |
| auto view_ref = fidl::Clone(view_identity.view_ref); |
| |
| // Set up view-bound protocols for flatland instance. |
| fuchsia::ui::composition::ViewBoundProtocols view_bound_protocols; |
| view_bound_protocols.set_view_focuser(focuser.NewRequest()); |
| view_bound_protocols.set_touch_source(std::move(touch_source_server_end)); |
| |
| // Create a11y view, and set it as the content for the root transform. |
| flatland_a11y->CreateView2(std::move(a11y_view_token), std::move(view_identity), |
| std::move(view_bound_protocols), parent_watcher.NewRequest()); |
| |
| flatland_a11y->CreateTransform(TransformId({.value = kA11yViewRootTransformId})); |
| flatland_a11y->SetRootTransform(TransformId({.value = kA11yViewRootTransformId})); |
| |
| // Create magnifier transform, and attach as a child of the root transform. |
| // Attach highlight viewport transform as a child of magnifier transform. |
| flatland_a11y->CreateTransform(TransformId{.value = kMagnifierTransformId}); |
| flatland_a11y->AddChild(TransformId{.value = kA11yViewRootTransformId}, |
| TransformId{.value = kMagnifierTransformId}); |
| |
| return view_ref; |
| } |
| |
| void FinishA11yViewSetup(fuchsia::ui::composition::Flatland* flatland_a11y, |
| const fuchsia::math::SizeU& logical_size, |
| fuchsia::ui::views::ViewportCreationToken highlight_viewport_token) { |
| FX_DCHECK(flatland_a11y); |
| |
| // Change the default hit region to SEMANTICALLY_INVISIBLE. |
| flatland_a11y->SetHitRegions( |
| TransformId({.value = kA11yViewRootTransformId}), |
| {fuchsia::ui::composition::HitRegion{ |
| SizeUToRectFAtOrigin(logical_size), |
| fuchsia::ui::composition::HitTestInteraction::SEMANTICALLY_INVISIBLE}}); |
| |
| // Create the highlight viewport. |
| fuchsia::ui::composition::ViewportProperties viewport_properties; |
| viewport_properties.set_logical_size(logical_size); |
| { |
| fuchsia::ui::composition::ChildViewWatcherPtr child_view_watcher; |
| flatland_a11y->CreateViewport( |
| ContentId{.value = kHighlightViewportContentId}, std::move(highlight_viewport_token), |
| fidl::Clone(viewport_properties), child_view_watcher.NewRequest()); |
| } |
| |
| // Set up the highlight viewport transform. |
| flatland_a11y->CreateTransform(TransformId{.value = kHighlightViewportTransformId}); |
| flatland_a11y->SetContent(TransformId{.value = kHighlightViewportTransformId}, |
| ContentId{.value = kHighlightViewportContentId}); |
| flatland_a11y->AddChild(TransformId{.value = kMagnifierTransformId}, |
| TransformId{.value = kHighlightViewportTransformId}); |
| } |
| |
| fuchsia::ui::views::ViewRef HighlightViewSetup( |
| fuchsia::ui::composition::Flatland* flatland_highlight, |
| const fuchsia::math::SizeU& logical_size, |
| fuchsia::ui::views::ViewCreationToken highlight_view_token, |
| fuchsia::ui::views::ViewportCreationToken proxy_viewport_token, |
| fuchsia::ui::composition::ParentViewportWatcherPtr& highlight_view_watcher) { |
| FX_DCHECK(flatland_highlight); |
| |
| // Create the highlight view. |
| auto view_identity = scenic::NewViewIdentityOnCreation(); |
| // Save its ViewRef to return. |
| auto view_ref = fidl::Clone(view_identity.view_ref); |
| |
| fuchsia::ui::composition::ViewBoundProtocols view_bound_protocols; |
| flatland_highlight->CreateView2(std::move(highlight_view_token), std::move(view_identity), |
| std::move(view_bound_protocols), |
| highlight_view_watcher.NewRequest()); |
| |
| // Set up the root transform. |
| flatland_highlight->CreateTransform(TransformId({.value = kHighlightViewRootTransformId})); |
| flatland_highlight->SetRootTransform(TransformId({.value = kHighlightViewRootTransformId})); |
| |
| // Clear the default hit region. |
| flatland_highlight->SetHitRegions(TransformId({.value = kHighlightViewRootTransformId}), {}); |
| |
| // Create the proxy viewport. |
| fuchsia::ui::composition::ViewportProperties viewport_properties; |
| viewport_properties.set_logical_size(logical_size); |
| |
| { |
| fuchsia::ui::composition::ChildViewWatcherPtr child_view_watcher; |
| flatland_highlight->CreateViewport( |
| ContentId{.value = kProxyViewportContentId}, std::move(proxy_viewport_token), |
| std::move(viewport_properties), child_view_watcher.NewRequest()); |
| } |
| |
| // Set up the proxy viewport transform. |
| flatland_highlight->CreateTransform(TransformId{.value = kProxyViewportTransformId}); |
| flatland_highlight->SetContent(TransformId{.value = kProxyViewportTransformId}, |
| ContentId{.value = kProxyViewportContentId}); |
| flatland_highlight->AddChild(TransformId{.value = kHighlightViewRootTransformId}, |
| TransformId{.value = kProxyViewportTransformId}); |
| |
| // Set up the highlight transform and its children. |
| // Note that we do *not* add it to the scene, we'll only do that when a highlight is drawn. |
| flatland_highlight->CreateTransform(TransformId{.value = kHighlightTransformId}); |
| |
| for (int i = 0; i < 4; i++) { |
| auto transform_id = TransformId{.value = kRectangleTransformIds[i]}; |
| auto content_id = ContentId{.value = kRectangleContentIds[i]}; |
| |
| flatland_highlight->CreateTransform(transform_id); |
| flatland_highlight->AddChild(TransformId{.value = kHighlightTransformId}, transform_id); |
| |
| flatland_highlight->CreateFilledRect(content_id); |
| flatland_highlight->SetContent(transform_id, content_id); |
| } |
| |
| return view_ref; |
| } |
| |
| bool InvokeViewPropertiesChangedCallback( |
| const LayoutInfo& layout_info, |
| const FlatlandAccessibilityView::ViewPropertiesChangedCallback& callback) { |
| ViewportProperties viewport_properties; |
| viewport_properties.set_logical_size(fidl::Clone(layout_info.logical_size())); |
| return callback(viewport_properties); |
| } |
| |
| void InvokeViewPropertiesChangedCallbacks( |
| const LayoutInfo& layout_info, |
| std::vector<FlatlandAccessibilityView::ViewPropertiesChangedCallback>* callbacks) { |
| auto it = callbacks->begin(); |
| while (it != callbacks->end()) { |
| if (InvokeViewPropertiesChangedCallback(layout_info, *it)) { |
| it++; |
| } else { |
| it = callbacks->erase(it); |
| } |
| } |
| } |
| |
| void InvokeSceneReadyCallbacks( |
| std::vector<FlatlandAccessibilityView::SceneReadyCallback>* callbacks) { |
| auto it = callbacks->begin(); |
| while (it != callbacks->end()) { |
| if ((*it)()) { |
| it++; |
| } else { |
| it = callbacks->erase(it); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| FlatlandAccessibilityView::FlatlandAccessibilityView( |
| fuchsia::ui::composition::FlatlandPtr flatland1, |
| fuchsia::ui::composition::FlatlandPtr flatland2, |
| fuchsia::ui::observation::scope::RegistryPtr registry, |
| fuchsia::ui::pointer::augment::LocalHitPtr local_hit) |
| : flatland_a11y_(std::move(flatland1), /* debug name = */ "a11y_view"), |
| flatland_highlight_(std::move(flatland2), /* debug name = */ "highlight_view"), |
| registry_(std::move(registry)), |
| local_hit_(std::move(local_hit)) {} |
| |
| void FlatlandAccessibilityView::CreateView( |
| fuchsia::ui::views::ViewCreationToken a11y_view_token, |
| fuchsia::ui::views::ViewportCreationToken proxy_viewport_token) { |
| // Crash a11y_manager if we've already received a CreateView request. |
| // See https://fxbug.dev/42061763 for more discussion. |
| FX_CHECK(!received_create_view_request_) |
| << "Receiving more than one `CreateView` request in a single run of a11y_manager is unsupported. See https://fxbug.dev/42061763."; |
| |
| FX_LOGS(INFO) << "A11y received `CreateView` request"; |
| received_create_view_request_ = true; |
| |
| // We can't create the proxy viewport until we receive layout info from |
| // scenic, so we'll store the proxy viewport creation token to use later. |
| proxy_viewport_token_ = std::move(proxy_viewport_token); |
| |
| // Creates a touch source to be initialized. When the a11y view becomes ready, it will be upgraded |
| // to its augmented form. |
| fuchsia::ui::pointer::TouchSourcePtr touch_source; |
| a11y_view_ref_ = InitialA11yViewSetup(flatland_a11y_.flatland(), std::move(a11y_view_token), |
| focuser_, touch_source.NewRequest(), parent_watcher_); |
| |
| // Present changes. |
| flatland_a11y_.Present(); |
| |
| // Watch for next layout info change. |
| parent_watcher_->GetLayout([this, touch_source = |
| std::move(touch_source)](LayoutInfo layout_info) mutable { |
| FX_DCHECK(proxy_viewport_token_.has_value()); |
| |
| layout_info_ = std::move(layout_info); |
| const auto logical_size = layout_info_->logical_size(); |
| FX_LOGS(INFO) << "A11y view received layout info; view has width = " << logical_size.width |
| << ", height = " << logical_size.height; |
| |
| // Create highlight viewport. |
| auto [highlight_view_token, highlight_viewport_token] = scenic::ViewCreationTokenPair::New(); |
| |
| FinishA11yViewSetup(flatland_a11y_.flatland(), logical_size, |
| std::move(highlight_viewport_token)); |
| fuchsia::ui::composition::ParentViewportWatcherPtr unused_watcher{}; |
| auto highlight_view_ref = HighlightViewSetup( |
| flatland_highlight_.flatland(), logical_size, std::move(highlight_view_token), |
| std::move(proxy_viewport_token_.value()), unused_watcher); |
| proxy_viewport_token_.reset(); |
| |
| FX_CHECK(registry_); |
| // Create a view coordinate converter relative to the highlight view. |
| highlight_view_coordinate_converter_ = std::make_unique<ViewCoordinateConverter>( |
| std::move(registry_.value()), GetKoid(highlight_view_ref)); |
| |
| // Make sure the highlight view is ready before presenting the a11y view. |
| // Probably not necessary, but it might help avoid a flicker at startup. |
| flatland_highlight_.Present( |
| PresentArgs{}, [this, touch_source = std::move(touch_source)](auto) mutable { |
| flatland_a11y_.Present(PresentArgs{}, [this, touch_source = |
| std::move(touch_source)](auto) mutable { |
| // Upgrade Touchsource to its augmented form. This needs to be done when we are sure the |
| // a11y view is in the scene and its regular Touch Source is initialized. |
| FX_LOGS(INFO) << "upgrading accessibility touch source."; |
| local_hit_->Upgrade( |
| std::move(touch_source), |
| [this](fidl::InterfaceHandle<fuchsia::ui::pointer::augment::TouchSourceWithLocalHit> |
| augmented, |
| std::unique_ptr<fuchsia::ui::pointer::augment::ErrorForLocalHit> error) { |
| FX_DCHECK(error == nullptr); |
| touch_source_ = augmented.Bind(); |
| |
| // The a11y view can be considered initialized from now on, because it has the |
| // Touch Source it needs. |
| is_initialized_ = true; |
| InvokeSceneReadyCallbacks(&scene_ready_callbacks_); |
| }); |
| }); |
| }); |
| |
| // Report changes in view properties to observers. |
| InvokeViewPropertiesChangedCallbacks(*layout_info_, &view_properties_changed_callbacks_); |
| |
| // Watch for further resizes of the parent viewport. |
| WatchForResizes(); |
| }); |
| } |
| |
| void FlatlandAccessibilityView::WatchForResizes() { |
| // Watch for next layout info change. |
| parent_watcher_->GetLayout([this](LayoutInfo layout_info) { |
| layout_info_ = std::move(layout_info); |
| |
| if (highlight_is_present_) { |
| FX_LOGS(ERROR) << "A11y view has been resized while a highlight is present. " |
| "The highlight will likely not be drawn in the correct location."; |
| } |
| |
| const auto logical_size = layout_info_->logical_size(); |
| FX_LOGS(INFO) << "A11y view received layout info; view has width = " << logical_size.width |
| << ", height = " << logical_size.height; |
| |
| ResizeLayout(logical_size); |
| |
| // Report changes in view properties to observers. |
| InvokeViewPropertiesChangedCallbacks(*layout_info_, &view_properties_changed_callbacks_); |
| |
| WatchForResizes(); |
| }); |
| } |
| |
| void FlatlandAccessibilityView::ResizeLayout(fuchsia::math::SizeU logical_size) { |
| FX_DCHECK(layout_info_.has_value()); |
| |
| fuchsia::ui::composition::ViewportProperties viewport_properties; |
| viewport_properties.set_logical_size(logical_size); |
| |
| flatland_a11y_.flatland()->SetViewportProperties(ContentId{.value = kHighlightViewportContentId}, |
| fidl::Clone(viewport_properties)); |
| flatland_a11y_.flatland()->SetHitRegions( |
| TransformId({.value = kA11yViewRootTransformId}), |
| {fuchsia::ui::composition::HitRegion{ |
| SizeUToRectFAtOrigin(logical_size), |
| fuchsia::ui::composition::HitTestInteraction::SEMANTICALLY_INVISIBLE}}); |
| |
| flatland_highlight_.flatland()->SetViewportProperties(ContentId{.value = kProxyViewportContentId}, |
| std::move(viewport_properties)); |
| |
| flatland_a11y_.Present(); |
| flatland_highlight_.Present(); |
| } |
| |
| std::optional<fuchsia::ui::views::ViewRef> FlatlandAccessibilityView::view_ref() { |
| if (!a11y_view_ref_) { |
| return std::nullopt; |
| } |
| fuchsia::ui::views::ViewRef copy; |
| fidl::Clone(*a11y_view_ref_, ©); |
| return std::move(copy); |
| } |
| |
| void FlatlandAccessibilityView::add_view_properties_changed_callback( |
| ViewPropertiesChangedCallback callback) { |
| view_properties_changed_callbacks_.push_back(std::move(callback)); |
| if (layout_info_) { |
| InvokeViewPropertiesChangedCallback(*layout_info_, view_properties_changed_callbacks_.back()); |
| } |
| } |
| |
| void FlatlandAccessibilityView::add_scene_ready_callback(SceneReadyCallback callback) { |
| scene_ready_callbacks_.push_back(std::move(callback)); |
| if (is_initialized_) { |
| scene_ready_callbacks_.back()(); |
| } |
| } |
| |
| void FlatlandAccessibilityView::RequestFocus(fuchsia::ui::views::ViewRef view_ref, |
| RequestFocusCallback callback) { |
| FX_DCHECK(focuser_); |
| focuser_->RequestFocus(std::move(view_ref), std::move(callback)); |
| } |
| |
| fidl::InterfaceRequestHandler<fuchsia::accessibility::scene::Provider> |
| FlatlandAccessibilityView::GetHandler() { |
| return view_bindings_.GetHandler(this); |
| } |
| |
| void FlatlandAccessibilityView::DrawHighlight(fuchsia::math::PointF top_left, |
| fuchsia::math::PointF bottom_right, |
| zx_koid_t view_koid, fit::function<void()> callback) { |
| auto local_top_left = |
| highlight_view_coordinate_converter_->Convert(view_koid, {top_left.x, top_left.y}); |
| auto local_bottom_right = |
| highlight_view_coordinate_converter_->Convert(view_koid, {bottom_right.x, bottom_right.y}); |
| |
| if (!local_top_left || !local_bottom_right) { |
| FX_LOGS(ERROR) << "ViewCoordinateConverter failed to convert -- cannot draw highlight."; |
| callback(); |
| return; |
| } |
| |
| FX_DCHECK(is_initialized_); |
| |
| int32_t left = static_cast<int32_t>(lround(local_top_left->x)); |
| int32_t top = static_cast<int32_t>(lround(local_top_left->y)); |
| int32_t right = static_cast<int32_t>(lround(local_bottom_right->x)); |
| int32_t bottom = static_cast<int32_t>(lround(local_bottom_right->y)); |
| if (left > right) { |
| std::swap(left, right); |
| } |
| if (top > bottom) { |
| std::swap(top, bottom); |
| } |
| |
| // Adjust these points so that they represent the *upper left corner* of the rectangles we will |
| // draw. For example, if kHighlightEdgeHalfThickness is 3, all our rectangles' upper left corners |
| // will be shifted left & up by 3 pixels from the rect the caller provided. |
| top -= kHighlightHalfThickness; |
| bottom -= kHighlightHalfThickness; |
| left -= kHighlightHalfThickness; |
| right -= kHighlightHalfThickness; |
| |
| // Length of the long sides of the rectangles we'll draw. |
| const uint32_t horizontal_extent = right - left + kHighlightThickness; |
| const uint32_t vertical_extent = bottom - top + kHighlightThickness; |
| |
| const auto size_horizontal_rect = |
| fuchsia::math::SizeU{.width = horizontal_extent, .height = kHighlightThickness}; |
| const auto size_vertical_rect = |
| fuchsia::math::SizeU{.width = kHighlightThickness, .height = vertical_extent}; |
| flatland_highlight_.flatland()->SetSolidFill(ContentId{.value = kRectangleContentIds[kTopRect]}, |
| kHighlightColor, size_horizontal_rect); |
| flatland_highlight_.flatland()->SetSolidFill( |
| ContentId{.value = kRectangleContentIds[kBottomRect]}, kHighlightColor, size_horizontal_rect); |
| flatland_highlight_.flatland()->SetSolidFill(ContentId{.value = kRectangleContentIds[kLeftRect]}, |
| kHighlightColor, size_vertical_rect); |
| flatland_highlight_.flatland()->SetSolidFill(ContentId{.value = kRectangleContentIds[kRightRect]}, |
| kHighlightColor, size_vertical_rect); |
| |
| // Note that: |
| // - [0,0] is the top left of the transform's coord space |
| // - SetSolidFill rects are drawn with their top left corner at [0,0] |
| flatland_highlight_.flatland()->SetTranslation( |
| TransformId{.value = kRectangleTransformIds[kTopRect]}, fuchsia::math::Vec{left, top}); |
| flatland_highlight_.flatland()->SetTranslation( |
| TransformId{.value = kRectangleTransformIds[kBottomRect]}, fuchsia::math::Vec{left, bottom}); |
| flatland_highlight_.flatland()->SetTranslation( |
| TransformId{.value = kRectangleTransformIds[kLeftRect]}, fuchsia::math::Vec{left, top}); |
| flatland_highlight_.flatland()->SetTranslation( |
| TransformId{.value = kRectangleTransformIds[kRightRect]}, fuchsia::math::Vec{right, top}); |
| |
| // Attach the highlight transform to the rest of the graph so that the rects will be rendered! |
| if (!highlight_is_present_) { |
| flatland_highlight_.flatland()->AddChild(TransformId{.value = kHighlightViewRootTransformId}, |
| TransformId{.value = kHighlightTransformId}); |
| highlight_is_present_ = true; |
| } |
| |
| flatland_highlight_.Present(fuchsia::ui::composition::PresentArgs{}, |
| [callback = std::move(callback)](auto) { callback(); }); |
| } |
| |
| void FlatlandAccessibilityView::ClearHighlight(fit::function<void()> callback) { |
| FX_DCHECK(is_initialized_); |
| |
| if (!highlight_is_present_) { |
| callback(); |
| return; |
| } |
| highlight_is_present_ = false; |
| |
| // Detach the highlight transform from the rest of the graph so that the rects won't be rendered. |
| flatland_highlight_.flatland()->RemoveChild(TransformId{.value = kHighlightViewRootTransformId}, |
| TransformId{.value = kHighlightTransformId}); |
| flatland_highlight_.Present(fuchsia::ui::composition::PresentArgs{}, |
| [callback = std::move(callback)](auto) { callback(); }); |
| } |
| |
| void FlatlandAccessibilityView::SetMagnificationTransform( |
| float scale, float x, float y, SetMagnificationTransformCallback callback) { |
| FX_DCHECK(is_initialized_); |
| |
| flatland_a11y_.flatland()->SetScale( |
| fuchsia::ui::composition::TransformId{.value = kMagnifierTransformId}, |
| fuchsia::math::VecF{.x = scale, .y = scale}); |
| |
| // HACK HACK HACK |
| // TODO(https://fxbug.dev/42081619): Remove this when we move to the new gesture disambiguation protocols. |
| // The input to this method is not properly adjusted for display rotation |
| // (https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/scenic/config/display_rotation) |
| // We adjust it here for the default display rotation value of 270 degrees, but this won't be |
| // correct for any product with a different display rotation value. |
| auto rotated_x = -y; |
| auto rotated_y = x; |
| |
| // TODO(https://fxbug.dev/42063097): Remove these hacks to accommodate a translation |
| // specified in scaled NDC space. |
| // |
| // Translation arguments to this method are in "scaled NDC" space, i.e. NDC |
| // space with |scale| applied. We need to put them into the coordinate space of the |
| // magnifier transform. |
| // |
| // To do so, we first compute the center of the |
| // "viewport", or the portion of the a11y view that we would like to be |
| // visible post-scale-and-translate. For convenience, we compute this location |
| // in a hypothetical coordinate space that spans [0, scale] on both axes, |
| // where single "units" on the x- and y-axes is taken to be equivalent to the logical |
| // width and height of the a11y view, respectively. |
| // |
| // Computing the final translation then reduces to finding the top-left corner of |
| // the "viewport". Since we defined our virtual coordinate space such that the |
| // viewport is 1 unit wide and 1 unit tall, we can simply subtract 0.5f from |
| // viewport_center_x and viewport_center_y to find the virtual coordinates of the |
| // top-left corner of the viewport. We can convert to the magnifier |
| // transform's post-scale space by mutliplying the x- and y- virtual |
| // coordinates by the a11y view's logical width and height, respectively. |
| // |
| // Finally, we compute the end translation such that it moves the top-left |
| // corner of the viewport to the top-left corner of the a11y view; i.e. the |
| // final translation is (-left, -top). |
| |
| auto viewport_center_x = (-rotated_x + scale) / 2; |
| auto viewport_center_y = (-rotated_y + scale) / 2; |
| |
| auto viewport_left_f = |
| (viewport_center_x - 0.5f) * static_cast<float>(layout_info_->logical_size().width); |
| auto viewport_top_f = |
| (viewport_center_y - 0.5f) * static_cast<float>(layout_info_->logical_size().height); |
| flatland_a11y_.flatland()->SetTranslation( |
| fuchsia::ui::composition::TransformId{.value = kMagnifierTransformId}, |
| fuchsia::math::Vec{.x = static_cast<int32_t>(-viewport_left_f), |
| .y = static_cast<int32_t>(-viewport_top_f)}); |
| |
| flatland_a11y_.Present(fuchsia::ui::composition::PresentArgs{}, |
| [callback = std::move(callback)](auto) { callback(); }); |
| } |
| |
| fuchsia::ui::pointer::augment::TouchSourceWithLocalHitPtr |
| FlatlandAccessibilityView::TakeTouchSource() { |
| FX_CHECK(touch_source_) << "Tried to obtain a touch source that was not initialized."; |
| return std::move(touch_source_.value()); |
| } |
| |
| void FlatlandAccessibilityView::SetTouchSource( |
| fuchsia::ui::pointer::augment::TouchSourceWithLocalHitPtr touch_source) { |
| touch_source_ = std::move(touch_source); |
| } |
| |
| } // namespace a11y |