blob: dc4fb7ef72d36742abb708b64af6abcaff4e9d59 [file] [log] [blame]
// 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/view_coordinate_converter.h"
#include <fuchsia/ui/observation/scope/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <cmath>
namespace a11y {
// Used for floating point comparisons.
constexpr float kEpsilon = 1.e-10f;
ViewCoordinateConverter::ViewCoordinateConverter(
fuchsia::ui::observation::scope::RegistryPtr registry, zx_koid_t context_view_ref_koid)
: context_view_ref_koid_(context_view_ref_koid) {
registry.set_error_handler([](zx_status_t status) {
FX_LOGS(ERROR) << "Error from fuchsia::ui::observation::scope::Registry: "
<< zx_status_get_string(status);
});
registry->RegisterScopedViewTreeWatcher(context_view_ref_koid_, watcher_.NewRequest(), []() {});
watcher_.set_error_handler([](zx_status_t status) {
FX_LOGS(ERROR) << "Error from fuchsia::ui::observation::geometry::ViewTreeWatcher: "
<< zx_status_get_string(status);
});
Watch();
}
void ViewCoordinateConverter::ProcessResponse(
fuchsia::ui::observation::geometry::WatchResponse response) {
if (response.has_error() || !response.has_updates() || response.updates().empty()) {
// For now, a11y does not care about the possible errors here and makes a best effort to receive
// updated values.
return;
}
// We only care about the most recent snapshot, so access the last value.
const auto& view_tree_snapshot = response.updates().back();
FX_DCHECK(view_tree_snapshot.has_views());
for (const auto& view : view_tree_snapshot.views()) {
ViewData& view_data = view_transforms_[view.view_ref_koid()];
view_data.origin_in_context = {view.extent_in_context().origin.x,
view.extent_in_context().origin.y};
view_data.origin = {view.layout().extent.min.x, view.layout().extent.min.y};
view_data.angle = view.extent_in_context().angle_degrees;
const float view_width = std::abs(view.layout().extent.max.x - view.layout().extent.min.x);
const float view_height = std::abs(view.layout().extent.max.y - view.layout().extent.min.y);
// We have to do this computation instead of reading the scale that the geometry watcher
// reports, because that scale is not relative to the context view.
view_data.x_scale = view.extent_in_context().width / view_width;
view_data.y_scale = view.extent_in_context().height / view_height;
}
for (auto& callback : callbacks_) {
callback();
}
}
std::optional<fuchsia::math::PointF> ViewCoordinateConverter::Convert(
zx_koid_t view_ref_koid, fuchsia::math::PointF coordinate) const {
const auto it = view_transforms_.find(view_ref_koid);
if (it == view_transforms_.end()) {
return std::nullopt;
}
if (view_ref_koid == context_view_ref_koid_) {
return coordinate;
}
const auto& view_data = it->second;
// First, compute an offset of the coordinate to its origin, and convert this offset into context
// view scale.
const float x_offset = view_data.x_scale * (coordinate.x - view_data.origin.x);
const float y_offset = view_data.y_scale * (coordinate.y - view_data.origin.y);
fuchsia::math::PointF context_point;
// Map `view_data.angle` to the range [0., 360.)
const float angle = fmod(360.f + fmod(view_data.angle, 360.f), 360.f);
if (abs(angle - 0.0) < kEpsilon) {
context_point.x = view_data.origin_in_context.x + x_offset;
context_point.y = view_data.origin_in_context.y + y_offset;
return context_point;
} else if (abs(angle - 90.0) < kEpsilon) {
context_point.x = view_data.origin_in_context.x + y_offset;
context_point.y = view_data.origin_in_context.y - x_offset;
return context_point;
} else if (abs(angle - 180.0) < kEpsilon) {
context_point.x = view_data.origin_in_context.x - x_offset;
context_point.y = view_data.origin_in_context.y - y_offset;
return context_point;
} else if (abs(angle - 270.0) < kEpsilon) {
context_point.x = view_data.origin_in_context.x - y_offset;
context_point.y = view_data.origin_in_context.y + x_offset;
return context_point;
}
FX_LOGS(WARNING)
<< "ViewCoordinateConverter can't handle rotations that aren't a multiple of 90 degrees, returning nullopt";
return std::nullopt;
}
void ViewCoordinateConverter::Watch() {
watcher_->Watch([this](fuchsia::ui::observation::geometry::WatchResponse response) {
ProcessResponse(std::move(response));
Watch();
});
}
void ViewCoordinateConverter::RegisterCallback(OnGeometryChangeCallback callback) {
callbacks_.push_back(std::move(callback));
}
} // namespace a11y