| // Copyright 2015 The Chromium 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 "base/bind.h" |
| #include "examples/ui/tile/tile_view.h" |
| #include "mojo/services/geometry/cpp/geometry_util.h" |
| |
| namespace examples { |
| |
| namespace { |
| constexpr uint32_t kViewResourceIdBase = 100; |
| constexpr uint32_t kViewResourceIdSpacing = 100; |
| |
| constexpr uint32_t kRootNodeId = mojo::gfx::composition::kSceneRootNodeId; |
| constexpr uint32_t kViewNodeIdBase = 100; |
| constexpr uint32_t kViewNodeIdSpacing = 100; |
| constexpr uint32_t kViewSceneNodeIdOffset = 1; |
| constexpr uint32_t kViewFallbackNodeIdOffset = 2; |
| } // namespace |
| |
| TileView::TileView( |
| mojo::ApplicationImpl* app_impl, |
| mojo::InterfaceRequest<mojo::ui::ViewOwner> view_owner_request, |
| const std::vector<std::string>& view_urls) |
| : BaseView(app_impl, view_owner_request.Pass(), "Tile"), |
| view_urls_(view_urls) { |
| ConnectViews(); |
| } |
| |
| TileView::~TileView() {} |
| |
| void TileView::ConnectViews() { |
| uint32_t child_key = 0; |
| for (const auto& url : view_urls_) { |
| // Start connecting to the view provider. |
| mojo::ui::ViewProviderPtr provider; |
| app_impl()->ConnectToService(url, &provider); |
| |
| LOG(INFO) << "Connecting to view: child_key=" << child_key |
| << ", url=" << url; |
| mojo::ui::ViewOwnerPtr child_view_owner; |
| provider->CreateView(mojo::GetProxy(&child_view_owner), nullptr, nullptr); |
| |
| view()->AddChild(child_key, child_view_owner.Pass()); |
| views_.emplace(std::make_pair( |
| child_key, std::unique_ptr<ViewData>(new ViewData(url, child_key)))); |
| |
| child_key++; |
| } |
| } |
| |
| void TileView::OnChildUnavailable(uint32_t child_key, |
| const OnChildUnavailableCallback& callback) { |
| auto it = views_.find(child_key); |
| DCHECK(it != views_.end()); |
| LOG(ERROR) << "View died unexpectedly: child_key=" << child_key |
| << ", url=" << it->second->url; |
| |
| std::unique_ptr<ViewData> view_data = std::move(it->second); |
| views_.erase(it); |
| |
| view()->RemoveChild(child_key, nullptr); |
| |
| if (view_data->layout_pending) { |
| DCHECK(pending_child_layout_count_); |
| pending_child_layout_count_--; |
| FinishLayout(); |
| } |
| |
| callback.Run(); |
| } |
| |
| void TileView::OnLayout(mojo::ui::ViewLayoutParamsPtr layout_params, |
| mojo::Array<uint32_t> children_needing_layout, |
| const OnLayoutCallback& callback) { |
| size_.width = layout_params->constraints->max_width; |
| size_.height = layout_params->constraints->max_height; |
| |
| // Wipe out cached layout information for children needing layout. |
| for (uint32_t child_key : children_needing_layout) { |
| auto view_it = views_.find(child_key); |
| if (view_it != views_.end()) |
| view_it->second->layout_info.reset(); |
| } |
| |
| // Layout all children in a row. |
| if (!views_.empty()) { |
| uint32_t index = 0; |
| uint32_t base_width = size_.width / views_.size(); |
| uint32_t excess_width = size_.width % views_.size(); |
| uint32_t x = 0; |
| for (auto it = views_.begin(); it != views_.end(); ++it, ++index) { |
| ViewData* view_data = it->second.get(); |
| DCHECK(!view_data->layout_pending); |
| |
| // Distribute any excess width among the leading children. |
| uint32_t child_width = base_width; |
| if (excess_width) { |
| child_width++; |
| excess_width--; |
| } |
| uint32_t child_height = size_.height; |
| uint32_t child_x = x; |
| x += child_width; |
| |
| view_data->layout_bounds.x = child_x; |
| view_data->layout_bounds.y = 0; |
| view_data->layout_bounds.width = child_width; |
| view_data->layout_bounds.height = child_height; |
| |
| mojo::ui::ViewLayoutParamsPtr params = mojo::ui::ViewLayoutParams::New(); |
| params->constraints = mojo::ui::BoxConstraints::New(); |
| params->constraints->min_width = child_width; |
| params->constraints->max_width = child_width; |
| params->constraints->min_height = child_height; |
| params->constraints->max_height = child_height; |
| params->device_pixel_ratio = layout_params->device_pixel_ratio; |
| |
| if (view_data->layout_info && view_data->layout_params.Equals(params)) |
| continue; // no layout work to do |
| |
| pending_child_layout_count_++; |
| view_data->layout_pending = true; |
| view_data->layout_params = params.Clone(); |
| view_data->layout_info.reset(); |
| |
| view()->LayoutChild(it->first, params.Pass(), |
| base::Bind(&TileView::OnChildLayoutFinished, |
| base::Unretained(this), it->first)); |
| } |
| } |
| |
| // Store the callback until layout of all children is finished. |
| pending_layout_callback_ = callback; |
| FinishLayout(); |
| } |
| |
| void TileView::OnChildLayoutFinished( |
| uint32_t child_key, |
| mojo::ui::ViewLayoutInfoPtr child_layout_info) { |
| auto it = views_.find(child_key); |
| if (it != views_.end()) { |
| ViewData* view_data = it->second.get(); |
| DCHECK(view_data->layout_pending); |
| DCHECK(pending_child_layout_count_); |
| pending_child_layout_count_--; |
| view_data->layout_pending = false; |
| view_data->layout_info = child_layout_info.Pass(); |
| FinishLayout(); |
| } |
| } |
| |
| void TileView::FinishLayout() { |
| if (pending_layout_callback_.is_null()) |
| return; |
| |
| // Wait until all children have laid out. |
| // TODO(jeffbrown): There should be a timeout on this. |
| if (pending_child_layout_count_) |
| return; |
| |
| // Update the scene. |
| // TODO: only send the resources once, be more incremental |
| auto update = mojo::gfx::composition::SceneUpdate::New(); |
| |
| // Create the root node. |
| auto root_node = mojo::gfx::composition::Node::New(); |
| |
| // Add the children. |
| for (auto it = views_.cbegin(); it != views_.cend(); it++) { |
| const ViewData& view_data = *(it->second.get()); |
| const uint32_t scene_resource_id = |
| kViewResourceIdBase + view_data.key * kViewResourceIdSpacing; |
| const uint32_t container_node_id = |
| kViewNodeIdBase + view_data.key * kViewNodeIdSpacing; |
| const uint32_t scene_node_id = container_node_id + kViewSceneNodeIdOffset; |
| const uint32_t fallback_node_id = |
| container_node_id + kViewFallbackNodeIdOffset; |
| |
| mojo::Rect extent; |
| extent.width = view_data.layout_bounds.width; |
| extent.height = view_data.layout_bounds.height; |
| |
| // Create a container to represent the place where the child view |
| // will be presented. The children of the container provide |
| // fallback behavior in case the view is not available. |
| auto container_node = mojo::gfx::composition::Node::New(); |
| container_node->content_clip = extent.Clone(); |
| container_node->content_transform = mojo::Transform::New(); |
| SetTranslationTransform(container_node->content_transform.get(), |
| view_data.layout_bounds.x, |
| view_data.layout_bounds.y, 0.f); |
| container_node->combinator = |
| mojo::gfx::composition::Node::Combinator::FALLBACK; |
| |
| // If we have the view, add it to the scene. |
| if (view_data.layout_info) { |
| auto scene_resource = mojo::gfx::composition::Resource::New(); |
| scene_resource->set_scene(mojo::gfx::composition::SceneResource::New()); |
| scene_resource->get_scene()->scene_token = |
| view_data.layout_info->scene_token.Clone(); |
| update->resources.insert(scene_resource_id, scene_resource.Pass()); |
| |
| auto scene_node = mojo::gfx::composition::Node::New(); |
| scene_node->op = mojo::gfx::composition::NodeOp::New(); |
| scene_node->op->set_scene(mojo::gfx::composition::SceneNodeOp::New()); |
| scene_node->op->get_scene()->scene_resource_id = scene_resource_id; |
| update->nodes.insert(scene_node_id, scene_node.Pass()); |
| container_node->child_node_ids.push_back(scene_node_id); |
| } else { |
| update->resources.insert(fallback_node_id, nullptr); |
| update->nodes.insert(scene_node_id, nullptr); |
| } |
| |
| // Add the fallback content. |
| auto fallback_node = mojo::gfx::composition::Node::New(); |
| fallback_node->op = mojo::gfx::composition::NodeOp::New(); |
| fallback_node->op->set_rect(mojo::gfx::composition::RectNodeOp::New()); |
| fallback_node->op->get_rect()->content_rect = extent.Clone(); |
| fallback_node->op->get_rect()->color = mojo::gfx::composition::Color::New(); |
| fallback_node->op->get_rect()->color->red = 255; |
| fallback_node->op->get_rect()->color->alpha = 255; |
| update->nodes.insert(fallback_node_id, fallback_node.Pass()); |
| container_node->child_node_ids.push_back(fallback_node_id); |
| |
| // Add the container. |
| update->nodes.insert(container_node_id, container_node.Pass()); |
| root_node->child_node_ids.push_back(container_node_id); |
| } |
| |
| // Add the root node. |
| update->nodes.insert(kRootNodeId, root_node.Pass()); |
| |
| // Publish the scene. |
| scene()->Update(update.Pass()); |
| scene()->Publish(nullptr); |
| |
| // Submit the new layout information. |
| auto info = mojo::ui::ViewLayoutResult::New(); |
| info->size = size_.Clone(); |
| pending_layout_callback_.Run(info.Pass()); |
| pending_layout_callback_.reset(); |
| } |
| |
| TileView::ViewData::ViewData(const std::string& url, uint32_t key) |
| : url(url), key(key), layout_pending(false) {} |
| |
| TileView::ViewData::~ViewData() {} |
| |
| } // namespace examples |