blob: 773bdb7dd2cbc89690e361f8bc507cb62b18e1aa [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 "garnet/bin/developer/tiles/tiles.h"
#include <lib/async/default.h>
#include "lib/fidl/cpp/optional.h"
constexpr float kTileElevation = 5.f;
namespace tiles {
Tiles::Tiles(component::StartupContext* startup_context,
zx::eventpair root_view_token, std::vector<std::string> urls,
int border)
: startup_context_(startup_context),
root_view_listener_binding_(this),
root_view_container_listener_binding_(this),
session_(scenic::CreateScenicSessionPtrAndListenerRequest(
startup_context_
->ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>()
.get())),
root_node_(&session_),
background_node_(&session_),
container_node_(&session_),
launcher_(startup_context_
->ConnectToEnvironmentService<fuchsia::sys::Launcher>()),
present_scene_task_([this]() { PresentScene(); }),
border_(border) {
// Create a simple background scene.
zx::eventpair root_export_token;
scenic::Material background_material(&session_);
background_material.SetColor(0xFF, 0xE4, 0xE1, 0xFF); // Misty Rose
background_node_.SetMaterial(background_material);
root_node_.BindAsRequest(&root_export_token);
root_node_.AddChild(background_node_);
root_node_.AddChild(container_node_);
// Create a View and export our scene from it.
auto view_manager =
startup_context_
->ConnectToEnvironmentService<fuchsia::ui::viewsv1::ViewManager>();
view_manager->CreateView2(root_view_.NewRequest(), std::move(root_view_token),
root_view_listener_binding_.NewBinding(),
std::move(root_export_token), "Tiles Root");
// Listen for events from the View.
root_view_->GetContainer(root_view_container_.NewRequest());
root_view_container_->SetListener(
root_view_container_listener_binding_.NewBinding());
// Add initial tiles.
for (const auto& url : urls) {
AddTileFromURL(fidl::StringPtr(url), true, {}, {});
}
// Make ourselves available as a |fuchsia.developer.TilesController|.
startup_context_->outgoing().AddPublicService(
tiles_binding_.GetHandler(this));
}
void Tiles::AddTileFromURL(std::string url, bool allow_focus,
fidl::VectorPtr<std::string> args,
AddTileFromURLCallback callback) {
FXL_VLOG(2) << "AddTile " << url;
component::Services services;
fuchsia::sys::ComponentControllerPtr controller;
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = url;
launch_info.arguments = std::move(args);
launch_info.directory_request = services.NewRequest();
launcher_->CreateComponent(std::move(launch_info), controller.NewRequest());
// Create a View from the launched component.
zx::eventpair view_token, view_holder_token;
if (zx::eventpair::create(0u, &view_token, &view_holder_token) != ZX_OK)
FXL_NOTREACHED() << "Failed to create view tokens";
auto view_provider =
services.ConnectToService<fuchsia::ui::app::ViewProvider>();
view_provider->CreateView(std::move(view_token), nullptr, nullptr);
uint32_t child_key = next_child_view_key_++;
AddChildView(child_key, std::move(view_holder_token), url,
std::move(controller), allow_focus);
if (callback)
callback(child_key);
}
void Tiles::AddTileFromViewProvider(
std::string url,
fidl::InterfaceHandle<fuchsia::ui::app::ViewProvider> provider,
AddTileFromViewProviderCallback callback) {
FXL_VLOG(2) << "AddTile " << url;
// Create a View from the ViewProvider.
zx::eventpair view_token, view_holder_token;
if (zx::eventpair::create(0u, &view_token, &view_holder_token) != ZX_OK)
FXL_NOTREACHED() << "Failed to create view tokens";
auto view_provider = provider.Bind();
view_provider->CreateView(std::move(view_token), nullptr, nullptr);
uint32_t child_key = next_child_view_key_++;
AddChildView(child_key, std::move(view_holder_token), url,
nullptr /* controller */, true /* allow_focus */);
if (callback)
callback(child_key);
}
void Tiles::RemoveTile(uint32_t child_key) {
auto it = views_.find(child_key);
if (it == views_.end())
return;
it->second->host_node.Detach();
views_.erase(it);
root_view_container_->RemoveChild2(child_key, zx::eventpair());
InvalidateScene();
}
void Tiles::ListTiles(ListTilesCallback callback) {
std::vector<uint32_t> child_keys;
std::vector<std::string> child_urls;
std::vector<fuchsia::math::SizeF> child_sizes;
std::vector<bool> focusabilities;
for (const auto& it : views_) {
child_keys.push_back(it.first);
child_urls.push_back(it.second->url);
focusabilities.push_back(it.second->allow_focus);
// We may not know every tile's size if we haven't laid everything out yet.
if (it.second->view_properties.view_layout != nullptr) {
child_sizes.push_back(it.second->view_properties.view_layout->size);
} else {
child_sizes.push_back(fuchsia::math::SizeF{});
}
}
callback(fidl::VectorPtr<uint32_t>(std::move(child_keys)),
fidl::VectorPtr<std::string>(std::move(child_urls)),
fidl::VectorPtr<fuchsia::math::SizeF>(std::move(child_sizes)),
fidl::VectorPtr<bool>(std::move(focusabilities)));
}
void Tiles::Quit() { exit(0); }
void Tiles::OnPropertiesChanged(fuchsia::ui::viewsv1::ViewProperties properties,
OnPropertiesChangedCallback callback) {
size_ = properties.view_layout->size;
scenic::Rectangle background_shape(&session_, size_.width, size_.height);
background_node_.SetShape(background_shape);
float center_x = size_.width / 2.f;
float center_y = size_.height / 2.f;
background_node_.SetTranslation(center_x, center_y, 0.f);
callback();
InvalidateScene();
}
void Tiles::OnChildAttached(uint32_t child_key,
fuchsia::ui::viewsv1::ViewInfo child_view_info,
OnChildAttachedCallback callback) {
auto it = views_.find(child_key);
FXL_DCHECK(it != views_.end());
ViewData* view_data = it->second.get();
view_data->view_info = std::move(child_view_info);
callback();
InvalidateScene();
}
void Tiles::OnChildUnavailable(uint32_t child_key,
OnChildUnavailableCallback callback) {
FXL_LOG(ERROR) << "View died unexpectedly: " << child_key;
RemoveTile(child_key);
callback();
InvalidateScene();
}
void Tiles::AddChildView(uint32_t child_key, zx::eventpair view_holder_token,
const std::string& url,
fuchsia::sys::ComponentControllerPtr controller,
bool allow_focus) {
auto view_data = std::make_unique<ViewData>(
url, child_key, std::move(controller), &session_, allow_focus);
zx::eventpair host_import_token;
view_data->host_node.ExportAsRequest(&host_import_token);
container_node_.AddChild(view_data->host_node);
views_.emplace(child_key, std::move(view_data));
root_view_container_->AddChild2(child_key, std::move(view_holder_token),
std::move(host_import_token));
InvalidateScene();
}
void Tiles::InvalidateScene() {
if (present_scene_task_.is_pending())
return;
present_scene_task_.Post(async_get_default_dispatcher());
}
static void Inset(fuchsia::math::RectF* rect, int border) {
float inset = std::min(
{static_cast<float>(border), rect->width / 3.f, rect->height / 3.f});
rect->x += inset;
rect->y += inset;
rect->width -= 2 * inset;
rect->height -= 2 * inset;
}
void Tiles::Layout() {
if (views_.empty())
return;
int num_tiles = views_.size();
int columns = std::ceil(std::sqrt(num_tiles));
int rows = (columns + num_tiles - 1) / columns;
float tile_height = size_.height / rows;
auto view_it = views_.begin();
for (int r = 0; r < rows; ++r) {
// Each row has a tile per column, except possibly the last one.
int tiles_in_row = columns;
if (r == rows - 1 && (num_tiles % columns) != 0)
tiles_in_row = num_tiles % columns;
float tile_width = size_.width / tiles_in_row;
for (int c = 0; c < tiles_in_row; ++c) {
fuchsia::math::RectF tile_bounds;
tile_bounds.x = c * tile_width;
tile_bounds.y = r * tile_height;
tile_bounds.width = tile_width;
tile_bounds.height = tile_height;
Inset(&tile_bounds, border_);
ViewData* tile = view_it->second.get();
fuchsia::ui::viewsv1::ViewProperties view_properties;
view_properties.view_layout = ::fuchsia::ui::viewsv1::ViewLayout::New();
view_properties.view_layout->size.width = tile_bounds.width;
view_properties.view_layout->size.height = tile_bounds.height;
view_properties.custom_focus_behavior =
::fuchsia::ui::viewsv1::CustomFocusBehavior::New();
view_properties.custom_focus_behavior->allow_focus = tile->allow_focus;
if (tile->view_properties != view_properties) {
fuchsia::ui::viewsv1::ViewProperties view_properties_clone;
view_properties.Clone(&view_properties_clone);
tile->view_properties = std::move(view_properties_clone);
root_view_container_->SetChildProperties(
view_it->first, fidl::MakeOptional(std::move(view_properties)));
}
tile->host_node.SetTranslation(tile_bounds.x, tile_bounds.y,
kTileElevation);
++view_it;
}
}
}
void Tiles::PresentScene() {
if (!size_.width || !size_.height)
return;
Layout();
zx_time_t presentation_time = 0;
session_.Present(presentation_time,
[](fuchsia::images::PresentationInfo info) {});
}
Tiles::ViewData::ViewData(const std::string& url, uint32_t key,
fuchsia::sys::ComponentControllerPtr controller,
scenic::Session* session, bool allow_focus)
: url(url),
key(key),
allow_focus(allow_focus),
controller(std::move(controller)),
host_node(session) {}
Tiles::ViewData::~ViewData() {}
} // namespace tiles