blob: a2583f5a3f0a6626bc96e90779ee076422494bcb [file] [log] [blame]
// Copyright 2015 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 "garnet/bin/ui/root_presenter/presentation.h"
#include <cmath>
#if defined(countof)
// Workaround for compiler error due to Zircon defining countof() as a macro.
// Redefines countof() using GLM_COUNTOF(), which currently provides a more
// sophisticated implementation anyway.
#undef countof
#include <glm/glm.hpp>
#define countof(X) GLM_COUNTOF(X)
#else
// No workaround required.
#include <glm/glm.hpp>
#endif
#include <glm/gtc/type_ptr.hpp>
#include "lib/app/cpp/connect.h"
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/logging.h"
#include "lib/ui/input/cpp/formatting.h"
#include "lib/ui/views/cpp/formatting.h"
namespace root_presenter {
namespace {
// View Key: The presentation's own root view.
constexpr uint32_t kRootViewKey = 1u;
// View Key: The presented content view.
constexpr uint32_t kContentViewKey = 2u;
// The shape and elevation of the cursor.
constexpr float kCursorWidth = 20;
constexpr float kCursorHeight = 20;
constexpr float kCursorRadius = 10;
constexpr float kCursorElevation = 800;
} // namespace
Presentation::Presentation(mozart::ViewManager* view_manager,
scenic::SceneManager* scene_manager)
: view_manager_(view_manager),
scene_manager_(scene_manager),
session_(scene_manager_),
compositor_(&session_),
layer_stack_(&session_),
layer_(&session_),
renderer_(&session_),
scene_(&session_),
camera_(scene_),
root_view_host_node_(&session_),
root_view_parent_node_(&session_),
content_view_host_node_(&session_),
cursor_shape_(&session_,
kCursorWidth,
kCursorHeight,
0u,
kCursorRadius,
kCursorRadius,
kCursorRadius),
cursor_material_(&session_),
presentation_binding_(this),
tree_listener_binding_(this),
tree_container_listener_binding_(this),
view_container_listener_binding_(this),
view_listener_binding_(this),
weak_factory_(this) {
session_.set_connection_error_handler([this] {
FXL_LOG(ERROR)
<< "Root presenter: Scene manager session died unexpectedly.";
Shutdown();
});
renderer_.SetCamera(camera_);
scene_.AddChild(root_view_host_node_);
layer_.SetRenderer(renderer_);
layer_stack_.AddLayer(layer_);
compositor_.SetLayerStack(layer_stack_);
root_view_host_node_.ExportAsRequest(&root_view_host_import_token_);
root_view_parent_node_.BindAsRequest(&root_view_parent_export_token_);
root_view_parent_node_.AddChild(content_view_host_node_);
content_view_host_node_.ExportAsRequest(&content_view_host_import_token_);
cursor_material_.SetColor(0xff, 0x00, 0xff, 0xff);
}
Presentation::~Presentation() {}
void Presentation::Present(
mozart::ViewOwnerPtr view_owner,
fidl::InterfaceRequest<mozart::Presentation> presentation_request,
fxl::Closure shutdown_callback) {
FXL_DCHECK(view_owner);
FXL_DCHECK(!display_info_);
shutdown_callback_ = std::move(shutdown_callback);
scene_manager_->GetDisplayInfo(fxl::MakeCopyable([
weak = weak_factory_.GetWeakPtr(), view_owner = std::move(view_owner),
presentation_request = std::move(presentation_request)
](scenic::DisplayInfoPtr display_info) mutable {
if (weak)
weak->CreateViewTree(std::move(view_owner),
std::move(presentation_request),
std::move(display_info));
}));
}
void Presentation::CreateViewTree(
mozart::ViewOwnerPtr view_owner,
fidl::InterfaceRequest<mozart::Presentation> presentation_request,
scenic::DisplayInfoPtr display_info) {
FXL_DCHECK(!display_info_);
FXL_DCHECK(display_info);
if (presentation_request) {
presentation_binding_.Bind(std::move(presentation_request));
}
display_info_ = std::move(display_info);
device_pixel_ratio_ = display_info_->device_pixel_ratio;
logical_width_ = display_info_->physical_width / device_pixel_ratio_;
logical_height_ = display_info_->physical_height / device_pixel_ratio_;
scene_.SetScale(device_pixel_ratio_, device_pixel_ratio_, 1.f);
layer_.SetSize(static_cast<float>(display_info_->physical_width),
static_cast<float>(display_info_->physical_height));
// Register the view tree.
mozart::ViewTreeListenerPtr tree_listener;
tree_listener_binding_.Bind(tree_listener.NewRequest());
view_manager_->CreateViewTree(tree_.NewRequest(), std::move(tree_listener),
"Presentation");
tree_.set_connection_error_handler([this] {
FXL_LOG(ERROR) << "Root presenter: View tree connection error.";
Shutdown();
});
// Prepare the view container for the root.
tree_->GetContainer(tree_container_.NewRequest());
tree_container_.set_connection_error_handler([this] {
FXL_LOG(ERROR) << "Root presenter: Tree view container connection error.";
Shutdown();
});
mozart::ViewContainerListenerPtr tree_container_listener;
tree_container_listener_binding_.Bind(tree_container_listener.NewRequest());
tree_container_->SetListener(std::move(tree_container_listener));
// Get view tree services.
app::ServiceProviderPtr tree_service_provider;
tree_->GetServiceProvider(tree_service_provider.NewRequest());
input_dispatcher_ = app::ConnectToService<mozart::InputDispatcher>(
tree_service_provider.get());
input_dispatcher_.set_connection_error_handler([this] {
// This isn't considered a fatal error right now since it is still useful
// to be able to test a view system that has graphics but no input.
FXL_LOG(WARNING)
<< "Input dispatcher connection error, input will not work.";
input_dispatcher_.reset();
});
// Create root view.
fidl::InterfaceHandle<mozart::ViewOwner> root_view_owner;
auto root_view_owner_request = root_view_owner.NewRequest();
mozart::ViewListenerPtr root_view_listener;
view_listener_binding_.Bind(root_view_listener.NewRequest());
view_manager_->CreateView(
root_view_.NewRequest(), std::move(root_view_owner_request),
std::move(root_view_listener), std::move(root_view_parent_export_token_),
"RootView");
root_view_->GetContainer(root_container_.NewRequest());
// Attach root view to view tree.
tree_container_->AddChild(kRootViewKey, std::move(root_view_owner),
std::move(root_view_host_import_token_));
auto root_properties = mozart::ViewProperties::New();
root_properties->display_metrics = mozart::DisplayMetrics::New();
root_properties->display_metrics->device_pixel_ratio = device_pixel_ratio_;
root_properties->view_layout = mozart::ViewLayout::New();
root_properties->view_layout->size = mozart::SizeF::New();
root_properties->view_layout->size->width = logical_width_;
root_properties->view_layout->size->height = logical_height_;
root_properties->view_layout->inset = mozart::InsetF::New();
tree_container_->SetChildProperties(kRootViewKey, std::move(root_properties));
// Add content view to root view.
mozart::ViewContainerListenerPtr view_container_listener;
view_container_listener_binding_.Bind(view_container_listener.NewRequest());
root_container_->SetListener(std::move(view_container_listener));
root_container_->AddChild(kContentViewKey, std::move(view_owner),
std::move(content_view_host_import_token_));
root_container_->SetChildProperties(kContentViewKey,
mozart::ViewProperties::New());
PresentScene();
}
void Presentation::OnDeviceAdded(mozart::InputDeviceImpl* input_device) {
FXL_VLOG(1) << "OnDeviceAdded: device_id=" << input_device->id();
FXL_DCHECK(device_states_by_id_.count(input_device->id()) == 0);
std::unique_ptr<mozart::DeviceState> state =
std::make_unique<mozart::DeviceState>(
input_device->id(), input_device->descriptor(),
[this](mozart::InputEventPtr event) { OnEvent(std::move(event)); });
mozart::DeviceState* state_ptr = state.get();
auto device_pair = std::make_pair(input_device, std::move(state));
state_ptr->OnRegistered();
device_states_by_id_.emplace(input_device->id(), std::move(device_pair));
}
void Presentation::OnDeviceRemoved(uint32_t device_id) {
FXL_VLOG(1) << "OnDeviceRemoved: device_id=" << device_id;
if (device_states_by_id_.count(device_id) != 0) {
device_states_by_id_[device_id].second->OnUnregistered();
auto it = cursors_.find(device_id);
if (it != cursors_.end()) {
it->second.node->Detach();
cursors_.erase(it);
PresentScene();
}
device_states_by_id_.erase(device_id);
}
}
void Presentation::OnReport(uint32_t device_id,
mozart::InputReportPtr input_report) {
FXL_VLOG(2) << "OnReport device=" << device_id
<< ", count=" << device_states_by_id_.count(device_id)
<< ", report=" << *(input_report);
if (device_states_by_id_.count(device_id) == 0) {
FXL_VLOG(1) << "OnReport: Unknown device " << device_id;
return;
}
if (!display_info_)
return;
mozart::DeviceState* state = device_states_by_id_[device_id].second.get();
mozart::Size size;
size.width = logical_width_;
size.height = logical_height_;
state->Update(std::move(input_report), size);
}
void Presentation::OnEvent(mozart::InputEventPtr event) {
FXL_VLOG(1) << "OnEvent " << *(event);
bool invalidate = false;
if (event->is_pointer()) {
const mozart::PointerEventPtr& pointer = event->get_pointer();
if (pointer->type == mozart::PointerEvent::Type::MOUSE) {
if (cursors_.count(pointer->device_id) == 0) {
cursors_.emplace(pointer->device_id, CursorState{});
}
cursors_[pointer->device_id].position.x = pointer->x;
cursors_[pointer->device_id].position.y = pointer->y;
// TODO(jpoichet) for now don't show cursor when mouse is added until we
// have a timer to hide it. Acer12 sleeve reports 2 mice but only one will
// generate events for now.
if (pointer->phase != mozart::PointerEvent::Phase::ADD &&
pointer->phase != mozart::PointerEvent::Phase::REMOVE) {
cursors_[pointer->device_id].visible = true;
}
invalidate = true;
} else {
for (auto it = cursors_.begin(); it != cursors_.end(); ++it) {
if (it->second.visible) {
it->second.visible = false;
invalidate = true;
}
}
}
if (animation_state_ == kTrackball) {
if (pointer->phase == mozart::PointerEvent::Phase::DOWN) {
// If we're not already panning/rotating the camera, then start, but
// only if the touch-down is in the bottom 10% of the screen.
if (!trackball_pointer_down_ && pointer->y > 0.9f * logical_height_) {
trackball_pointer_down_ = true;
trackball_device_id_ = pointer->device_id;
trackball_pointer_id_ = pointer->pointer_id;
trackball_previous_x_ = pointer->x;
}
} else if (pointer->phase == mozart::PointerEvent::Phase::MOVE) {
// If the moved pointer is the one that is currently panning/rotating
// the camera, then update the camera position.
if (trackball_pointer_down_ &&
trackball_device_id_ == pointer->device_id &&
trackball_device_id_ == pointer->device_id) {
float pan_rate = -2.5f / logical_width_;
float pan_change = pan_rate * (pointer->x - trackball_previous_x_);
trackball_previous_x_ = pointer->x;
camera_pan_ += pan_change;
if (camera_pan_ < -1.f) {
camera_pan_ = -1.f;
} else if (camera_pan_ > 1.f) {
camera_pan_ = 1.f;
}
}
} else if (pointer->phase == mozart::PointerEvent::Phase::UP) {
// The pointer was released.
if (trackball_pointer_down_ &&
trackball_device_id_ == pointer->device_id &&
trackball_device_id_ == pointer->device_id) {
trackball_pointer_down_ = false;
}
}
}
} else if (event->is_keyboard()) {
// Alt-Backspace cycles through modes.
const mozart::KeyboardEventPtr& kbd = event->get_keyboard();
if ((kbd->modifiers & mozart::kModifierAlt) &&
kbd->phase == mozart::KeyboardEvent::Phase::PRESSED &&
kbd->code_point == 0 && kbd->hid_usage == 42 &&
!trackball_pointer_down_) {
HandleAltBackspace();
invalidate = true;
}
}
if (invalidate) {
PresentScene();
}
if (input_dispatcher_)
input_dispatcher_->DispatchEvent(std::move(event));
}
void Presentation::HandleAltBackspace() {
switch (animation_state_) {
case kDefault:
animation_state_ = kNoClipping;
renderer_.SetDisableClipping(true);
break;
case kNoClipping:
animation_state_ = kCameraMovingAway;
break;
case kTrackball:
animation_state_ = kCameraReturning;
break;
case kCameraMovingAway:
case kCameraReturning:
return;
}
animation_start_time_ = zx_time_get(ZX_CLOCK_MONOTONIC);
UpdateAnimation(animation_start_time_);
}
bool Presentation::UpdateAnimation(uint64_t presentation_time) {
if (animation_state_ == kDefault || animation_state_ == kNoClipping) {
return false;
}
const float half_width = logical_width_ * device_pixel_ratio_ * 0.5f;
const float half_height = logical_height_ * device_pixel_ratio_ * 0.5f;
// Always look at the middle of the stage.
float target[3] = {half_width, half_height, 0};
glm::vec3 glm_up(0, 0.1, -0.9);
glm_up = glm::normalize(glm_up);
float up[3] = {glm_up[0], glm_up[1], glm_up[2]};
double secs = static_cast<double>(presentation_time - animation_start_time_) /
1'000'000'000;
constexpr double kAnimationDuration = 1.3;
float param = secs / kAnimationDuration;
if (param >= 1.f) {
param = 1.f;
switch (animation_state_) {
case kDefault:
case kNoClipping:
FXL_DCHECK(false);
return false;
case kCameraMovingAway:
animation_state_ = kTrackball;
break;
case kCameraReturning: {
animation_state_ = kDefault;
// Switch back to ortho view, and re-enable clipping.
float ortho_eye[3] = {half_width, half_height, 1100.f};
camera_.SetProjection(ortho_eye, target, up, 0.f);
renderer_.SetDisableClipping(false);
return true;
}
case kTrackball:
break;
}
}
if (animation_state_ == kCameraReturning) {
param = 1.f - param; // Animating back to regular position.
}
param = glm::smoothstep(0.f, 1.f, param);
// TODO: kOrthoEyeDist and the values in |eye_end| below are somewhat
// dependent on the screen size, but also the depth of the stage's viewing
// volume (currently hardcoded in the SceneManager implementation to 1000, and
// not available outside). Since this is a demo feature, it seems OK for now.
constexpr float kOrthoEyeDist = 60000;
const float fovy = 2.f * atan(half_height / kOrthoEyeDist);
glm::vec3 eye_start(half_width, half_height, kOrthoEyeDist);
constexpr float kEyePanRadius = 1.01f * kOrthoEyeDist;
constexpr float kMaxPanAngle = 3.14159 / 4;
float eye_end_x =
sin(camera_pan_ * kMaxPanAngle) * kEyePanRadius + half_width;
float eye_end_y =
cos(camera_pan_ * kMaxPanAngle) * kEyePanRadius + half_height;
glm::vec3 eye_end(eye_end_x, eye_end_y, 0.75f * kOrthoEyeDist);
glm::vec3 eye_mid = glm::mix(eye_start, eye_end, 0.4f);
eye_mid.z = 1.5f * kOrthoEyeDist;
// Quadratic bezier.
glm::vec3 eye = glm::mix(glm::mix(eye_start, eye_mid, param),
glm::mix(eye_mid, eye_end, param), param);
camera_.SetProjection(glm::value_ptr(eye), target, up, fovy);
return true;
}
void Presentation::OnChildAttached(uint32_t child_key,
mozart::ViewInfoPtr child_view_info,
const OnChildAttachedCallback& callback) {
FXL_DCHECK(child_view_info);
if (kContentViewKey == child_key) {
FXL_VLOG(1) << "OnChildAttached(content): child_view_info="
<< child_view_info;
}
callback();
}
void Presentation::OnChildUnavailable(
uint32_t child_key,
const OnChildUnavailableCallback& callback) {
if (kRootViewKey == child_key) {
FXL_LOG(ERROR) << "Root presenter: Root view terminated unexpectedly.";
Shutdown();
} else if (kContentViewKey == child_key) {
FXL_LOG(ERROR) << "Root presenter: Content view terminated unexpectedly.";
Shutdown();
}
callback();
}
void Presentation::OnPropertiesChanged(
mozart::ViewPropertiesPtr properties,
const OnPropertiesChangedCallback& callback) {
// Nothing to do right now.
callback();
}
// |Presentation|
void Presentation::EnableClipping(bool enabled) {
FXL_LOG(INFO) << "Presentation Controller method called: EnableClipping!!";
}
// |Presentation|
void Presentation::UseOrthographicView() {
FXL_LOG(INFO)
<< "Presentation Controller method called: UseOrthographicView!!";
}
// |Presentation|
void Presentation::UsePerspectiveView() {
FXL_LOG(INFO)
<< "Presentation Controller method called: UsePerspectiveView!!";
}
void Presentation::PresentScene() {
for (auto it = cursors_.begin(); it != cursors_.end(); ++it) {
CursorState& state = it->second;
if (state.visible) {
if (!state.created) {
state.node = std::make_unique<scenic_lib::ShapeNode>(&session_);
state.node->SetShape(cursor_shape_);
state.node->SetMaterial(cursor_material_);
scene_.AddChild(*state.node);
state.created = true;
}
state.node->SetTranslation(state.position.x + kCursorWidth * .5f,
state.position.y + kCursorHeight * .5f,
kCursorElevation);
} else if (state.created) {
state.node->Detach();
state.created = false;
}
}
session_.Present(
0, [weak = weak_factory_.GetWeakPtr()](scenic::PresentationInfoPtr info) {
if (auto self = weak.get()) {
uint64_t next_presentation_time =
info->presentation_time + info->presentation_interval;
if (self->UpdateAnimation(next_presentation_time)) {
self->PresentScene();
}
}
});
}
void Presentation::Shutdown() {
shutdown_callback_();
}
void Presentation::SetRendererParams(
::fidl::Array<scenic::RendererParamPtr> params) {
for (auto& param : params) {
renderer_.SetParam(std::move(param));
}
session_.Present(0, [](scenic::PresentationInfoPtr info) {});
}
} // namespace root_presenter