blob: de4310ed2a45ba5be8e3bd84abe94e18f5fc0b53 [file] [log] [blame]
// Copyright 2018 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/examples/lab/direct_input/child/app.h"
#include <fuchsia/math/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fit/function.h>
#include <lib/zx/time.h>
#include <limits>
#include <string>
#include "lib/ui/scenic/cpp/resources.h"
#include "src/lib/fxl/logging.h"
namespace direct_input_child {
const uint32_t kNoFinger = std::numeric_limits<uint32_t>::max(); // Sentinel.
App::App(async::Loop* loop)
: component_context_(sys::ComponentContext::CreateAndServeOutgoingDirectory()),
message_loop_(loop),
focused_(false),
view_provider_binding_(this) {
FX_DCHECK(component_context_);
scenic_ = component_context_->svc()->Connect<fuchsia::ui::scenic::Scenic>();
scenic_.set_error_handler([this](zx_status_t status) { OnScenicError(); });
FX_LOGS(INFO) << "Child - connect to Scenic.";
session_ = std::make_unique<scenic::Session>(scenic_.get());
session_->set_error_handler([this](zx_status_t status) { this->OnSessionError(); });
session_->set_event_handler(fit::bind_member(this, &App::OnSessionEvents));
FX_LOGS(INFO) << "Child - session setup.";
scenic_->GetDisplayInfo([this](fuchsia::ui::gfx::DisplayInfo display_info) {
CreateScene(display_info.width_in_px, display_info.height_in_px);
UpdateScene(zx_clock_get_monotonic());
});
component_context_->outgoing()->AddPublicService<fuchsia::ui::app::ViewProvider>(
[this](fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> request) {
view_provider_binding_.Bind(std::move(request));
},
"view_provider");
{
fuchsia::ui::input::SetHardKeyboardDeliveryCmd cmd;
cmd.delivery_request = true;
fuchsia::ui::input::Command input_cmd;
input_cmd.set_set_hard_keyboard_delivery(std::move(cmd));
session_->Enqueue(std::move(input_cmd));
}
FX_LOGS(INFO) << "Child - ViewProvider service set up.";
}
App::~App() {
component_context_->outgoing()->RemovePublicService<fuchsia::ui::app::ViewProvider>();
ReleaseSessionResources();
}
void App::ReleaseSessionResources() {
if (session_) {
root_node_.reset();
focus_frame_.reset();
for (size_t i = 0; i < 10; ++i) {
pointer_id_[i] = kNoFinger;
pointer_tracker_[i].reset();
}
view_.reset();
session_->Flush();
session_.reset();
}
}
void App::OnScenicError() {
FX_LOGS(ERROR) << "Child - scenic connection error.";
ReleaseSessionResources();
message_loop_->Quit();
}
void App::OnSessionError() {
FX_LOGS(ERROR) << "Child - session error.";
ReleaseSessionResources();
message_loop_->Quit();
}
void App::OnSessionClose() {
FX_LOGS(INFO) << "Child - session close.";
ReleaseSessionResources();
message_loop_->Quit();
}
void App::OnSessionEvents(std::vector<fuchsia::ui::scenic::Event> events) {
using InputEvent = fuchsia::ui::input::InputEvent;
using InputType = fuchsia::ui::input::InputEvent::Tag;
for (const auto& event : events) {
if (event.is_input()) {
const InputEvent& input_event = event.input();
switch (input_event.Which()) {
case InputType::kPointer:
OnPointerEvent(input_event.pointer());
continue;
case InputType::kKeyboard:
OnKeyboardEvent(input_event.keyboard());
continue;
case InputType::kFocus:
OnFocusEvent(input_event.focus());
continue;
case InputType::Invalid:
FX_LOGS(FATAL) << "Unknown input event received.";
}
} else if (event.is_gfx()) {
FX_LOGS(ERROR) << "Child - GFX command unimplemented.";
}
}
}
void App::OnFocusEvent(const fuchsia::ui::input::FocusEvent& event) {
focused_ = event.focused;
if (focused_) {
view_->AddChild(*focus_frame_);
} else {
view_->DetachChild(*focus_frame_);
}
}
void App::OnKeyboardEvent(const fuchsia::ui::input::KeyboardEvent& event) {
using Phase = fuchsia::ui::input::KeyboardEventPhase;
// "Blink" the focus frame to acknowledge keyboard event.
if (event.phase == Phase::PRESSED) {
view_->DetachChild(*focus_frame_);
async::PostDelayedTask(
message_loop_->dispatcher(),
[this]() {
if (focused_) {
view_->AddChild(*focus_frame_);
}
},
zx::msec(80));
}
}
// Helper function for OnPointerEvent. Find index of matching element.
template <std::size_t N>
static size_t find_idx(const std::array<uint32_t, N>& array, uint32_t elem) {
for (size_t i = 0; i < N; ++i) {
if (array[i] == elem) {
return i;
}
}
return kNoFinger; // Keep it simple.
}
// Helper function for OnPointerEvent. Return contents of array as a string.
template <std::size_t N>
static std::string contents(const std::array<uint32_t, N>& array) {
std::string value = "[";
for (size_t i = 0; i < N; ++i) {
value += std::to_string(array[i]);
if (i < N - 1) {
value += ", ";
}
}
return value + "]";
}
// This function implements a very specific input-recognition behavior.
// Despite parallel dispatch of input events on a DOWN hit, we only track a
// pointer if we are also focused. In contrast, gestures need to see all inputs,
// regardless of focus state.
void App::OnPointerEvent(const fuchsia::ui::input::PointerEvent& event) {
using Type = fuchsia::ui::input::PointerEventType;
using Phase = fuchsia::ui::input::PointerEventPhase;
if (event.type == Type::TOUCH) {
// TODO(SCN-920): Reduce the very noticeable tracking lag.
if (focused_ && event.phase == Phase::DOWN) {
// Nice to meet you. Add to known-fingers list.
size_t idx = find_idx(pointer_id_, kNoFinger);
FX_CHECK(idx != kNoFinger) << "Pointer index full: " << contents(pointer_id_);
pointer_id_[idx] = event.pointer_id;
view_->AddChild(*pointer_tracker_[idx]);
pointer_tracker_[idx]->SetTranslation(event.x, event.y, -400.f);
} else if (event.phase == Phase::MOVE) {
size_t idx = find_idx(pointer_id_, event.pointer_id);
if (idx != kNoFinger) {
// It's a finger we know, keep moving.
pointer_tracker_[idx]->SetTranslation(event.x, event.y, -400.f);
}
} else if (event.phase == Phase::UP || event.phase == Phase::CANCEL) {
size_t idx = find_idx(pointer_id_, event.pointer_id);
if (idx != kNoFinger) {
// It's a finger we know, but time to remove.
view_->DetachChild(*pointer_tracker_[idx]);
pointer_id_[idx] = kNoFinger;
}
}
}
}
void App::CreateView(zx::eventpair view_token,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services) {
FX_LOGS(INFO) << "Child - CreateView invoked.";
view_ = std::make_unique<scenic::View>(session_.get(), std::move(view_token), "child view");
view_->SetLabel("child view");
if (root_node_) {
view_->AddChild(*root_node_);
FX_LOGS(INFO) << "Child - outbound view is set up.";
}
}
void App::UpdateScene(uint64_t next_presentation_time) {
session_->Present(next_presentation_time, [this](fuchsia::images::PresentationInfo info) {
UpdateScene(info.presentation_time + 2 * info.presentation_interval);
});
}
void App::CreateScene(float display_width, float display_height) {
FX_LOGS(INFO) << "Child - display size: " << display_width << ", " << display_height;
width_in_px_ = display_width; // Store display size, not view size!
height_in_px_ = display_height;
auto session = session_.get();
const float kMargin = 100.f;
const float kWidth = display_width - 6.f * kMargin;
const float kHeight = display_height - 6.f * kMargin;
// Set up root node, expose it to outbound View.
{
std::unique_ptr<scenic::EntityNode> root_node = std::make_unique<scenic::EntityNode>(session);
root_node->SetLabel("child root node");
root_node->SetTranslation(0.f, 0.f, -100.f);
scenic::ShapeNode shape(session);
scenic::Rectangle rec(session, kWidth, kHeight);
shape.SetShape(rec);
scenic::Material material(session);
material.SetColor(0, 191, 255, 255); // Light blue
shape.SetMaterial(material);
root_node->AddChild(shape);
if (view_) {
view_->AddChild(*root_node);
FX_LOGS(INFO) << "Child - outbound view is set up.";
}
root_node_ = std::move(root_node);
FX_LOGS(INFO) << "Child - root node is set up.";
}
// Create frame to trigger on focus.
{
std::unique_ptr<scenic::EntityNode> frame = std::make_unique<scenic::EntityNode>(session);
frame->SetLabel("child focus frame");
const float kElevation = 110.f; // Z height
const float kBar = 50.f;
const float kTranslateX = (kWidth - kBar) * 0.5f;
const float kTranslateY = (kHeight - kBar) * 0.5f;
scenic::Material material(session);
material.SetColor(0, 0, 255, 255); // Blue
scenic::Rectangle horizontal_bar(session, kWidth, kBar);
scenic::Rectangle vertical_bar(session, kBar, kHeight);
scenic::ShapeNode top_bar(session);
top_bar.SetTranslation(0.f, -kTranslateY, -kElevation);
top_bar.SetShape(horizontal_bar);
top_bar.SetMaterial(material);
frame->AddChild(top_bar);
scenic::ShapeNode bottom_bar(session);
bottom_bar.SetTranslation(0.f, kTranslateY, -kElevation);
bottom_bar.SetShape(horizontal_bar);
bottom_bar.SetMaterial(material);
frame->AddChild(bottom_bar);
scenic::ShapeNode left_bar(session);
left_bar.SetTranslation(-kTranslateX, 0, -kElevation);
left_bar.SetShape(vertical_bar);
left_bar.SetMaterial(material);
frame->AddChild(left_bar);
scenic::ShapeNode right_bar(session);
right_bar.SetTranslation(kTranslateX, 0, -kElevation);
right_bar.SetShape(vertical_bar);
right_bar.SetMaterial(material);
frame->AddChild(right_bar);
focus_frame_ = std::move(frame);
FX_LOGS(INFO) << "Child - focus frame prepared.";
}
// Create a visual tracker for pointer movement.
{
const float kElevation = 400.f;
for (size_t i = 0; i < 10; ++i) {
pointer_id_[i] = kNoFinger;
pointer_tracker_[i] = std::make_unique<scenic::ShapeNode>(session);
pointer_tracker_[i]->SetLabel("pointer tracker");
pointer_tracker_[i]->SetTranslation(0.f, 0.f, -kElevation);
scenic::Circle circle(session, 50.f);
pointer_tracker_[i]->SetShape(circle);
scenic::Material material(session);
material.SetColor(0, 0, 255, 255); // Blue
pointer_tracker_[i]->SetMaterial(material);
}
FX_LOGS(INFO) << "Child - pointer tracker prepared.";
}
}
} // namespace direct_input_child