blob: b614ef8c6de413d58691959b5a8848c48e8d068a [file] [log] [blame]
// 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/vector3d_f.h"
#include "ui/gfx/transform_util.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(MsToTicks(1), kStartHeadPose);
parent->SetVisible(false);
scene.OnBeginFrame(MsToTicks(2), kStartHeadPose);
// On the second walk, we should skip the child.
scene.OnBeginFrame(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(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(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(MsToTicks(0), kStartHeadPose);
EXPECT_TRUE(scene.GetWebVrOverlayElementsToDraw().empty());
}
TEST(UiScene, InvisibleElementsDoNotCauseAnimationDirtiness) {
UiScene scene;
auto element = std::make_unique<UiElement>();
element->AddKeyframeModel(CreateBackgroundColorAnimation(
1, 1, SK_ColorBLACK, SK_ColorWHITE, MsToDelta(1000)));
UiElement* element_ptr = element.get();
scene.AddUiElement(kRoot, std::move(element));
EXPECT_TRUE(scene.OnBeginFrame(MsToTicks(1), kStartHeadPose));
element_ptr->SetVisible(false);
element_ptr->UpdateComputedOpacity();
EXPECT_FALSE(scene.OnBeginFrame(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(MsToTicks(1), kStartHeadPose));
model.foo = 2;
element_ptr->SetVisible(false);
element_ptr->UpdateComputedOpacity();
EXPECT_FALSE(scene.OnBeginFrame(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(MsToTicks(1), kStartHeadPose));
element_ptr->SetVisible(false);
element_ptr->UpdateComputedOpacity();
EXPECT_FALSE(scene.OnBeginFrame(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(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