| // Copyright 2017 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 <algorithm> |
| |
| #include "garnet/lib/ui/gfx/resources/nodes/node.h" |
| |
| #include <fuchsia/ui/gfx/cpp/fidl.h> |
| #include "garnet/lib/ui/gfx/engine/session.h" |
| #include "garnet/lib/ui/gfx/resources/import.h" |
| #include "garnet/lib/ui/gfx/resources/nodes/traversal.h" |
| #include "garnet/lib/ui/gfx/resources/view.h" |
| #include "garnet/lib/ui/gfx/resources/view_holder.h" |
| #include "lib/escher/geometry/types.h" |
| #include "lib/fxl/logging.h" |
| |
| namespace scenic_impl { |
| namespace gfx { |
| |
| namespace { |
| |
| constexpr ResourceTypeFlags kHasChildren = ResourceType::kEntityNode | |
| ResourceType::kOpacityNode | |
| ResourceType::kScene; |
| constexpr ResourceTypeFlags kHasParts = ResourceType::kEntityNode | |
| ResourceType::kOpacityNode | |
| ResourceType::kClipNode; |
| constexpr ResourceTypeFlags kHasTransform = |
| ResourceType::kClipNode | ResourceType::kEntityNode | |
| ResourceType::kOpacityNode | ResourceType::kScene | |
| ResourceType::kShapeNode; |
| constexpr ResourceTypeFlags kHasClip = ResourceType::kEntityNode; |
| |
| } // anonymous namespace |
| |
| const ResourceTypeInfo Node::kTypeInfo = {ResourceType::kNode, "Node"}; |
| |
| Node::Node(Session* session, ResourceId node_id, |
| const ResourceTypeInfo& type_info) |
| : Resource(session, node_id, type_info) { |
| FXL_DCHECK(type_info.IsKindOf(Node::kTypeInfo)); |
| } |
| |
| Node::~Node() { |
| for (auto& view_holder : view_holders_) { |
| view_holder->Detach(); |
| } |
| ForEachDirectDescendantFrontToBack(*this, [](Node* node) { |
| FXL_DCHECK(node->parent_relation_ != ParentRelation::kNone); |
| |
| // Detach without affecting parent Node (because thats us) or firing the |
| // on_detached_cb_ (because that shouldn't be up to us). |
| node->DetachInternal(); |
| }); |
| } |
| |
| bool Node::SetEventMask(uint32_t event_mask) { |
| if (!Resource::SetEventMask(event_mask)) |
| return false; |
| |
| // If the client unsubscribed from the event, ensure that we will deliver |
| // fresh metrics next time they subscribe. |
| if (!(event_mask & ::fuchsia::ui::gfx::kMetricsEventMask)) { |
| reported_metrics_ = ::fuchsia::ui::gfx::Metrics(); |
| } |
| return true; |
| } |
| |
| bool Node::AddChild(NodePtr child_node) { |
| // TODO(SCN-130): Some node types (e.g. Scenes) cannot be re-parented. We |
| // should add verification to reject such operations. |
| |
| if (!(type_flags() & kHasChildren)) { |
| error_reporter()->ERROR() << "scenic::gfx::Node::AddChild(): node of type '" |
| << type_name() << "' cannot have children."; |
| return false; |
| } |
| |
| if (child_node->parent_relation_ == ParentRelation::kChild && |
| child_node->parent_ == this) { |
| return true; // no change |
| } |
| |
| // Detach and re-attach Node to us. |
| child_node->Detach(); |
| child_node->SetParent(this, ParentRelation::kChild); |
| children_.push_back(std::move(child_node)); |
| return true; |
| } |
| |
| bool Node::AddPart(NodePtr part_node) { |
| if (!(type_flags() & kHasParts)) { |
| error_reporter()->ERROR() << "scenic::gfx::Node::AddPart(): node of type " |
| << type_name() << " cannot have parts."; |
| return false; |
| } |
| |
| if (part_node->parent_relation_ == ParentRelation::kPart && |
| part_node->parent_ == this) { |
| return true; // no change |
| } |
| |
| // Detach and re-attach Node to us. |
| part_node->Detach(); |
| part_node->SetParent(this, ParentRelation::kPart); |
| parts_.push_back(std::move(part_node)); |
| return true; |
| } |
| |
| void Node::SetParent(Node* parent, ParentRelation relation) { |
| FXL_DCHECK(parent_ == nullptr); |
| // A Scene node should always be a root node, and never a child. |
| FXL_DCHECK(!(type_flags() & ResourceType::kScene)) << "A Scene node cannot" |
| << " have a parent"; |
| |
| parent_ = parent; |
| parent_relation_ = relation; |
| RefreshScene(parent_->scene()); |
| } |
| |
| bool Node::AddViewHolder(ViewHolderPtr view_holder) { |
| // Just treat ViewHolders as children for the purposes of capabilities for |
| // now. |
| if (!(type_flags() & kHasChildren)) { |
| error_reporter()->ERROR() |
| << "scenic::gfx::Node::AddViewHolder(): node of type " << type_name() |
| << " cannot have children."; |
| return false; |
| } |
| |
| if (view_holder->parent() == this) { |
| return true; // no change |
| } |
| |
| view_holder->SetParent(this); |
| view_holders_.insert(std::move(view_holder)); |
| |
| return true; |
| } |
| |
| void Node::EraseViewHolder(ViewHolderPtr view_holder) { |
| view_holders_.erase(view_holder); |
| } |
| |
| bool Node::Detach() { |
| if (parent_) { |
| switch (parent_relation_) { |
| case ParentRelation::kChild: |
| parent_->EraseChild(this); |
| break; |
| case ParentRelation::kPart: |
| parent_->ErasePart(this); |
| break; |
| case ParentRelation::kImportDelegate: |
| error_reporter()->ERROR() << "An imported node cannot be detached."; |
| return false; |
| case ParentRelation::kNone: |
| FXL_NOTREACHED(); |
| break; |
| } |
| |
| if (view_ != nullptr) { |
| view_->RemoveChild(this); |
| view_ = nullptr; |
| } |
| |
| DetachInternal(); |
| } |
| return true; |
| } |
| |
| bool Node::DetachChildren() { |
| if (!(type_flags() & kHasChildren)) { |
| error_reporter()->ERROR() |
| << "scenic::gfx::Node::DetachChildren(): node of type '" << type_name() |
| << "' cannot have children."; |
| return false; |
| } |
| for (auto& child : children_) { |
| // Detach without affecting parent Node (because thats us) or firing the |
| // on_detached_cb_ (because that shouldn't be up to us). |
| child->DetachInternal(); |
| } |
| children_.clear(); |
| for (auto& view_holder : view_holders_) { |
| view_holder->Detach(); |
| } |
| view_holders_.clear(); |
| return true; |
| } |
| |
| bool Node::SetTagValue(uint32_t tag_value) { |
| tag_value_ = tag_value; |
| return true; |
| } |
| |
| bool Node::SetTransform(const escher::Transform& transform) { |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() |
| << "scenic::gfx::Node::SetTransform(): node of type " << type_name() |
| << " cannot have transform set."; |
| return false; |
| } |
| transform_ = transform; |
| InvalidateGlobalTransform(); |
| return true; |
| } |
| |
| bool Node::SetTranslation(const escher::vec3& translation) { |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() |
| << "scenic::gfx::Node::SetTranslation(): node of type " << type_name() |
| << " cannot have translation set."; |
| return false; |
| } |
| bound_variables_.erase(NodeProperty::kTranslation); |
| |
| transform_.translation = translation; |
| InvalidateGlobalTransform(); |
| return true; |
| } |
| |
| bool Node::SetTranslation(Vector3VariablePtr translation_variable) { |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() |
| << "scenic::gfx::Node::SetTranslation(): node of type " << type_name() |
| << " cannot have translation set."; |
| return false; |
| } |
| |
| bound_variables_[NodeProperty::kTranslation] = |
| std::make_unique<Vector3VariableBinding>(translation_variable, |
| [this](escher::vec3 value) { |
| transform_.translation = value; |
| InvalidateGlobalTransform(); |
| }); |
| return true; |
| } |
| |
| bool Node::SetScale(const escher::vec3& scale) { |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() << "scenic::gfx::Node::SetScale(): node of type " |
| << type_name() << " cannot have scale set."; |
| return false; |
| } |
| bound_variables_.erase(NodeProperty::kScale); |
| transform_.scale = scale; |
| InvalidateGlobalTransform(); |
| return true; |
| } |
| |
| bool Node::SetScale(Vector3VariablePtr scale_variable) { |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() << "scenic::gfx::Node::SetScale(): node of type " |
| << type_name() << " cannot have scale set."; |
| return false; |
| } |
| bound_variables_[NodeProperty::kScale] = |
| std::make_unique<Vector3VariableBinding>(scale_variable, |
| [this](escher::vec3 value) { |
| transform_.scale = value; |
| InvalidateGlobalTransform(); |
| }); |
| return true; |
| } |
| |
| bool Node::SetRotation(const escher::quat& rotation) { |
| // TODO(SCN-967): Safer handling of quats. Put DCHECK here; validation |
| // should happen elsewhere, before reaching this point. |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() |
| << "scenic::gfx::Node::SetRotation(): node of type " << type_name() |
| << " cannot have rotation set."; |
| return false; |
| } |
| bound_variables_.erase(NodeProperty::kRotation); |
| transform_.rotation = rotation; |
| InvalidateGlobalTransform(); |
| return true; |
| } |
| |
| bool Node::SetRotation(QuaternionVariablePtr rotation_variable) { |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() |
| << "scenic::gfx::Node::SetRotation(): node of type " << type_name() |
| << " cannot have rotation set."; |
| return false; |
| } |
| bound_variables_[NodeProperty::kRotation] = |
| std::make_unique<QuaternionVariableBinding>(rotation_variable, |
| [this](escher::quat value) { |
| transform_.rotation = value; |
| InvalidateGlobalTransform(); |
| }); |
| return true; |
| } |
| |
| bool Node::SetAnchor(const escher::vec3& anchor) { |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() << "scenic::gfx::Node::SetAnchor(): node of type " |
| << type_name() << " cannot have anchor set."; |
| return false; |
| } |
| bound_variables_.erase(NodeProperty::kAnchor); |
| transform_.anchor = anchor; |
| InvalidateGlobalTransform(); |
| return true; |
| } |
| |
| bool Node::SetAnchor(Vector3VariablePtr anchor_variable) { |
| if (!(type_flags() & kHasTransform)) { |
| error_reporter()->ERROR() << "scenic::gfx::Node::SetAnchor(): node of type " |
| << type_name() << " cannot have anchor set."; |
| return false; |
| } |
| bound_variables_[NodeProperty::kAnchor] = |
| std::make_unique<Vector3VariableBinding>(anchor_variable, |
| [this](escher::vec3 value) { |
| transform_.anchor = value; |
| InvalidateGlobalTransform(); |
| }); |
| return true; |
| } |
| |
| bool Node::SetClipToSelf(bool clip_to_self) { |
| if (!(type_flags() & kHasClip)) { |
| error_reporter()->ERROR() |
| << "scenic::gfx::Node::SetClipToSelf(): node of type " << type_name() |
| << " cannot have clip params set."; |
| return false; |
| } |
| clip_to_self_ = clip_to_self; |
| return true; |
| } |
| |
| bool Node::SetHitTestBehavior( |
| ::fuchsia::ui::gfx::HitTestBehavior hit_test_behavior) { |
| hit_test_behavior_ = hit_test_behavior; |
| return true; |
| } |
| |
| bool Node::SendSizeChangeHint(float width_change_factor, |
| float height_change_factor) { |
| if (event_mask() & ::fuchsia::ui::gfx::kSizeChangeHintEventMask) { |
| auto event = ::fuchsia::ui::gfx::Event(); |
| event.set_size_change_hint(::fuchsia::ui::gfx::SizeChangeHintEvent()); |
| event.size_change_hint().node_id = id(); |
| event.size_change_hint().width_change_factor = width_change_factor; |
| event.size_change_hint().height_change_factor = height_change_factor; |
| session()->EnqueueEvent(std::move(event)); |
| } |
| |
| ForEachDirectDescendantFrontToBack( |
| *this, [width_change_factor, height_change_factor](Node* node) { |
| node->SendSizeChangeHint(width_change_factor, height_change_factor); |
| }); |
| return true; |
| } |
| |
| void Node::AddImport(Import* import) { |
| Resource::AddImport(import); |
| |
| auto delegate = static_cast<Node*>(import->delegate()); |
| FXL_DCHECK(delegate->parent_relation_ == ParentRelation::kNone); |
| delegate->parent_ = this; |
| delegate->parent_relation_ = ParentRelation::kImportDelegate; |
| |
| delegate->InvalidateGlobalTransform(); |
| } |
| |
| void Node::RemoveImport(Import* import) { |
| Resource::RemoveImport(import); |
| |
| auto delegate = static_cast<Node*>(import->delegate()); |
| FXL_DCHECK(delegate->parent_relation_ == ParentRelation::kImportDelegate); |
| delegate->parent_relation_ = ParentRelation::kNone; |
| delegate->parent_ = nullptr; |
| |
| delegate->InvalidateGlobalTransform(); |
| } |
| |
| bool Node::GetIntersection(const escher::ray4& ray, float* out_distance) const { |
| return false; |
| } |
| |
| void Node::InvalidateGlobalTransform() { |
| if (!global_transform_dirty_) { |
| global_transform_dirty_ = true; |
| ForEachDirectDescendantFrontToBack( |
| *this, [](Node* node) { node->InvalidateGlobalTransform(); }); |
| } |
| } |
| |
| void Node::ComputeGlobalTransform() const { |
| if (parent_) { |
| global_transform_ = |
| parent_->GetGlobalTransform() * static_cast<escher::mat4>(transform_); |
| } else { |
| global_transform_ = static_cast<escher::mat4>(transform_); |
| } |
| } |
| |
| void Node::EraseChild(Node* child) { |
| auto it = |
| std::find_if(children_.begin(), children_.end(), |
| [child](const NodePtr& ptr) { return child == ptr.get(); }); |
| FXL_DCHECK(it != children_.end()); |
| children_.erase(it); |
| } |
| |
| void Node::ErasePart(Node* part) { |
| auto it = |
| std::find_if(parts_.begin(), parts_.end(), |
| [part](const NodePtr& ptr) { return part == ptr.get(); }); |
| FXL_DCHECK(it != parts_.end()); |
| parts_.erase(it); |
| } |
| |
| void Node::DetachInternal() { |
| parent_relation_ = ParentRelation::kNone; |
| parent_ = nullptr; |
| RefreshScene(nullptr); |
| InvalidateGlobalTransform(); |
| } |
| |
| void Node::RefreshScene(Scene* new_scene) { |
| if (new_scene == scene_) { |
| // Scene is already set on this node and all its children. |
| return; |
| } |
| |
| scene_ = new_scene; |
| for (auto& view_holder : view_holders_) { |
| view_holder->RefreshScene(); |
| } |
| ForEachDirectDescendantFrontToBack( |
| *this, [this](Node* node) { node->RefreshScene(scene_); }); |
| } |
| |
| ResourcePtr Node::FindOwningView() const { |
| const Node* node = this; |
| while (node) { |
| if (View* view = node->view()) { |
| return ViewPtr(view); |
| } |
| |
| // TODO(SCN-1006): After v2 transition, remove this clause. |
| if (node->is_exported() && // Exported |
| node->imports().size() > 0 && // Imported |
| node->tag_value() > 0) { // Used by ViewManager |
| FXL_DCHECK(node->imports().size() == 1); |
| return ImportPtr(node->imports()[0]); |
| } |
| |
| node = node->parent(); |
| } |
| return nullptr; |
| } |
| |
| } // namespace gfx |
| } // namespace scenic_impl |