blob: 487bd6fc245ba1e4b673d7428b9daded3f6fa90a [file] [log] [blame]
// Copyright 2019 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/annotation/annotation_view.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <zircon/status.h>
#include "src/ui/a11y/lib/util/util.h"
namespace a11y {
AnnotationView::AnnotationView(sys::ComponentContext* component_context,
ViewPropertiesChangedCallback view_properties_changed_callback,
ViewAttachedCallback view_attached_callback,
ViewDetachedCallback view_detached_callback)
: session_listener_binding_(this),
view_properties_changed_callback_(std::move(view_properties_changed_callback)),
view_attached_callback_(std::move(view_attached_callback)),
view_detached_callback_(std::move(view_detached_callback)) {
fuchsia::ui::scenic::ScenicPtr scenic =
component_context->svc()->Connect<fuchsia::ui::scenic::Scenic>();
// Create a Scenic Session and a Scenic SessionListener.
scenic->CreateSession(session_.NewRequest(), session_listener_binding_.NewBinding());
// Connect to Scenic annotation registry service.
annotation_registry_ = component_context->svc()->Connect<fuchsia::ui::annotation::Registry>();
annotation_registry_.set_error_handler([](zx_status_t status) {
FX_LOGS(ERROR) << "Error from fuchsia::ui::annotation::Registry"
<< zx_status_get_string(status);
});
}
void AnnotationView::InitializeView(fuchsia::ui::views::ViewRef client_view_ref) {
FX_CHECK(client_view_ref.reference);
client_view_koid_ = GetKoid(client_view_ref);
std::vector<fuchsia::ui::scenic::Command> cmds;
// Create view token pair for annotation view and view holder.
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
// Register annotation view holder with scenic.
annotation_registry_->CreateAnnotationViewHolder(
std::move(client_view_ref), std::move(annotation_view_holder_token),
[this]() { state_.annotation_view_registered = true; });
// Use |view_token| to create annotation view in the session.
PushCommand(&cmds, scenic::NewCreateViewCmd(kAnnotationViewId, std::move(annotation_view_token),
"annotation_view"));
// Create entity node that will be the parent of all the annotation content. Attaching the
// annotation content as children of this node enables us to clear the contents of the view by
// detaching only this node from the view.
PushCommand(&cmds, scenic::NewCreateEntityNodeCmd(kContentNodeId));
// Create material (fill color) for bounding box.
// TODO: Update color.
PushCommand(&cmds, scenic::NewCreateMaterialCmd(kHighlightMaterialId));
PushCommand(&cmds,
scenic::NewSetColorCmd(kHighlightMaterialId, 0xf5, 0x00, 0x57, 0xff)); // Pink A400
// Create shape nodes to hold each of the edges of the bounding box.
CreateHighlightEdgeNode(&cmds, kHighlightLeftEdgeNodeId);
CreateHighlightEdgeNode(&cmds, kHighlightRightEdgeNodeId);
CreateHighlightEdgeNode(&cmds, kHighlightTopEdgeNodeId);
CreateHighlightEdgeNode(&cmds, kHighlightBottomEdgeNodeId);
// Enque commands to create view.
session_->Enqueue(std::move(cmds));
// Apply commands.
session_->Present(0, {}, {}, [this](fuchsia::images::PresentationInfo info) {
state_.tree_initialized = true;
});
}
void AnnotationView::DrawHighlight(const fuchsia::ui::gfx::BoundingBox& bounding_box,
const std::array<float, 3>& scale_vector,
const std::array<float, 3>& translation_vector) {
if (!state_.tree_initialized) {
FX_LOGS(INFO) << "Annotation view tree is not initialized.";
return;
}
if (!state_.annotation_view_registered) {
FX_LOGS(INFO) << "Annotation view not registered.";
return;
}
// Used to specify height/width of edge rectangles.
const auto bounding_box_width = bounding_box.max.x - bounding_box.min.x;
const auto bounding_box_height = bounding_box.max.y - bounding_box.min.y;
// Annotation views currently have the same bounding boxes as their parent views, so in order
// to ensure that annotations appear superimposed on parent view content, the elevation should
// be set to the top of the parent view.
// Add some offset to ensure these rectangles fall into the View bounding box
// so that it's drawable.
// TODO(fxbug.dev/64277): Ideally we would draw this higher in the scene graph so that we can't
// accidentally draw behind the camera. In the mean time, we draw 5% between min z and max z
// in our view to give some extra space for z translations higher up in the scene graph. It
// is very common to find a flutter::LayerTree EntityNode that applies a -10 z translation,
// and the bounding box min z and max z are typically -1000 and 1000, so make sure we have
// plenty of space for that.
constexpr float kEpsilon = 0.05;
const auto annotation_elevation = parent_view_properties_.bounding_box.min.z * (1 - kEpsilon);
// Used to translate edge rectangles.
const auto bounding_box_center_x = (bounding_box.max.x + bounding_box.min.x) / 2.f;
const auto bounding_box_center_y = (bounding_box.max.y + bounding_box.min.y) / 2.f;
std::vector<fuchsia::ui::scenic::Command> cmds;
// Create edges of highlight rectangle.
DrawHighlightEdge(&cmds, kHighlightLeftEdgeNodeId, kHighlightEdgeThickness,
bounding_box_height + kHighlightEdgeThickness, bounding_box.min.x,
bounding_box_center_y, annotation_elevation);
DrawHighlightEdge(&cmds, kHighlightRightEdgeNodeId, kHighlightEdgeThickness,
bounding_box_height + kHighlightEdgeThickness, bounding_box.max.x,
bounding_box_center_y, annotation_elevation);
DrawHighlightEdge(&cmds, kHighlightTopEdgeNodeId, bounding_box_width + kHighlightEdgeThickness,
kHighlightEdgeThickness, bounding_box_center_x, bounding_box.max.y,
annotation_elevation);
DrawHighlightEdge(&cmds, kHighlightBottomEdgeNodeId, bounding_box_width + kHighlightEdgeThickness,
kHighlightEdgeThickness, bounding_box_center_x, bounding_box.min.y,
annotation_elevation);
PushCommand(&cmds, scenic::NewSetTranslationCmd(kContentNodeId, translation_vector));
PushCommand(&cmds, scenic::NewSetScaleCmd(kContentNodeId, scale_vector));
// If state_.has_annotations is false, then either the top-level content node has not yet been
// attached to the annotation view node after initialization, OR it has been detached after a view
// focus change. In either case, it needs to be re-attached to render new annotation content. This
// check is necessary to ensure that we do not re-draw a stale highlight when a client view comes
// back into focus.
if (!state_.view_content_attached) {
PushCommand(&cmds, scenic::NewAddChildCmd(kAnnotationViewId, kContentNodeId));
}
session_->Enqueue(std::move(cmds));
session_->Present(0, {}, {}, [](fuchsia::images::PresentationInfo info) {});
state_.view_content_attached = true;
}
void AnnotationView::DetachViewContents() {
std::vector<fuchsia::ui::scenic::Command> cmds;
// Clear view contents by detaching top-level content node from view.
PushCommand(&cmds, scenic::NewDetachCmd(kContentNodeId));
session_->Enqueue(std::move(cmds));
session_->Present(0, {}, {}, [](fuchsia::images::PresentationInfo info) {});
state_.view_content_attached = false;
}
void AnnotationView::DrawHighlightEdge(std::vector<fuchsia::ui::scenic::Command>* cmds,
int parent_node_id, float width, float height,
float center_x, float center_y, float elevation) {
const auto edge_id = next_resource_id_++;
PushCommand(cmds, scenic::NewCreateRectangleCmd(edge_id, width, height));
PushCommand(cmds, scenic::NewSetShapeCmd(parent_node_id, edge_id));
// By releasing the resource here, we make the edge's parent node the only holder of a reference
// to the edge rectangle. Once the background shape node no longer references this rectangle,
// scenic will destroy it internally. This behavior ensures that we don't need to explicitly
// delete annotations when we want to create new ones -- we can simply call invoke NewSetShapeCmd
// with the updated shape to delete the old one.
PushCommand(cmds, scenic::NewReleaseResourceCmd(edge_id));
PushCommand(cmds, scenic::NewSetTranslationCmd(parent_node_id, {center_x, center_y, elevation}));
}
void AnnotationView::PushCommand(std::vector<fuchsia::ui::scenic::Command>* cmds,
fuchsia::ui::gfx::Command cmd) {
// Wrap the gfx::Command in a scenic::Command, then push it.
cmds->push_back(scenic::NewCommand(std::move(cmd)));
}
void AnnotationView::CreateHighlightEdgeNode(std::vector<fuchsia::ui::scenic::Command>* cmds,
int edge_node_id) {
PushCommand(cmds, scenic::NewCreateShapeNodeCmd(edge_node_id));
PushCommand(cmds, scenic::NewSetMaterialCmd(edge_node_id, kHighlightMaterialId));
PushCommand(cmds, scenic::NewAddChildCmd(kContentNodeId, edge_node_id));
}
void AnnotationView::OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) {
for (const auto& event : events) {
if (event.Which() != fuchsia::ui::scenic::Event::Tag::kGfx) {
// We don't expect to receive any input events, and can ignore unhandled events.
continue;
}
HandleGfxEvent(event.gfx());
}
}
void AnnotationView::HandleGfxEvent(const fuchsia::ui::gfx::Event& event) {
if (event.Which() == fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged) {
// Update parent View properties.
parent_view_properties_ = event.view_properties_changed().properties;
view_properties_changed_callback_();
} else if (event.Which() == fuchsia::ui::gfx::Event::Tag::kViewDetachedFromScene) {
view_detached_callback_();
} else if (event.Which() == fuchsia::ui::gfx::Event::Tag::kViewAttachedToScene) {
view_attached_callback_();
}
}
std::unique_ptr<AnnotationViewInterface> AnnotationViewFactory::CreateAndInitAnnotationView(
fuchsia::ui::views::ViewRef client_view_ref, sys::ComponentContext* context,
AnnotationViewInterface::ViewPropertiesChangedCallback view_properties_changed_callback,
AnnotationViewInterface::ViewAttachedCallback view_attached_callback,
AnnotationViewInterface::ViewDetachedCallback view_detached_callback) {
auto annotation_view = std::make_unique<AnnotationView>(
context, std::move(view_properties_changed_callback), std::move(view_attached_callback),
std::move(view_detached_callback));
annotation_view->InitializeView(std::move(client_view_ref));
return annotation_view;
}
} // namespace a11y