Skip to content

Commit

Permalink
Introduce ViewTargeterDelegate and MaskedTargeterDelegate
Browse files Browse the repository at this point in the history
These two classes define the default hit-testing behaviour
for views with and without hit-test masks. Views wishing
to define their own hit-testing behaviour will extend
one of these classes.

BUG=388838
TEST=ViewTargeterTest.DoesIntersectRect

Review URL: https://codereview.chromium.org/336953005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@280713 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
tdanderson@chromium.org committed Jul 1, 2014
1 parent f0f8e08 commit 8a6d7ef
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 20 deletions.
9 changes: 5 additions & 4 deletions chrome/browser/ui/views/tabs/tab.cc
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,13 @@ class Tab::TabCloseButton : public views::ImageButton {
return true;
}

virtual void GetHitTestMask(HitTestSource source,
gfx::Path* path) const OVERRIDE {
virtual void GetHitTestMaskDeprecated(HitTestSource source,
gfx::Path* path) const OVERRIDE {
// Use the button's contents bounds (which does not include padding)
// and the hit test mask of our parent |tab_| to determine if the
// button is hidden behind another tab.
gfx::Path tab_mask;
tab_->GetHitTestMask(source, &tab_mask);
tab_->GetHitTestMaskDeprecated(source, &tab_mask);

gfx::Rect button_bounds(GetContentsBounds());
button_bounds.set_x(GetMirroredXForRect(button_bounds));
Expand Down Expand Up @@ -807,7 +807,8 @@ bool Tab::HasHitTestMask() const {
return true;
}

void Tab::GetHitTestMask(HitTestSource source, gfx::Path* path) const {
void Tab::GetHitTestMaskDeprecated(HitTestSource source,
gfx::Path* path) const {
// When the window is maximized we don't want to shave off the edges or top
// shadow of the tab, such that the user can click anywhere along the top
// edge of the screen to select a tab. Ditto for immersive fullscreen.
Expand Down
4 changes: 2 additions & 2 deletions chrome/browser/ui/views/tabs/tab.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ class Tab : public gfx::AnimationDelegate,
virtual void OnThemeChanged() OVERRIDE;
virtual const char* GetClassName() const OVERRIDE;
virtual bool HasHitTestMask() const OVERRIDE;
virtual void GetHitTestMask(HitTestSource source,
gfx::Path* path) const OVERRIDE;
virtual void GetHitTestMaskDeprecated(HitTestSource source,
gfx::Path* path) const OVERRIDE;
virtual bool GetTooltipText(const gfx::Point& p,
base::string16* tooltip) const OVERRIDE;
virtual bool GetTooltipTextOrigin(const gfx::Point& p,
Expand Down
7 changes: 4 additions & 3 deletions chrome/browser/ui/views/tabs/tab_strip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ class NewTabButton : public views::ImageButton {
protected:
// Overridden from views::View:
virtual bool HasHitTestMask() const OVERRIDE;
virtual void GetHitTestMask(HitTestSource source,
gfx::Path* path) const OVERRIDE;
virtual void GetHitTestMaskDeprecated(HitTestSource source,
gfx::Path* path) const OVERRIDE;
#if defined(OS_WIN)
virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
#endif
Expand Down Expand Up @@ -295,7 +295,8 @@ bool NewTabButton::HasHitTestMask() const {
return !tab_strip_->SizeTabButtonToTopOfTabStrip();
}

void NewTabButton::GetHitTestMask(HitTestSource source, gfx::Path* path) const {
void NewTabButton::GetHitTestMaskDeprecated(HitTestSource source,
gfx::Path* path) const {
DCHECK(path);

SkScalar w = SkIntToScalar(width());
Expand Down
2 changes: 1 addition & 1 deletion chrome/browser/ui/views/tabs/tab_strip_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class TabStripTest : public testing::Test {
// coordinate space.
gfx::Rect GetTabHitTestMask(Tab* tab) {
gfx::Path mask;
tab->GetHitTestMask(views::View::HIT_TEST_SOURCE_TOUCH, &mask);
tab->GetHitTestMaskDeprecated(views::View::HIT_TEST_SOURCE_TOUCH, &mask);
return gfx::ToEnclosingRect((gfx::SkRectToRectF(mask.getBounds())));
}

Expand Down
8 changes: 4 additions & 4 deletions ui/app_list/views/speech_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ class MicButton : public views::ImageButton {
private:
// Overridden from views::View:
virtual bool HasHitTestMask() const OVERRIDE;
virtual void GetHitTestMask(views::View::HitTestSource source,
gfx::Path* mask) const OVERRIDE;
virtual void GetHitTestMaskDeprecated(views::View::HitTestSource source,
gfx::Path* mask) const OVERRIDE;

DISALLOW_COPY_AND_ASSIGN(MicButton);
};
Expand All @@ -93,8 +93,8 @@ bool MicButton::HasHitTestMask() const {
return true;
}

void MicButton::GetHitTestMask(views::View::HitTestSource source,
gfx::Path* mask) const {
void MicButton::GetHitTestMaskDeprecated(views::View::HitTestSource source,
gfx::Path* mask) const {
DCHECK(mask);

// The mic button icon is a circle. |source| doesn't matter.
Expand Down
34 changes: 34 additions & 0 deletions ui/views/masked_targeter_delegate.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2014 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 "ui/views/masked_targeter_delegate.h"

#include "ui/gfx/path.h"
#include "ui/gfx/skia_util.h"
#include "ui/views/view.h"

namespace views {

bool MaskedTargeterDelegate::DoesIntersectRect(const View* target,
const gfx::Rect& rect) const {
// Early return if |rect| does not even intersect the rectangular bounds
// of |target|.
if (!ViewTargeterDelegate::DoesIntersectRect(target, rect))
return false;

// Early return if |mask| is not a valid hit test mask.
gfx::Path mask;
if (!GetHitTestMask(&mask))
return false;

// Return whether or not |rect| intersects the custom hit test mask
// of |target|.
SkRegion clip_region;
clip_region.setRect(0, 0, target->width(), target->height());
SkRegion mask_region;
return mask_region.setPath(mask, clip_region) &&
mask_region.intersects(RectToSkIRect(rect));
}

} // namespace views
43 changes: 43 additions & 0 deletions ui/views/masked_targeter_delegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2014 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.

#ifndef UI_VIEWS_MASKED_TARGETER_DELEGATE_H_
#define UI_VIEWS_MASKED_TARGETER_DELEGATE_H_

#include "ui/base/ui_base_types.h"
#include "ui/views/view_targeter_delegate.h"
#include "ui/views/views_export.h"

namespace gfx {
class Path;
class Rect;
}

namespace views {
class View;

// Defines the default behaviour for hit-testing a rectangular region against
// the bounds of a View having a custom-shaped hit test mask. Views define
// such a mask by extending this class.
class VIEWS_EXPORT MaskedTargeterDelegate : public ViewTargeterDelegate {
public:
MaskedTargeterDelegate() {}
virtual ~MaskedTargeterDelegate() {}

// Sets the hit-test mask for the view which implements this interface,
// in that view's local coordinate space. Returns whether a valid mask
// has been set in |mask|.
virtual bool GetHitTestMask(gfx::Path* mask) const = 0;

// ViewTargeterDelegate:
virtual bool DoesIntersectRect(const View* target,
const gfx::Rect& rect) const OVERRIDE;

private:
DISALLOW_COPY_AND_ASSIGN(MaskedTargeterDelegate);
};

} // namespace views

#endif // UI_VIEWS_MASKED_TARGETER_DELEGATE_H_
5 changes: 3 additions & 2 deletions ui/views/view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ bool View::HitTestRect(const gfx::Rect& rect) const {
HitTestSource source = HIT_TEST_SOURCE_MOUSE;
if (!views::UsePointBasedTargeting(rect))
source = HIT_TEST_SOURCE_TOUCH;
GetHitTestMask(source, &mask);
GetHitTestMaskDeprecated(source, &mask);
SkRegion clip_region;
clip_region.setRect(0, 0, width(), height());
SkRegion mask_region;
Expand Down Expand Up @@ -1585,7 +1585,8 @@ bool View::HasHitTestMask() const {
return false;
}

void View::GetHitTestMask(HitTestSource source, gfx::Path* mask) const {
void View::GetHitTestMaskDeprecated(HitTestSource source,
gfx::Path* mask) const {
DCHECK(mask);
}

Expand Down
6 changes: 5 additions & 1 deletion ui/views/view.h
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,11 @@ class VIEWS_EXPORT View : public ui::LayerDelegate,

// Called by HitTestRect() to retrieve a mask for hit-testing against.
// Subclasses override to provide custom shaped hit test regions.
virtual void GetHitTestMask(HitTestSource source, gfx::Path* mask) const;
// TODO(tdanderson): Remove this method once Tab, TabCloseButton,
// NewTabButton, and MicButton all implement
// MaskedViewTargeter.
virtual void GetHitTestMaskDeprecated(HitTestSource source,
gfx::Path* mask) const;

virtual DragInfo* GetDragInfo();

Expand Down
15 changes: 15 additions & 0 deletions ui/views/view_targeter_delegate.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2014 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 "ui/views/view.h"
#include "ui/views/view_targeter_delegate.h"

namespace views {

bool ViewTargeterDelegate::DoesIntersectRect(const View* target,
const gfx::Rect& rect) const {
return target->GetLocalBounds().Intersects(rect);
}

} // namespace views
38 changes: 38 additions & 0 deletions ui/views/view_targeter_delegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2014 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.

#ifndef UI_VIEWS_VIEW_TARGETER_DELEGATE_H_
#define UI_VIEWS_VIEW_TARGETER_DELEGATE_H_

#include "base/macros.h"
#include "ui/views/views_export.h"

namespace gfx {
class Rect;
}

namespace views {
class View;

// Defines the default behaviour for hit-testing a rectangular region against
// the bounds of a View. Subclasses of View wishing to define custom
// hit-testing behaviour can extend this class.
class VIEWS_EXPORT ViewTargeterDelegate {
public:
ViewTargeterDelegate() {}
virtual ~ViewTargeterDelegate() {}

// Returns true if the bounds of |target| intersects |rect|, where |rect|
// is in the local coodinate space of |target|. Overrides of this method by
// a View subclass should enforce DCHECK_EQ(this, target).
virtual bool DoesIntersectRect(const View* target,
const gfx::Rect& rect) const;

private:
DISALLOW_COPY_AND_ASSIGN(ViewTargeterDelegate);
};

} // namespace views

#endif // UI_VIEWS_VIEW_TARGETER_DELEGATE_H_
87 changes: 86 additions & 1 deletion ui/views/view_targeter_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@

#include "ui/events/event_targeter.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/path.h"
#include "ui/views/masked_targeter_delegate.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view_targeter.h"
#include "ui/views/view_targeter_delegate.h"
#include "ui/views/widget/root_view.h"

namespace views {

// A derived class of View used for testing purposes.
class TestingView : public View {
class TestingView : public View, public ViewTargeterDelegate {
public:
TestingView() : can_process_events_within_subtree_(true) {}
virtual ~TestingView() {}
Expand All @@ -25,6 +28,11 @@ class TestingView : public View {
can_process_events_within_subtree_ = can_process;
}

// A call-through function to ViewTargeterDelegate::DoesIntersectRect().
bool TestDoesIntersectRect(const View* target, const gfx::Rect& rect) const {
return DoesIntersectRect(target, rect);
}

// View:
virtual bool CanProcessEventsWithinSubtree() const OVERRIDE {
return can_process_events_within_subtree_;
Expand All @@ -37,6 +45,35 @@ class TestingView : public View {
DISALLOW_COPY_AND_ASSIGN(TestingView);
};

// A derived class of View having a triangular-shaped hit test mask.
class TestMaskedView : public View, public MaskedTargeterDelegate {
public:
TestMaskedView() {}
virtual ~TestMaskedView() {}

// A call-through function to MaskedTargeterDelegate::DoesIntersectRect().
bool TestDoesIntersectRect(const View* target, const gfx::Rect& rect) const {
return DoesIntersectRect(target, rect);
}

private:
// MaskedTargeterDelegate:
virtual bool GetHitTestMask(gfx::Path* mask) const OVERRIDE {
DCHECK(mask);
SkScalar w = SkIntToScalar(width());
SkScalar h = SkIntToScalar(height());

// Create a triangular mask within the bounds of this View.
mask->moveTo(w / 2, 0);
mask->lineTo(w, h);
mask->lineTo(0, h);
mask->close();
return true;
}

DISALLOW_COPY_AND_ASSIGN(TestMaskedView);
};

namespace test {

typedef ViewsTestBase ViewTargeterTest;
Expand Down Expand Up @@ -292,5 +329,53 @@ TEST_F(ViewTargeterTest, CanProcessEventsWithinSubtree) {
// with gestures. See crbug.com/375822.
}

// Tests that the functions ViewTargeterDelegate::DoesIntersectRect()
// and MaskedTargeterDelegate::DoesIntersectRect() work as intended when
// called on views which are derived from ViewTargeterDelegate.
TEST_F(ViewTargeterTest, DoesIntersectRect) {
Widget widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(0, 0, 650, 650);
widget.Init(params);

internal::RootView* root_view =
static_cast<internal::RootView*>(widget.GetRootView());

// The coordinates used for SetBounds() are in the parent coordinate space.
TestingView v2;
TestMaskedView v1, v3;
v1.SetBounds(0, 0, 200, 200);
v2.SetBounds(300, 0, 300, 300);
v3.SetBounds(0, 0, 100, 100);
root_view->AddChildView(&v1);
root_view->AddChildView(&v2);
v2.AddChildView(&v3);

// The coordinates used below are in the local coordinate space of the
// view that is passed in as an argument.

// Hit tests against |v1|, which has a hit test mask.
EXPECT_TRUE(v1.TestDoesIntersectRect(&v1, gfx::Rect(0, 0, 200, 200)));
EXPECT_TRUE(v1.TestDoesIntersectRect(&v1, gfx::Rect(-10, -10, 110, 12)));
EXPECT_TRUE(v1.TestDoesIntersectRect(&v1, gfx::Rect(112, 142, 1, 1)));
EXPECT_FALSE(v1.TestDoesIntersectRect(&v1, gfx::Rect(0, 0, 20, 20)));
EXPECT_FALSE(v1.TestDoesIntersectRect(&v1, gfx::Rect(-10, -10, 90, 12)));
EXPECT_FALSE(v1.TestDoesIntersectRect(&v1, gfx::Rect(150, 49, 1, 1)));

// Hit tests against |v2|, which does not have a hit test mask.
EXPECT_TRUE(v2.TestDoesIntersectRect(&v2, gfx::Rect(0, 0, 200, 200)));
EXPECT_TRUE(v2.TestDoesIntersectRect(&v2, gfx::Rect(-10, 250, 60, 60)));
EXPECT_TRUE(v2.TestDoesIntersectRect(&v2, gfx::Rect(250, 250, 1, 1)));
EXPECT_FALSE(v2.TestDoesIntersectRect(&v2, gfx::Rect(-10, 250, 7, 7)));
EXPECT_FALSE(v2.TestDoesIntersectRect(&v2, gfx::Rect(-1, -1, 1, 1)));

// Hit tests against |v3|, which has a hit test mask and is a child of |v2|.
EXPECT_TRUE(v3.TestDoesIntersectRect(&v3, gfx::Rect(0, 0, 50, 50)));
EXPECT_TRUE(v3.TestDoesIntersectRect(&v3, gfx::Rect(90, 90, 1, 1)));
EXPECT_FALSE(v3.TestDoesIntersectRect(&v3, gfx::Rect(10, 125, 50, 50)));
EXPECT_FALSE(v3.TestDoesIntersectRect(&v3, gfx::Rect(110, 110, 1, 1)));
}

} // namespace test
} // namespace views
4 changes: 2 additions & 2 deletions ui/views/view_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -797,8 +797,8 @@ class HitTestView : public View {
virtual bool HasHitTestMask() const OVERRIDE {
return has_hittest_mask_;
}
virtual void GetHitTestMask(HitTestSource source,
gfx::Path* mask) const OVERRIDE {
virtual void GetHitTestMaskDeprecated(HitTestSource source,
gfx::Path* mask) const OVERRIDE {
DCHECK(has_hittest_mask_);
DCHECK(mask);

Expand Down
Loading

0 comments on commit 8a6d7ef

Please sign in to comment.