text: // Copyright (c) 2016 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 "chrome/browser/vr/ui_scene.h"
  
  #include <utility>
  #include <vector>
  
  #include "base/numerics/math_constants.h"
  #include "base/test/gtest_util.h"
  #include "base/values.h"
  #include "chrome/browser/vr/databinding/binding.h"
  #include "chrome/browser/vr/elements/draw_phase.h"
  #include "chrome/browser/vr/elements/transient_element.h"
  #include "chrome/browser/vr/elements/ui_element.h"
  #include "chrome/browser/vr/elements/viewport_aware_root.h"
  #include "chrome/browser/vr/test/animation_utils.h"
  #include "chrome/browser/vr/test/constants.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "ui/gfx/geometry/transform_util.h"
  #include "ui/gfx/geometry/vector3d_f.h"
  
  #define TOLERANCE 0.0001
  
  #define EXPECT_VEC3F_NEAR(a, b)         \
    EXPECT_NEAR(a.x(), b.x(), TOLERANCE); \
    EXPECT_NEAR(a.y(), b.y(), TOLERANCE); \
    EXPECT_NEAR(a.z(), b.z(), TOLERANCE);
  
  namespace vr {
  
  namespace {
  
  size_t NumElementsInSubtree(UiElement* element) {
    size_t count = 1;
    for (auto& child : element->children()) {
      count += NumElementsInSubtree(child.get());
    }
    return count;
  }
  
  class AlwaysDirty : public UiElement {
   public:
    ~AlwaysDirty() override {}
  
    bool OnBeginFrame(const gfx::Transform& head_pose) override { return true; }
  };
  
  }  // namespace
  
  TEST(UiScene, AddRemoveElements) {
    UiScene scene;
  
    // Always start with the root element.
    EXPECT_EQ(NumElementsInSubtree(&scene.root_element()), 1u);
  
    auto element = std::make_unique<UiElement>();
    element->SetDrawPhase(kPhaseForeground);
    UiElement* parent = element.get();
    int parent_id = parent->id();
    scene.AddUiElement(kRoot, std::move(element));
  
    EXPECT_EQ(NumElementsInSubtree(&scene.root_element()), 2u);
  
    element = std::make_unique<UiElement>();
    element->SetDrawPhase(kPhaseForeground);
    UiElement* child = element.get();
    int child_id = child->id();
  
    parent->AddChild(std::move(element));
  
    EXPECT_EQ(NumElementsInSubtree(&scene.root_element()), 3u);
  
    EXPECT_NE(scene.GetUiElementById(parent_id), nullptr);
    EXPECT_NE(scene.GetUiElementById(child_id), nullptr);
    EXPECT_EQ(scene.GetUiElementById(-1), nullptr);
  
    auto removed_child = scene.RemoveUiElement(child_id);
    EXPECT_EQ(removed_child.get(), child);
    EXPECT_EQ(NumElementsInSubtree(&scene.root_element()), 2u);
    EXPECT_EQ(scene.GetUiElementById(child_id), nullptr);
  
    auto removed_parent = scene.RemoveUiElement(parent_id);
    EXPECT_EQ(removed_parent.get(), parent);
    EXPECT_EQ(NumElementsInSubtree(&scene.root_element()), 1u);
    EXPECT_EQ(scene.GetUiElementById(parent_id), nullptr);
  }
  
  TEST(UiScene, IsVisibleInHiddenSubtree) {
    UiScene scene;
  
    // Always start with the root element.
    EXPECT_EQ(NumElementsInSubtree(&scene.root_element()), 1u);
  
    auto element = std::make_unique<UiElement>();
    element->SetDrawPhase(kPhaseForeground);
    UiElement* parent = element.get();
    scene.AddUiElement(kRoot, std::move(element));
  
    element = std::make_unique<UiElement>();
    element->SetDrawPhase(kPhaseForeground);
    UiElement* child = element.get();
  
    parent->AddChild(std::move(element));
  
    // Set initial computed opacity.
    scene.OnBeginFrame(gfx::MsToTicks(1), kStartHeadPose);
  
    parent->SetVisible(false);
  
    scene.OnBeginFrame(gfx::MsToTicks(2), kStartHeadPose);
  
    // On the second walk, we should skip the child.
    scene.OnBeginFrame(gfx::MsToTicks(3), kStartHeadPose);
  
    EXPECT_FALSE(child->IsVisible());
  }
  
  // This test creates a parent and child UI element, each with their own
  // transformations, and ensures that the child's computed total transform
  // incorporates the parent's transform as well as its own.
  TEST(UiScene, ParentTransformAppliesToChild) {
    UiScene scene;
  
    // Add a parent element, with distinct transformations.
    // Size of the parent should be ignored by the child.
    auto element = std::make_unique<UiElement>();
    UiElement* parent = element.get();
    element->SetSize(1000, 1000);
  
    element->SetTranslate(6, 1, 0);
    element->SetRotate(0, 0, 1, 0.5f * base::kPiFloat);
    element->SetScale(3, 3, 1);
    scene.AddUiElement(kRoot, std::move(element));
  
    // Add a child to the parent, with different transformations.
    element = std::make_unique<UiElement>();
    element->SetTranslate(3, 0, 0);
    element->SetRotate(0, 0, 1, 0.5f * base::kPiFloat);
    element->SetScale(2, 2, 1);
    UiElement* child = element.get();
    parent->AddChild(std::move(element));
  
    gfx::Point3F origin(0, 0, 0);
    gfx::Point3F point(1, 0, 0);
  
    scene.OnBeginFrame(gfx::MsToTicks(0), kStartHeadPose);
    child->world_space_transform().TransformPoint(&origin);
    child->world_space_transform().TransformPoint(&point);
    EXPECT_VEC3F_NEAR(gfx::Point3F(6, 10, 0), origin);
    EXPECT_VEC3F_NEAR(gfx::Point3F(0, 10, 0), point);
  }
  
  TEST(UiScene, Opacity) {
    UiScene scene;
  
    auto element = std::make_unique<UiElement>();
    UiElement* parent = element.get();
    element->SetOpacity(0.5);
    scene.AddUiElement(kRoot, std::move(element));
  
    element = std::make_unique<UiElement>();
    UiElement* child = element.get();
    element->SetOpacity(0.5);
    parent->AddChild(std::move(element));
  
    scene.OnBeginFrame(gfx::MsToTicks(0), kStartHeadPose);
    EXPECT_EQ(0.5f, parent->computed_opacity());
    EXPECT_EQ(0.25f, child->computed_opacity());
  }
  
  TEST(UiScene, NoViewportAwareElementWhenNoVisibleChild) {
    UiScene scene;
    auto element = std::make_unique<UiElement>();
    UiElement* container = element.get();
    element->SetName(kWebVrRoot);
    scene.AddUiElement(kRoot, std::move(element));
  
    auto root = std::make_unique<ViewportAwareRoot>();
    UiElement* viewport_aware_root = root.get();
    container->AddChild(std::move(root));
  
    element = std::make_unique<UiElement>();
    UiElement* child = element.get();
    element->SetDrawPhase(kPhaseOverlayForeground);
    viewport_aware_root->AddChild(std::move(element));
  
    element = std::make_unique<UiElement>();
    element->SetDrawPhase(kPhaseOverlayForeground);
    child->AddChild(std::move(element));
  
    EXPECT_FALSE(scene.GetWebVrOverlayElementsToDraw().empty());
    child->SetVisible(false);
    scene.OnBeginFrame(gfx::MsToTicks(0), kStartHeadPose);
    EXPECT_TRUE(scene.GetWebVrOverlayElementsToDraw().empty());
  }
  
  TEST(UiScene, InvisibleElementsDoNotCauseAnimationDirtiness) {
    UiScene scene;
    auto element = std::make_unique<UiElement>();
    element->AddKeyframeModel(gfx::CreateColorAnimation(
        element.get(), 1, BACKGROUND_COLOR, SK_ColorBLACK, SK_ColorWHITE,
        gfx::MsToDelta(1000)));
    UiElement* element_ptr = element.get();
    scene.AddUiElement(kRoot, std::move(element));
    EXPECT_TRUE(scene.OnBeginFrame(gfx::MsToTicks(1), kStartHeadPose));
  
    element_ptr->SetVisible(false);
    element_ptr->UpdateComputedOpacity();
    EXPECT_FALSE(scene.OnBeginFrame(gfx::MsToTicks(2), kStartHeadPose));
  }
  
  TEST(UiScene, InvisibleElementsDoNotCauseBindingDirtiness) {
    UiScene scene;
    auto element = std::make_unique<UiElement>();
    struct FakeModel {
      int foo = 1;
    } model;
    element->AddBinding(VR_BIND(int, FakeModel, &model, model->foo, UiElement,
                                element.get(), view->SetSize(1, value)));
    UiElement* element_ptr = element.get();
    scene.AddUiElement(kRoot, std::move(element));
    EXPECT_TRUE(scene.OnBeginFrame(gfx::MsToTicks(1), kStartHeadPose));
  
    model.foo = 2;
    element_ptr->SetVisible(false);
    element_ptr->UpdateComputedOpacity();
    EXPECT_FALSE(scene.OnBeginFrame(gfx::MsToTicks(2), kStartHeadPose));
  }
  
  TEST(UiScene, InvisibleElementsDoNotCauseOnBeginFrameDirtiness) {
    UiScene scene;
    auto element = std::make_unique<AlwaysDirty>();
    UiElement* element_ptr = element.get();
    scene.AddUiElement(kRoot, std::move(element));
    EXPECT_TRUE(scene.OnBeginFrame(gfx::MsToTicks(1), kStartHeadPose));
  
    element_ptr->SetVisible(false);
    element_ptr->UpdateComputedOpacity();
    EXPECT_FALSE(scene.OnBeginFrame(gfx::MsToTicks(2), kStartHeadPose));
  }
  
  typedef struct {
    LayoutAlignment x_anchoring;
    LayoutAlignment y_anchoring;
    LayoutAlignment x_centering;
    LayoutAlignment y_centering;
    float expected_x;
    float expected_y;
  } AlignmentTestCase;
  
  class AlignmentTest : public ::testing::TestWithParam<AlignmentTestCase> {};
  
  TEST_P(AlignmentTest, VerifyCorrectPosition) {
    UiScene scene;
  
    // Create a parent element with non-unity size and scale.
    auto element = std::make_unique<UiElement>();
    UiElement* parent = element.get();
    element->SetSize(2, 2);
    element->SetScale(2, 2, 1);
    scene.AddUiElement(kRoot, std::move(element));
  
    // Add a child to the parent, with anchoring.
    element = std::make_unique<UiElement>();
    UiElement* child = element.get();
    element->SetSize(1, 1);
    element->set_contributes_to_parent_bounds(false);
    element->set_x_anchoring(GetParam().x_anchoring);
    element->set_y_anchoring(GetParam().y_anchoring);
    element->set_x_centering(GetParam().x_centering);
    element->set_y_centering(GetParam().y_centering);
    parent->AddChild(std::move(element));
  
    scene.OnBeginFrame(gfx::MsToTicks(0), kStartHeadPose);
    EXPECT_NEAR(GetParam().expected_x, child->GetCenter().x(), TOLERANCE);
    EXPECT_NEAR(GetParam().expected_y, child->GetCenter().y(), TOLERANCE);
  }
  
  const std::vector<AlignmentTestCase> alignment_test_cases = {
      // Test anchoring.
      {NONE, NONE, NONE, NONE, 0, 0},
      {LEFT, NONE, NONE, NONE, -2, 0},
      {RIGHT, NONE, NONE, NONE, 2, 0},
      {NONE, TOP, NONE, NONE, 0, 2},
      {NONE, BOTTOM, NONE, NONE, 0, -2},
      {LEFT, TOP, NONE, NONE, -2, 2},
      // Test centering.
      {NONE, NONE, LEFT, NONE, 1, 0},
      {NONE, NONE, RIGHT, NONE, -1, 0},
      {NONE, NONE, NONE, TOP, 0, -1},
      {NONE, NONE, NONE, BOTTOM, 0, 1},
      {NONE, NONE, LEFT, TOP, 1, -1},
      // Test a combination of the two.
      {RIGHT, TOP, LEFT, BOTTOM, 3, 3},
  };
  
  INSTANTIATE_TEST_SUITE_P(AlignmentTestCases,
                           AlignmentTest,
                           ::testing::ValuesIn(alignment_test_cases));
  
  }  // namespace vr
  