1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/view_targeter.h"
7 #include "ui/events/event_targeter.h"
8 #include "ui/events/event_utils.h"
9 #include "ui/gfx/path.h"
10 #include "ui/views/masked_view_targeter.h"
11 #include "ui/views/test/views_test_base.h"
12 #include "ui/views/widget/root_view.h"
16 // A class used to define a triangular-shaped hit test mask on a View.
17 class TestMaskedViewTargeter
: public MaskedViewTargeter
{
19 explicit TestMaskedViewTargeter(View
* masked_view
)
20 : MaskedViewTargeter(masked_view
) {}
21 virtual ~TestMaskedViewTargeter() {}
24 virtual bool GetHitTestMask(const View
* view
,
25 gfx::Path
* mask
) const OVERRIDE
{
26 SkScalar w
= SkIntToScalar(view
->width());
27 SkScalar h
= SkIntToScalar(view
->height());
29 // Create a triangular mask within the bounds of |view|.
30 mask
->moveTo(w
/ 2, 0);
38 DISALLOW_COPY_AND_ASSIGN(TestMaskedViewTargeter
);
43 typedef ViewsTestBase ViewTargeterTest
;
45 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
46 // and ViewTargeter::FindNextBestTarget() are implemented correctly
48 TEST_F(ViewTargeterTest
, ViewTargeterForKeyEvents
) {
50 Widget::InitParams init_params
=
51 CreateParams(Widget::InitParams::TYPE_POPUP
);
52 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
53 widget
.Init(init_params
);
55 View
* content
= new View
;
56 View
* child
= new View
;
57 View
* grandchild
= new View
;
59 widget
.SetContentsView(content
);
60 content
->AddChildView(child
);
61 child
->AddChildView(grandchild
);
63 grandchild
->SetFocusable(true);
64 grandchild
->RequestFocus();
66 ui::EventTargeter
* targeter
= new ViewTargeter();
67 internal::RootView
* root_view
=
68 static_cast<internal::RootView
*>(widget
.GetRootView());
69 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
71 ui::KeyEvent
key_event(ui::ET_KEY_PRESSED
, ui::VKEY_A
, 0, true);
73 // The focused view should be the initial target of the event.
74 ui::EventTarget
* current_target
= targeter
->FindTargetForEvent(root_view
,
76 EXPECT_EQ(grandchild
, static_cast<View
*>(current_target
));
78 // Verify that FindNextBestTarget() will return the parent view of the
79 // argument (and NULL if the argument has no parent view).
80 current_target
= targeter
->FindNextBestTarget(grandchild
, &key_event
);
81 EXPECT_EQ(child
, static_cast<View
*>(current_target
));
82 current_target
= targeter
->FindNextBestTarget(child
, &key_event
);
83 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
84 current_target
= targeter
->FindNextBestTarget(content
, &key_event
);
85 EXPECT_EQ(widget
.GetRootView(), static_cast<View
*>(current_target
));
86 current_target
= targeter
->FindNextBestTarget(widget
.GetRootView(),
88 EXPECT_EQ(NULL
, static_cast<View
*>(current_target
));
91 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
92 // and ViewTargeter::FindNextBestTarget() are implemented correctly
94 TEST_F(ViewTargeterTest
, ViewTargeterForScrollEvents
) {
96 Widget::InitParams init_params
=
97 CreateParams(Widget::InitParams::TYPE_POPUP
);
98 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
99 init_params
.bounds
= gfx::Rect(0, 0, 200, 200);
100 widget
.Init(init_params
);
102 // The coordinates used for SetBounds() are in the parent coordinate space.
103 View
* content
= new View
;
104 content
->SetBounds(0, 0, 100, 100);
105 View
* child
= new View
;
106 child
->SetBounds(50, 50, 20, 20);
107 View
* grandchild
= new View
;
108 grandchild
->SetBounds(0, 0, 5, 5);
110 widget
.SetContentsView(content
);
111 content
->AddChildView(child
);
112 child
->AddChildView(grandchild
);
114 ui::EventTargeter
* targeter
= new ViewTargeter();
115 internal::RootView
* root_view
=
116 static_cast<internal::RootView
*>(widget
.GetRootView());
117 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
119 // The event falls within the bounds of |child| and |content| but not
120 // |grandchild|, so |child| should be the initial target for the event.
121 ui::ScrollEvent
scroll(ui::ET_SCROLL
,
123 ui::EventTimeForNow(),
128 ui::EventTarget
* current_target
= targeter
->FindTargetForEvent(root_view
,
130 EXPECT_EQ(child
, static_cast<View
*>(current_target
));
132 // Verify that FindNextBestTarget() will return the parent view of the
133 // argument (and NULL if the argument has no parent view).
134 current_target
= targeter
->FindNextBestTarget(child
, &scroll
);
135 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
136 current_target
= targeter
->FindNextBestTarget(content
, &scroll
);
137 EXPECT_EQ(widget
.GetRootView(), static_cast<View
*>(current_target
));
138 current_target
= targeter
->FindNextBestTarget(widget
.GetRootView(),
140 EXPECT_EQ(NULL
, static_cast<View
*>(current_target
));
142 // The event falls outside of the original specified bounds of |content|,
143 // |child|, and |grandchild|. But since |content| is the contents view,
144 // and contents views are resized to fill the entire area of the root
145 // view, the event's initial target should still be |content|.
146 scroll
= ui::ScrollEvent(ui::ET_SCROLL
,
147 gfx::Point(150, 150),
148 ui::EventTimeForNow(),
153 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
154 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
157 // Tests the basic functionality of the method
158 // ViewTargeter::SubtreeShouldBeExploredForEvent().
159 TEST_F(ViewTargeterTest
, SubtreeShouldBeExploredForEvent
) {
161 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
162 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
163 params
.bounds
= gfx::Rect(0, 0, 650, 650);
166 ui::EventTargeter
* targeter
= new ViewTargeter();
167 internal::RootView
* root_view
=
168 static_cast<internal::RootView
*>(widget
.GetRootView());
169 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
171 // The coordinates used for SetBounds() are in the parent coordinate space.
173 v1
.SetBounds(0, 0, 300, 300);
174 v2
.SetBounds(100, 100, 100, 100);
175 v3
.SetBounds(0, 0, 10, 10);
176 v3
.SetVisible(false);
177 root_view
->AddChildView(&v1
);
178 v1
.AddChildView(&v2
);
179 v2
.AddChildView(&v3
);
181 // Note that the coordinates used below are in |v1|'s coordinate space,
182 // and that SubtreeShouldBeExploredForEvent() expects the event location
183 // to be in the coordinate space of the target's parent. |v1| and
184 // its parent share a common coordinate space.
186 // Event located within |v1| only.
187 gfx::Point
point(10, 10);
188 ui::MouseEvent
event(ui::ET_MOUSE_PRESSED
, point
, point
,
189 ui::EF_LEFT_MOUSE_BUTTON
, ui::EF_LEFT_MOUSE_BUTTON
);
190 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v1
, event
));
191 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v2
, event
));
192 v1
.ConvertEventToTarget(&v2
, &event
);
193 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v3
, event
));
195 // Event located within |v1| and |v2| only.
196 event
.set_location(gfx::Point(150, 150));
197 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v1
, event
));
198 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v2
, event
));
199 v1
.ConvertEventToTarget(&v2
, &event
);
200 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v3
, event
));
202 // Event located within |v1|, |v2|, and |v3|. Note that |v3| is not
203 // visible, so it cannot handle the event.
204 event
.set_location(gfx::Point(105, 105));
205 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v1
, event
));
206 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v2
, event
));
207 v1
.ConvertEventToTarget(&v2
, &event
);
208 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v3
, event
));
210 // Event located outside the bounds of all views.
211 event
.set_location(gfx::Point(400, 400));
212 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v1
, event
));
213 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v2
, event
));
214 v1
.ConvertEventToTarget(&v2
, &event
);
215 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v3
, event
));
217 // TODO(tdanderson): Move the hit-testing unit tests out of view_unittest
218 // and into here. See crbug.com/355425.
221 // Tests that FindTargetForEvent() returns the correct target when some
222 // views in the view tree have a MaskedViewTargeter installed, i.e.,
223 // they have a custom-shaped hit test mask.
224 TEST_F(ViewTargeterTest
, MaskedViewTargeter
) {
226 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
227 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
228 params
.bounds
= gfx::Rect(0, 0, 650, 650);
231 ui::EventTargeter
* targeter
= new ViewTargeter();
232 internal::RootView
* root_view
=
233 static_cast<internal::RootView
*>(widget
.GetRootView());
234 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
236 // The coordinates used for SetBounds() are in the parent coordinate space.
237 View masked_view
, unmasked_view
, masked_child
;
238 masked_view
.SetBounds(0, 0, 200, 200);
239 unmasked_view
.SetBounds(300, 0, 300, 300);
240 masked_child
.SetBounds(0, 0, 100, 100);
241 root_view
->AddChildView(&masked_view
);
242 root_view
->AddChildView(&unmasked_view
);
243 unmasked_view
.AddChildView(&masked_child
);
245 // Install event targeters of type TestMaskedViewTargeter on the two masked
246 // views to define their hit test masks.
247 ui::EventTargeter
* masked_targeter
= new TestMaskedViewTargeter(&masked_view
);
248 masked_view
.SetEventTargeter(make_scoped_ptr(masked_targeter
));
249 masked_targeter
= new TestMaskedViewTargeter(&masked_child
);
250 masked_child
.SetEventTargeter(make_scoped_ptr(masked_targeter
));
252 // Note that the coordinates used below are in the coordinate space of
255 // Event located within the hit test mask of |masked_view|.
256 ui::ScrollEvent
scroll(ui::ET_SCROLL
,
257 gfx::Point(100, 190),
258 ui::EventTimeForNow(),
265 ui::EventTarget
* current_target
=
266 targeter
->FindTargetForEvent(root_view
, &scroll
);
267 EXPECT_EQ(&masked_view
, static_cast<View
*>(current_target
));
269 // Event located outside the hit test mask of |masked_view|.
270 scroll
.set_location(gfx::Point(10, 10));
271 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
272 EXPECT_EQ(root_view
, static_cast<View
*>(current_target
));
274 // Event located within the hit test mask of |masked_child|.
275 scroll
.set_location(gfx::Point(350, 3));
276 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
277 EXPECT_EQ(&masked_child
, static_cast<View
*>(current_target
));
279 // Event located within the hit test mask of |masked_child|.
280 scroll
.set_location(gfx::Point(300, 12));
281 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
282 EXPECT_EQ(&unmasked_view
, static_cast<View
*>(current_target
));
284 // TODO(tdanderson): We should also test that targeting of masked views
285 // works correctly with gestures. See crbug.com/375822.