blob: 0a33d2bd2fdcc31b60a55ed6cf05092d2b82154d [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/elements/ui_element.h"
#include <utility>
#include "base/bind.h"
#include "base/macros.h"
#include "cc/animation/keyframe_model.h"
#include "cc/animation/keyframed_animation_curve.h"
#include "cc/test/geometry_test_utils.h"
#include "chrome/browser/vr/test/animation_utils.h"
#include "chrome/browser/vr/test/constants.h"
#include "chrome/browser/vr/ui_scene.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace vr {
TEST(UiElement, BoundsContainChildren) {
auto parent = std::make_unique<UiElement>();
parent->set_bounds_contain_children(true);
parent->set_padding(0.1, 0.2);
auto c1 = std::make_unique<UiElement>();
c1->SetSize(3.0f, 3.0f);
c1->SetTranslate(2.5f, 2.5f, 0.0f);
auto* c1_ptr = c1.get();
parent->AddChild(std::move(c1));
parent->DoLayOutChildren();
EXPECT_RECT_NEAR(gfx::RectF(2.5f, 2.5f, 3.2f, 3.4f),
gfx::RectF(parent->local_origin(), parent->size()),
kEpsilon);
EXPECT_EQ(parent->GetCenter().ToString(), c1_ptr->GetCenter().ToString());
auto c2 = std::make_unique<UiElement>();
c2->SetSize(4.0f, 4.0f);
c2->SetTranslate(-3.0f, 0.0f, 0.0f);
parent->AddChild(std::move(c2));
parent->DoLayOutChildren();
EXPECT_RECT_NEAR(gfx::RectF(-0.5f, 1.0f, 9.2f, 6.4f),
gfx::RectF(parent->local_origin(), parent->size()),
kEpsilon);
auto c3 = std::make_unique<UiElement>();
c3->SetSize(2.0f, 2.0f);
c3->SetTranslate(0.0f, -2.0f, 0.0f);
parent->AddChild(std::move(c3));
parent->DoLayOutChildren();
EXPECT_RECT_NEAR(gfx::RectF(-0.5f, 0.5f, 9.2f, 7.4f),
gfx::RectF(parent->local_origin(), parent->size()),
kEpsilon);
auto c4 = std::make_unique<UiElement>();
c4->SetSize(2.0f, 2.0f);
c4->SetTranslate(20.0f, 20.0f, 0.0f);
c4->SetVisible(false);
parent->AddChild(std::move(c4));
// We expect no change due to an invisible child.
parent->DoLayOutChildren();
EXPECT_RECT_NEAR(gfx::RectF(-0.5f, 0.5f, 9.2f, 7.4f),
gfx::RectF(parent->local_origin(), parent->size()),
kEpsilon);
auto grand_parent = std::make_unique<UiElement>();
grand_parent->set_bounds_contain_children(true);
grand_parent->set_padding(0.1, 0.2);
grand_parent->AddChild(std::move(parent));
grand_parent->DoLayOutChildren();
EXPECT_RECT_NEAR(
gfx::RectF(-0.5f, 0.5f, 9.4f, 7.8f),
gfx::RectF(grand_parent->local_origin(), grand_parent->size()), kEpsilon);
}
TEST(UiElement, BoundsContainScaledChildren) {
auto a = std::make_unique<UiElement>();
a->SetSize(0.4, 0.3);
auto b = std::make_unique<UiElement>();
b->SetSize(0.2, 0.2);
b->SetTranslate(0.6, 0.0, 0.0);
b->SetScale(2.0, 3.0, 1.0);
auto c = std::make_unique<UiElement>();
c->set_bounds_contain_children(true);
c->set_padding(0.05, 0.05);
c->AddChild(std::move(a));
c->AddChild(std::move(b));
c->DoLayOutChildren();
EXPECT_RECT_NEAR(gfx::RectF(0.3f, 0.0f, 1.1f, 0.7f),
gfx::RectF(c->local_origin(), c->size()), kEpsilon);
}
TEST(UiElement, AnimateSize) {
UiScene scene;
auto rect = std::make_unique<UiElement>();
rect->SetSize(10, 100);
rect->AddKeyframeModel(CreateBoundsAnimation(1, 1, gfx::SizeF(10, 100),
gfx::SizeF(20, 200),
MicrosecondsToDelta(10000)));
UiElement* rect_ptr = rect.get();
scene.AddUiElement(kRoot, std::move(rect));
base::TimeTicks start_time = MicrosecondsToTicks(1);
EXPECT_TRUE(scene.OnBeginFrame(start_time, kStartHeadPose));
EXPECT_FLOAT_SIZE_EQ(gfx::SizeF(10, 100), rect_ptr->size());
EXPECT_TRUE(scene.OnBeginFrame(start_time + MicrosecondsToDelta(10000),
kStartHeadPose));
EXPECT_FLOAT_SIZE_EQ(gfx::SizeF(20, 200), rect_ptr->size());
}
TEST(UiElement, AnimationAffectsInheritableTransform) {
UiScene scene;
auto rect = std::make_unique<UiElement>();
UiElement* rect_ptr = rect.get();
scene.AddUiElement(kRoot, std::move(rect));
cc::TransformOperations from_operations;
from_operations.AppendTranslate(10, 100, 1000);
cc::TransformOperations to_operations;
to_operations.AppendTranslate(20, 200, 2000);
rect_ptr->AddKeyframeModel(CreateTransformAnimation(
2, 2, from_operations, to_operations, MicrosecondsToDelta(10000)));
base::TimeTicks start_time = MicrosecondsToTicks(1);
EXPECT_TRUE(scene.OnBeginFrame(start_time, kStartHeadPose));
gfx::Point3F p;
rect_ptr->LocalTransform().TransformPoint(&p);
EXPECT_VECTOR3DF_EQ(gfx::Vector3dF(10, 100, 1000), p);
p = gfx::Point3F();
EXPECT_TRUE(scene.OnBeginFrame(start_time + MicrosecondsToDelta(10000),
kStartHeadPose));
rect_ptr->LocalTransform().TransformPoint(&p);
EXPECT_VECTOR3DF_EQ(gfx::Vector3dF(20, 200, 2000), p);
}
TEST(UiElement, HitTest) {
UiElement rect;
rect.SetSize(1.0, 1.0);
UiElement circle;
circle.SetSize(1.0, 1.0);
circle.set_corner_radius(1.0 / 2);
UiElement rounded_rect;
rounded_rect.SetSize(1.0, 0.5);
rounded_rect.set_corner_radius(0.2);
struct {
gfx::PointF location;
bool expected_rect;
bool expected_circle;
bool expected_rounded_rect;
} test_cases[] = {
// Walk left edge
{gfx::PointF(0.f, 0.1f), true, false, false},
{gfx::PointF(0.f, 0.45f), true, false, true},
{gfx::PointF(0.f, 0.55f), true, false, true},
{gfx::PointF(0.f, 0.95f), true, false, false},
{gfx::PointF(0.f, 1.f), true, false, false},
// Walk bottom edge
{gfx::PointF(0.1f, 1.f), true, false, false},
{gfx::PointF(0.45f, 1.f), true, false, true},
{gfx::PointF(0.55f, 1.f), true, false, true},
{gfx::PointF(0.95f, 1.f), true, false, false},
{gfx::PointF(1.0f, 1.f), true, false, false},
// Walk right edge
{gfx::PointF(1.f, 0.95f), true, false, false},
{gfx::PointF(1.f, 0.55f), true, false, true},
{gfx::PointF(1.f, 0.45f), true, false, true},
{gfx::PointF(1.f, 0.1f), true, false, false},
{gfx::PointF(1.f, 0.f), true, false, false},
// Walk top edge
{gfx::PointF(0.95f, 0.f), true, false, false},
{gfx::PointF(0.55f, 0.f), true, false, true},
{gfx::PointF(0.45f, 0.f), true, false, true},
{gfx::PointF(0.1f, 0.f), true, false, false},
{gfx::PointF(0.f, 0.f), true, false, false},
// center
{gfx::PointF(0.5f, 0.5f), true, true, true},
// A point which is included in rounded rect but not in cicle.
{gfx::PointF(0.1f, 0.1f), true, false, true},
// An invalid point.
{gfx::PointF(-0.1f, -0.1f), false, false, false},
};
for (size_t i = 0; i < arraysize(test_cases); ++i) {
SCOPED_TRACE(i);
EXPECT_EQ(test_cases[i].expected_rect,
rect.LocalHitTest(test_cases[i].location));
EXPECT_EQ(test_cases[i].expected_circle,
circle.LocalHitTest(test_cases[i].location));
EXPECT_EQ(test_cases[i].expected_rounded_rect,
rounded_rect.LocalHitTest(test_cases[i].location));
}
}
class ElementEventHandlers {
public:
explicit ElementEventHandlers(UiElement* element) {
DCHECK(element);
EventHandlers event_handlers;
event_handlers.hover_enter = base::BindRepeating(
&ElementEventHandlers::HandleHoverEnter, base::Unretained(this));
event_handlers.hover_move = base::BindRepeating(
&ElementEventHandlers::HandleHoverMove, base::Unretained(this));
event_handlers.hover_leave = base::BindRepeating(
&ElementEventHandlers::HandleHoverLeave, base::Unretained(this));
event_handlers.button_down = base::BindRepeating(
&ElementEventHandlers::HandleButtonDown, base::Unretained(this));
event_handlers.button_up = base::BindRepeating(
&ElementEventHandlers::HandleButtonUp, base::Unretained(this));
element->set_event_handlers(event_handlers);
}
void HandleHoverEnter() { hover_enter_ = true; }
bool hover_enter_called() { return hover_enter_; }
void HandleHoverMove(const gfx::PointF& position) { hover_move_ = true; }
bool hover_move_called() { return hover_move_; }
void HandleHoverLeave() { hover_leave_ = true; }
bool hover_leave_called() { return hover_leave_; }
void HandleButtonDown() { button_down_ = true; }
bool button_down_called() { return button_down_; }
void HandleButtonUp() { button_up_ = true; }
bool button_up_called() { return button_up_; }
void ExpectCalled(bool called) {
EXPECT_EQ(hover_enter_called(), called);
EXPECT_EQ(hover_move_called(), called);
EXPECT_EQ(hover_leave_called(), called);
EXPECT_EQ(button_down_called(), called);
EXPECT_EQ(button_up_called(), called);
}
private:
bool hover_enter_ = false;
bool hover_move_ = false;
bool hover_leave_ = false;
bool button_up_ = false;
bool button_down_ = false;
DISALLOW_COPY_AND_ASSIGN(ElementEventHandlers);
};
TEST(UiElement, EventBubbling) {
auto element = std::make_unique<UiElement>();
auto child = std::make_unique<UiElement>();
auto grand_child = std::make_unique<UiElement>();
auto* child_ptr = child.get();
auto* grand_child_ptr = grand_child.get();
child->AddChild(std::move(grand_child));
element->AddChild(std::move(child));
// Add event handlers to element and child.
ElementEventHandlers element_handlers(element.get());
ElementEventHandlers child_handlers(child_ptr);
// Events on grand_child don't bubble up the parent chain.
grand_child_ptr->OnHoverEnter(gfx::PointF());
grand_child_ptr->OnMove(gfx::PointF());
grand_child_ptr->OnHoverLeave();
grand_child_ptr->OnButtonDown(gfx::PointF());
grand_child_ptr->OnButtonUp(gfx::PointF());
child_handlers.ExpectCalled(false);
element_handlers.ExpectCalled(false);
// Events on grand_child bubble up the parent chain.
grand_child_ptr->set_bubble_events(true);
grand_child_ptr->OnHoverEnter(gfx::PointF());
grand_child_ptr->OnMove(gfx::PointF());
grand_child_ptr->OnHoverLeave();
grand_child_ptr->OnButtonDown(gfx::PointF());
grand_child_ptr->OnButtonUp(gfx::PointF());
child_handlers.ExpectCalled(true);
// Events don't bubble to element since it doesn't have the bubble_events bit
// set.
element_handlers.ExpectCalled(false);
}
} // namespace vr