summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvarunjain@chromium.org <varunjain@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-26 22:14:37 +0000
committervarunjain@chromium.org <varunjain@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-26 22:14:37 +0000
commit62531e1316efc119fd1342b367b8f6e7b6007c7a (patch)
tree0ff5d2ffcd7800172e241cfce7067716552dedfb
parenta1ea5a9a54154af76ff112ccc363018aa8d985f7 (diff)
downloadchromium_src-62531e1316efc119fd1342b367b8f6e7b6007c7a.zip
chromium_src-62531e1316efc119fd1342b367b8f6e7b6007c7a.tar.gz
chromium_src-62531e1316efc119fd1342b367b8f6e7b6007c7a.tar.bz2
GestureRecognizer: Long press should be cancelled on prevented-defaulted moves.
BUG=133375 TEST=added new test Review URL: https://chromiumcodereview.appspot.com/10626009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@144295 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--ash/wm/system_gesture_event_filter.cc166
-rw-r--r--ash/wm/system_gesture_event_filter.h50
-rw-r--r--ash/wm/system_gesture_event_filter_unittest.cc81
-rw-r--r--ui/aura/gestures/gesture_recognizer_unittest.cc40
-rw-r--r--ui/base/gestures/gesture_point.cc17
-rw-r--r--ui/base/gestures/gesture_sequence.cc14
-rw-r--r--ui/base/gestures/gesture_sequence.h2
-rw-r--r--ui/base/gestures/gesture_util.cc23
-rw-r--r--ui/base/gestures/gesture_util.h27
-rw-r--r--ui/ui.gyp2
10 files changed, 322 insertions, 100 deletions
diff --git a/ash/wm/system_gesture_event_filter.cc b/ash/wm/system_gesture_event_filter.cc
index 4af722d..2be1782 100644
--- a/ash/wm/system_gesture_event_filter.cc
+++ b/ash/wm/system_gesture_event_filter.cc
@@ -18,17 +18,14 @@
#include "ash/wm/window_util.h"
#include "ash/wm/workspace/phantom_window_controller.h"
#include "ash/wm/workspace/snap_sizer.h"
-#include "base/timer.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/aura/event.h"
#include "ui/aura/root_window.h"
-#include "ui/base/animation/animation.h"
-#include "ui/base/animation/animation_delegate.h"
-#include "ui/base/animation/linear_animation.h"
#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/base/gestures/gesture_util.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/point.h"
@@ -87,13 +84,19 @@ Widget* CreateAffordanceWidget() {
return widget;
}
+} // namespace
+
+namespace ash {
+namespace internal {
+
// View of the LongPressAffordanceAnimation. Draws the actual contents and
// updates as the animation proceeds. It also maintains the views::Widget that
// the animation is shown in.
// Currently the affordance is to simply show an empty circle and fill it up as
// the animation proceeds.
// TODO(varunjain): Change the look of this affordance when we get official UX.
-class LongPressAffordanceView : public views::View {
+class LongPressAffordanceAnimation::LongPressAffordanceView
+ : public views::View {
public:
explicit LongPressAffordanceView(const gfx::Point& event_location)
: views::View(),
@@ -112,7 +115,6 @@ class LongPressAffordanceView : public views::View {
}
virtual ~LongPressAffordanceView() {
- widget_->Hide();
}
void UpdateWithAnimation(ui::Animation* animation) {
@@ -148,100 +150,94 @@ class LongPressAffordanceView : public views::View {
canvas->DrawPath(path, paint);
}
- scoped_ptr<Widget> widget_;
+ scoped_ptr<views::Widget> widget_;
int current_angle_;
DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView);
};
-} // namespace
-
-namespace ash {
-namespace internal {
+LongPressAffordanceAnimation::LongPressAffordanceAnimation()
+ : ui::LinearAnimation(kAffordanceFrameRateHz, this),
+ view_(NULL),
+ tap_down_target_(NULL) {
+ int duration =
+ ui::GestureConfiguration::long_press_time_in_seconds() * 1000 -
+ ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000;
+ SetDuration(duration);
+}
-// LongPressAffordanceAnimation displays an animated affordance that is shown
-// on a TAP_DOWN gesture. The animation completes on a LONG_PRESS gesture, or is
-// canceled and hidden if any other event is received before that.
-class SystemGestureEventFilter::LongPressAffordanceAnimation
- : public ui::AnimationDelegate,
- public ui::LinearAnimation {
- public:
- LongPressAffordanceAnimation()
- : ui::LinearAnimation(kAffordanceFrameRateHz, this),
- view_(NULL) {
- int duration =
- ui::GestureConfiguration::long_press_time_in_seconds() * 1000 -
- ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000;
- SetDuration(duration);
- }
+LongPressAffordanceAnimation::~LongPressAffordanceAnimation() {}
- void ProcessEvent(aura::Window* target, aura::LocatedEvent* event) {
- gfx::Point event_location;
- int64 timer_start_time_ms =
- ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000;
- switch (event->type()) {
- case ui::ET_GESTURE_TAP_DOWN:
- // Start animation.
- tap_down_location_ = event->root_location();
- timer_.Start(FROM_HERE,
- base::TimeDelta::FromMilliseconds(timer_start_time_ms),
- this,
- &LongPressAffordanceAnimation::StartAnimation);
- break;
- case ui::ET_TOUCH_MOVED:
- // We do not want to stop the animation on every TOUCH_MOVED. Instead,
- // we will rely on SCROLL_BEGIN to break the animation when the user
- // moves their finger.
- break;
- case ui::ET_GESTURE_LONG_PRESS:
- if (is_animating())
- End();
- // fall through to default to reset the view.
- default:
- // On all other touch and gesture events, we hide the animation.
+void LongPressAffordanceAnimation::ProcessEvent(aura::Window* target,
+ aura::LocatedEvent* event) {
+ // Once we have a target, we are only interested in events on that target.
+ if (tap_down_target_ && tap_down_target_ != target)
+ return;
+ int64 timer_start_time_ms =
+ ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000;
+ switch (event->type()) {
+ case ui::ET_GESTURE_TAP_DOWN:
+ // Start animation.
+ tap_down_location_ = event->root_location();
+ tap_down_target_ = target;
+ timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(timer_start_time_ms),
+ this,
+ &LongPressAffordanceAnimation::StartAnimation);
+ break;
+ case ui::ET_TOUCH_MOVED:
+ // If animation is running, We want it to be robust to small finger
+ // movements. So we stop the animation only when the finger moves a
+ // certain distance.
+ if (is_animating() && !ui::gestures::IsInsideManhattanSquare(
+ event->root_location(), tap_down_location_))
StopAnimation();
- break;
- }
- }
-
- private:
- void StartAnimation() {
- view_.reset(new LongPressAffordanceView(tap_down_location_));
- Start();
- }
-
- void StopAnimation() {
- if (timer_.IsRunning())
- timer_.Stop();
- if (is_animating())
- Stop();
- view_.reset();
+ break;
+ case ui::ET_GESTURE_LONG_PRESS:
+ if (is_animating())
+ End();
+ // fall through to default to reset the view and tap down target.
+ default:
+ // On all other touch and gesture events, we hide the animation.
+ StopAnimation();
+ break;
}
+}
- // Overridden from ui::LinearAnimation.
- virtual void AnimateToState(double state) OVERRIDE {
- DCHECK(view_.get());
- view_->UpdateWithAnimation(this);
- }
+void LongPressAffordanceAnimation::StartAnimation() {
+ view_.reset(new LongPressAffordanceView(tap_down_location_));
+ Start();
+}
- // Overridden from ui::AnimationDelegate.
- virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE {
- view_.reset();
- }
+void LongPressAffordanceAnimation::StopAnimation() {
+ if (timer_.IsRunning())
+ timer_.Stop();
+ if (is_animating())
+ Stop();
+ view_.reset();
+ tap_down_target_ = NULL;
+}
- virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE {
- }
+void LongPressAffordanceAnimation::AnimateToState(double state) {
+ DCHECK(view_.get());
+ view_->UpdateWithAnimation(this);
+}
- virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE {
- view_.reset();
- }
+void LongPressAffordanceAnimation::AnimationEnded(
+ const ui::Animation* animation) {
+ view_.reset();
+ tap_down_target_ = NULL;
+}
- scoped_ptr<LongPressAffordanceView> view_;
- gfx::Point tap_down_location_;
- base::OneShotTimer<LongPressAffordanceAnimation> timer_;
+void LongPressAffordanceAnimation::AnimationProgressed(
+ const ui::Animation* animation) {
+}
- DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceAnimation);
-};
+void LongPressAffordanceAnimation::AnimationCanceled(
+ const ui::Animation* animation) {
+ view_.reset();
+ tap_down_target_ = NULL;
+}
class SystemPinchHandler {
public:
diff --git a/ash/wm/system_gesture_event_filter.h b/ash/wm/system_gesture_event_filter.h
index 3c258d7..9389c2f 100644
--- a/ash/wm/system_gesture_event_filter.h
+++ b/ash/wm/system_gesture_event_filter.h
@@ -8,18 +8,28 @@
#include "ash/shell.h"
#include "ash/touch/touch_uma.h"
+#include "base/timer.h"
#include "ui/aura/event_filter.h"
#include "ui/aura/window_observer.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/linear_animation.h"
+#include "ui/gfx/point.h"
#include <map>
namespace aura {
-class MouseEvent;
class KeyEvent;
+class LocatedEvent;
+class MouseEvent;
class Window;
}
namespace ash {
+
+namespace test {
+class SystemGestureEventFilterTest;
+} // namespace test
+
namespace internal {
class SystemPinchHandler;
@@ -39,6 +49,41 @@ enum ScrollOrientation {
SCROLL_ORIENTATION_VERTICAL
};
+// LongPressAffordanceAnimation displays an animated affordance that is shown
+// on a TAP_DOWN gesture. The animation completes on a LONG_PRESS gesture, or is
+// canceled and hidden if any other event is received before that.
+class LongPressAffordanceAnimation : public ui::AnimationDelegate,
+ public ui::LinearAnimation {
+ public:
+ LongPressAffordanceAnimation();
+ virtual ~LongPressAffordanceAnimation();
+
+ // Display or removes long press affordance according to the |event|.
+ void ProcessEvent(aura::Window* target, aura::LocatedEvent* event);
+
+ private:
+ friend class ash::test::SystemGestureEventFilterTest;
+
+ void StartAnimation();
+ void StopAnimation();
+
+ // Overridden from ui::LinearAnimation.
+ virtual void AnimateToState(double state) OVERRIDE;
+
+ // Overridden from ui::AnimationDelegate.
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE;
+
+ class LongPressAffordanceView;
+ scoped_ptr<LongPressAffordanceView> view_;
+ gfx::Point tap_down_location_;
+ aura::Window* tap_down_target_;
+ base::OneShotTimer<LongPressAffordanceAnimation> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceAnimation);
+};
+
// An event filter which handles system level gesture events.
class SystemGestureEventFilter : public aura::EventFilter,
public aura::WindowObserver {
@@ -63,6 +108,8 @@ class SystemGestureEventFilter : public aura::EventFilter,
virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
private:
+ friend class ash::test::SystemGestureEventFilterTest;
+
// Removes system-gesture handlers for a window.
void ClearGestureHandlerForWindow(aura::Window* window);
@@ -96,7 +143,6 @@ class SystemGestureEventFilter : public aura::EventFilter,
// A device swipe gesture is in progress.
bool is_scrubbing_;
- class LongPressAffordanceAnimation;
scoped_ptr<LongPressAffordanceAnimation> long_press_affordance_;
TouchUMA touch_uma_;
diff --git a/ash/wm/system_gesture_event_filter_unittest.cc b/ash/wm/system_gesture_event_filter_unittest.cc
index 2ca46bf..8f0fceb 100644
--- a/ash/wm/system_gesture_event_filter_unittest.cc
+++ b/ash/wm/system_gesture_event_filter_unittest.cc
@@ -4,6 +4,7 @@
#include "ash/wm/system_gesture_event_filter.h"
+#include "base/timer.h"
#include "ash/accelerators/accelerator_controller.h"
#include "ash/launcher/launcher.h"
#include "ash/launcher/launcher_model.h"
@@ -95,7 +96,34 @@ class DummyBrightnessControlDelegate : public BrightnessControlDelegate,
} // namespace
-typedef test::AshTestBase SystemGestureEventFilterTest;
+class SystemGestureEventFilterTest : public AshTestBase {
+ public:
+ SystemGestureEventFilterTest() : AshTestBase() {}
+ virtual ~SystemGestureEventFilterTest() {}
+
+ internal::LongPressAffordanceAnimation* GetLongPressAffordance() {
+ Shell::TestApi shell_test(Shell::GetInstance());
+ return shell_test.system_gesture_event_filter()->
+ long_press_affordance_.get();
+ }
+
+ base::OneShotTimer<internal::LongPressAffordanceAnimation>*
+ GetLongPressAffordanceTimer() {
+ return &GetLongPressAffordance()->timer_;
+ }
+
+ aura::Window* GetLongPressAffordanceTarget() {
+ return GetLongPressAffordance()->tap_down_target_;
+ }
+
+ views::View* GetLongPressAffordanceView() {
+ return reinterpret_cast<views::View*>(
+ GetLongPressAffordance()->view_.get());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemGestureEventFilterTest);
+};
// Ensure that events targeted at the root window are consumed by the
// system event handler.
@@ -341,5 +369,56 @@ TEST_F(SystemGestureEventFilterTest, ApplicationControl) {
}
}
+TEST_F(SystemGestureEventFilterTest, LongPressAffordanceStateOnCaptureLoss) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+
+ aura::test::TestWindowDelegate delegate;
+ scoped_ptr<aura::Window> window0(
+ aura::test::CreateTestWindowWithDelegate(
+ &delegate, 9, gfx::Rect(0, 0, 100, 100), root_window));
+ scoped_ptr<aura::Window> window1(
+ aura::test::CreateTestWindowWithDelegate(
+ &delegate, 10, gfx::Rect(0, 0, 100, 50), window0.get()));
+ scoped_ptr<aura::Window> window2(
+ aura::test::CreateTestWindowWithDelegate(
+ &delegate, 11, gfx::Rect(0, 50, 100, 50), window0.get()));
+
+ const int kTouchId = 5;
+
+ // Capture first window.
+ window1->SetCapture();
+ EXPECT_TRUE(window1->HasCapture());
+
+ // Send touch event to first window.
+ aura::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), kTouchId,
+ base::Time::NowFromSystemTime() - base::Time());
+ root_window->DispatchTouchEvent(&press);
+ EXPECT_TRUE(window1->HasCapture());
+
+ base::OneShotTimer<internal::LongPressAffordanceAnimation>* timer =
+ GetLongPressAffordanceTimer();
+ EXPECT_TRUE(timer->IsRunning());
+ EXPECT_EQ(window1.get(), GetLongPressAffordanceTarget());
+
+ // Force timeout so that the affordance animation can start.
+ timer->user_task().Run();
+ timer->Stop();
+ EXPECT_TRUE(GetLongPressAffordance()->is_animating());
+
+ // Change capture.
+ window2->SetCapture();
+ EXPECT_TRUE(window2->HasCapture());
+
+ EXPECT_TRUE(GetLongPressAffordance()->is_animating());
+ EXPECT_EQ(window1.get(), GetLongPressAffordanceTarget());
+
+ // Animate to completion.
+ GetLongPressAffordance()->End();
+
+ // Check if state has reset.
+ EXPECT_EQ(NULL, GetLongPressAffordanceTarget());
+ EXPECT_EQ(NULL, GetLongPressAffordanceView());
+}
+
} // namespace test
} // namespace ash
diff --git a/ui/aura/gestures/gesture_recognizer_unittest.cc b/ui/aura/gestures/gesture_recognizer_unittest.cc
index 8af48e5..e1344c3 100644
--- a/ui/aura/gestures/gesture_recognizer_unittest.cc
+++ b/ui/aura/gestures/gesture_recognizer_unittest.cc
@@ -323,6 +323,10 @@ class TimerTestGestureSequence : public ui::GestureSequence {
long_press_timer())->ForceTimeout();
}
+ bool IsTimerRunning() {
+ return long_press_timer()->IsRunning();
+ }
+
base::OneShotTimer<ui::GestureSequence>* CreateTimer() {
return new TestOneShotGestureSequenceTimer();
}
@@ -2149,5 +2153,41 @@ TEST_F(GestureRecognizerTest, FlushAllOnHide) {
root_window()->gesture_recognizer()->GetTouchLockedTarget(&press2));
}
+TEST_F(GestureRecognizerTest, LongPressTimerStopsOnPreventDefaultedTouchMoves) {
+ scoped_ptr<QueueTouchEventDelegate> delegate(
+ new QueueTouchEventDelegate(root_window()));
+ const int kTouchId = 2;
+ gfx::Rect bounds(100, 200, 100, 100);
+ scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(
+ delegate.get(), -1234, bounds, NULL));
+ delegate->set_window(window.get());
+
+ TimerTestGestureRecognizer* gesture_recognizer =
+ new TimerTestGestureRecognizer(root_window());
+ TimerTestGestureSequence* gesture_sequence =
+ static_cast<TimerTestGestureSequence*>(
+ gesture_recognizer->GetGestureSequenceForTesting(window.get()));
+
+ root_window()->SetGestureRecognizerForTesting(gesture_recognizer);
+
+ delegate->Reset();
+ TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201),
+ kTouchId, GetTime());
+ root_window()->DispatchTouchEvent(&press);
+ // Scroll around, to cancel the long press
+ SendScrollEvent(root_window(), 130, 230, kTouchId, delegate.get());
+
+ delegate->Reset();
+ delegate->ReceivedAck();
+ EXPECT_TRUE(delegate->tap_down());
+ EXPECT_TRUE(gesture_sequence->IsTimerRunning());
+
+ delegate->Reset();
+ delegate->ReceivedAckPreventDefaulted();
+ EXPECT_FALSE(gesture_sequence->IsTimerRunning());
+ gesture_sequence->ForceTimeout();
+ EXPECT_FALSE(delegate->long_press());
+}
+
} // namespace test
} // namespace aura
diff --git a/ui/base/gestures/gesture_point.cc b/ui/base/gestures/gesture_point.cc
index d75250a..77988ff 100644
--- a/ui/base/gestures/gesture_point.cc
+++ b/ui/base/gestures/gesture_point.cc
@@ -9,6 +9,7 @@
#include "base/basictypes.h"
#include "ui/base/events.h"
#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/base/gestures/gesture_util.h"
#include "ui/base/gestures/gesture_types.h"
namespace ui {
@@ -147,22 +148,14 @@ bool GesturePoint::IsInSecondClickTimeWindow() const {
}
bool GesturePoint::IsInsideManhattanSquare(const TouchEvent& event) const {
- int manhattanDistance = abs(event.GetLocation().x() -
- first_touch_position_.x()) +
- abs(event.GetLocation().y() -
- first_touch_position_.y());
- return manhattanDistance <
- GestureConfiguration::max_touch_move_in_pixels_for_click();
+ return ui::gestures::IsInsideManhattanSquare(event.GetLocation(),
+ first_touch_position_);
}
bool GesturePoint::IsSecondClickInsideManhattanSquare(
const TouchEvent& event) const {
- int manhattanDistance = abs(event.GetLocation().x() -
- last_tap_position_.x()) +
- abs(event.GetLocation().y() -
- last_tap_position_.y());
- return manhattanDistance <
- GestureConfiguration::max_touch_move_in_pixels_for_click();
+ return ui::gestures::IsInsideManhattanSquare(event.GetLocation(),
+ last_tap_position_);
}
bool GesturePoint::IsOverMinFlickSpeed() {
diff --git a/ui/base/gestures/gesture_sequence.cc b/ui/base/gestures/gesture_sequence.cc
index 3dd82d7..9892fdce 100644
--- a/ui/base/gestures/gesture_sequence.cc
+++ b/ui/base/gestures/gesture_sequence.cc
@@ -11,6 +11,7 @@
#include "base/time.h"
#include "ui/base/events.h"
#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/base/gestures/gesture_util.h"
#include "ui/gfx/rect.h"
namespace ui {
@@ -219,6 +220,7 @@ GestureSequence::~GestureSequence() {
GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
const TouchEvent& event,
ui::TouchStatus status) {
+ StopLongPressTimerIfRequired(event);
last_touch_location_ = event.GetLocation();
if (status != ui::TOUCH_STATUS_UNKNOWN)
return NULL; // The event was consumed by a touch sequence.
@@ -916,4 +918,16 @@ bool GestureSequence::MaybeSwipe(const TouchEvent& event,
return true;
}
+void GestureSequence::StopLongPressTimerIfRequired(const TouchEvent& event) {
+ if (!long_press_timer_->IsRunning() ||
+ event.GetEventType() != ui::ET_TOUCH_MOVED)
+ return;
+
+ // Since long press timer has been started, there should be a non-NULL point.
+ const GesturePoint* point = GetPointByPointId(0);
+ if (!ui::gestures::IsInsideManhattanSquare(point->first_touch_position(),
+ event.GetLocation()))
+ long_press_timer_->Stop();
+}
+
} // namespace ui
diff --git a/ui/base/gestures/gesture_sequence.h b/ui/base/gestures/gesture_sequence.h
index 78e9bb1..812e63e 100644
--- a/ui/base/gestures/gesture_sequence.h
+++ b/ui/base/gestures/gesture_sequence.h
@@ -165,6 +165,8 @@ class UI_EXPORT GestureSequence {
const GesturePoint& point,
Gestures* gestures);
+ void StopLongPressTimerIfRequired(const TouchEvent& event);
+
// Current state of gesture recognizer.
GestureState state_;
diff --git a/ui/base/gestures/gesture_util.cc b/ui/base/gestures/gesture_util.cc
new file mode 100644
index 0000000..f4f003b
--- /dev/null
+++ b/ui/base/gestures/gesture_util.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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/base/gestures/gesture_util.h"
+
+#include <stdlib.h>
+
+#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/gfx/point.h"
+
+namespace ui {
+namespace gestures {
+
+bool IsInsideManhattanSquare(const gfx::Point& p1,
+ const gfx::Point& p2) {
+ int manhattan_distance = abs(p1.x() - p2.x()) + abs(p1.y() - p2.y());
+ return manhattan_distance <
+ GestureConfiguration::max_touch_move_in_pixels_for_click();
+}
+
+} // namespace gestures
+} // namespace ui
diff --git a/ui/base/gestures/gesture_util.h b/ui/base/gestures/gesture_util.h
new file mode 100644
index 0000000..9b49e85
--- /dev/null
+++ b/ui/base/gestures/gesture_util.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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_BASE_GESTURES_GESTURE_UTIL_H_
+#define UI_BASE_GESTURES_GESTURE_UTIL_H_
+#pragma once
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+class Point;
+} // namespace gfx
+
+namespace ui {
+namespace gestures {
+
+// Returns true if the distance between points |p1| and |p2| is less than a
+// threshold. This is generally used to determine if a touch point has moved
+// enough to be no longer considered a tap.
+UI_EXPORT bool IsInsideManhattanSquare(const gfx::Point& p1,
+ const gfx::Point& p2);
+
+} // namespace gestures
+} // namespace ui
+
+#endif // UI_BASE_GESTURES_GESTURE_UTIL_H_
diff --git a/ui/ui.gyp b/ui/ui.gyp
index 1b6c270..231e2a0 100644
--- a/ui/ui.gyp
+++ b/ui/ui.gyp
@@ -147,6 +147,8 @@
'base/gestures/gesture_sequence.h',
'base/gestures/gesture_types.cc',
'base/gestures/gesture_types.h',
+ 'base/gestures/gesture_util.cc',
+ 'base/gestures/gesture_util.h',
'base/gestures/velocity_calculator.cc',
'base/gestures/velocity_calculator.h',
'base/gtk/event_synthesis_gtk.cc',