From 6baec9fddef0cff918a0815e3af0e9d7a85a4e97 Mon Sep 17 00:00:00 2001
From: "vollick@google.com"
 <vollick@google.com@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Tue, 24 Jan 2012 14:48:41 +0000
Subject: Support touch scroll gestures in menus.

Depends on http://codereview.chromium.org/8364039/

BUG=None
TEST=None

Review URL: https://chromiumcodereview.appspot.com/8508024

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@118834 0039d316-1c4b-4281-b951-d872f2087c98
---
 ui/views/animation/scroll_animator.cc         | 82 +++++++++++++++++++++++++++
 ui/views/animation/scroll_animator.h          | 63 ++++++++++++++++++++
 ui/views/controls/menu/menu_controller.cc     |  8 +++
 ui/views/controls/menu/menu_controller.h      |  2 +
 ui/views/controls/menu/menu_host_root_view.cc | 10 +++-
 ui/views/controls/menu/menu_host_root_view.h  |  3 +-
 ui/views/controls/menu/submenu_view.cc        | 42 +++++++++++++-
 ui/views/controls/menu/submenu_view.h         | 15 ++++-
 ui/views/views.gyp                            |  2 +
 9 files changed, 221 insertions(+), 6 deletions(-)
 create mode 100644 ui/views/animation/scroll_animator.cc
 create mode 100644 ui/views/animation/scroll_animator.h

(limited to 'ui/views')

diff --git a/ui/views/animation/scroll_animator.cc b/ui/views/animation/scroll_animator.cc
new file mode 100644
index 0000000..1e484a3
--- /dev/null
+++ b/ui/views/animation/scroll_animator.cc
@@ -0,0 +1,82 @@
+// 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/views/animation/scroll_animator.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+#include "ui/base/animation/slide_animation.h"
+
+namespace {
+const float kDefaultAcceleration = -1500.0f; // in pixels per second^2
+
+// Assumes that d0 == 0.0f
+float GetPosition(float v0, float a, float t) {
+  float max_t = -v0 / a;
+  if (t > max_t)
+    t = max_t;
+  return t * (v0 + 0.5f * a * t);
+}
+
+float GetDelta(float v0, float a, float t1, float t2) {
+  return GetPosition(v0, a, t2) - GetPosition(v0, a, t1);
+}
+
+}  // namespace
+
+namespace views {
+
+ScrollAnimator::ScrollAnimator(ScrollDelegate* delegate)
+  : delegate_(delegate),
+    velocity_x_(0.0f),
+    velocity_y_(0.0f),
+    last_t_(0.0f),
+    duration_(0.0f),
+    acceleration_(kDefaultAcceleration) {
+  DCHECK(delegate);
+}
+
+ScrollAnimator::~ScrollAnimator() {
+  Stop();
+}
+
+void ScrollAnimator::Start(float velocity_x, float velocity_y) {
+  if (acceleration_ >= 0.0f)
+    acceleration_ = kDefaultAcceleration;
+  float v = std::max(fabs(velocity_x), fabs(velocity_y));
+  last_t_ = 0.0f;
+  velocity_x_ = velocity_x;
+  velocity_y_ = velocity_y;
+  duration_ = -v / acceleration_; // in seconds
+  animation_.reset(new ui::SlideAnimation(this));
+  animation_->SetSlideDuration(static_cast<int>(duration_ * 1000));
+  animation_->Show();
+}
+
+void ScrollAnimator::Stop() {
+  velocity_x_ = velocity_y_ = last_t_ = duration_ = 0.0f;
+  animation_.reset();
+}
+
+void ScrollAnimator::AnimationEnded(const ui::Animation* animation) {
+  Stop();
+}
+
+void ScrollAnimator::AnimationProgressed(const ui::Animation* animation) {
+  float t = static_cast<float>(animation->GetCurrentValue()) * duration_;
+  float a_x = velocity_x_ > 0 ? acceleration_ : -acceleration_;
+  float a_y = velocity_y_ > 0 ? acceleration_ : -acceleration_;
+  float dx = GetDelta(velocity_x_, a_x, last_t_, t);
+  float dy = GetDelta(velocity_y_, a_y, last_t_, t);
+  last_t_ = t;
+  delegate_->OnScroll(dx, dy);
+}
+
+void ScrollAnimator::AnimationCanceled(const ui::Animation* animation) {
+  Stop();
+}
+
+}  // namespace views
diff --git a/ui/views/animation/scroll_animator.h b/ui/views/animation/scroll_animator.h
new file mode 100644
index 0000000..6c3eb6d
--- /dev/null
+++ b/ui/views/animation/scroll_animator.h
@@ -0,0 +1,63 @@
+// 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_VIEWS_ANIMATION_SCROLL_ANIMATOR_H_
+#define UI_VIEWS_ANIMATION_SCROLL_ANIMATOR_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/views/views_export.h"
+
+namespace ui {
+class SlideAnimation;
+}
+
+namespace views {
+
+class VIEWS_EXPORT ScrollDelegate {
+ public:
+  virtual void OnScroll(float dx, float dy) = 0;
+
+ protected:
+  ~ScrollDelegate() {}
+};
+
+class VIEWS_EXPORT ScrollAnimator : public ui::AnimationDelegate {
+ public:
+  // The ScrollAnimator does not own the delegate. Uses default acceleration.
+  explicit ScrollAnimator(ScrollDelegate* delegate);
+  virtual ~ScrollAnimator();
+
+  // Use this if you would prefer different acceleration than the default.
+  void set_acceleration(float acceleration) { acceleration_ = acceleration; }
+
+  void Start(float velocity_x, float velocity_y);
+  void Stop();
+
+  bool is_scrolling() const { return animation_.get(); }
+
+ private:
+  // Implementation of 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;
+
+  ScrollDelegate* delegate_;
+
+  float velocity_x_;
+  float velocity_y_;
+  float last_t_;
+  float duration_;
+  float acceleration_;
+
+  scoped_ptr<ui::SlideAnimation> animation_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScrollAnimator);
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_ANIMATION_SCROLL_ANIMATOR_H_
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
index 3408d2f..2e26a6e 100644
--- a/ui/views/controls/menu/menu_controller.cc
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -623,6 +623,14 @@ bool MenuController::OnMouseWheel(SubmenuView* source,
 }
 #endif
 
+ui::GestureStatus MenuController::OnGestureEvent(SubmenuView* source,
+                                    const GestureEvent& event) {
+  MenuPart part = GetMenuPart(source, event.location());
+  if (!part.submenu)
+    return ui::GESTURE_STATUS_UNKNOWN;
+  return part.submenu->OnGestureEvent(event);
+}
+
 bool MenuController::GetDropFormats(
       SubmenuView* source,
       int* formats,
diff --git a/ui/views/controls/menu/menu_controller.h b/ui/views/controls/menu/menu_controller.h
index ceabd3b..e467bcd 100644
--- a/ui/views/controls/menu/menu_controller.h
+++ b/ui/views/controls/menu/menu_controller.h
@@ -104,6 +104,8 @@ class VIEWS_EXPORT MenuController : public MessageLoop::Dispatcher {
 #if defined(OS_LINUX)
   bool OnMouseWheel(SubmenuView* source, const MouseWheelEvent& event);
 #endif
+  ui::GestureStatus OnGestureEvent(SubmenuView* source,
+                                   const GestureEvent& event);
 
   bool GetDropFormats(
       SubmenuView* source,
diff --git a/ui/views/controls/menu/menu_host_root_view.cc b/ui/views/controls/menu/menu_host_root_view.cc
index 44ed97f..56086ea 100644
--- a/ui/views/controls/menu/menu_host_root_view.cc
+++ b/ui/views/controls/menu/menu_host_root_view.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
 
@@ -62,6 +62,14 @@ bool MenuHostRootView::OnMouseWheel(const MouseWheelEvent& event) {
 #endif
 }
 
+ui::GestureStatus MenuHostRootView::OnGestureEvent(const GestureEvent& event) {
+  // ChromeOS uses MenuController to forward events like other
+  // mouse events.
+  if (!GetMenuController())
+    return ui::GESTURE_STATUS_UNKNOWN;
+  return GetMenuController()->OnGestureEvent(submenu_, event);
+}
+
 MenuController* MenuHostRootView::GetMenuController() {
   return submenu_ ? submenu_->GetMenuItem()->GetMenuController() : NULL;
 }
diff --git a/ui/views/controls/menu/menu_host_root_view.h b/ui/views/controls/menu/menu_host_root_view.h
index 15f61bb..1af721c 100644
--- a/ui/views/controls/menu/menu_host_root_view.h
+++ b/ui/views/controls/menu/menu_host_root_view.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
 
@@ -31,6 +31,7 @@ class MenuHostRootView : public internal::RootView {
   virtual void OnMouseReleased(const MouseEvent& event) OVERRIDE;
   virtual void OnMouseMoved(const MouseEvent& event) OVERRIDE;
   virtual bool OnMouseWheel(const MouseWheelEvent& event) OVERRIDE;
+  virtual ui::GestureStatus OnGestureEvent(const GestureEvent& event) OVERRIDE;
 
  private:
   // Returns the MenuController for this MenuHostRootView.
diff --git a/ui/views/controls/menu/submenu_view.cc b/ui/views/controls/menu/submenu_view.cc
index 37f199b..3839ac7 100644
--- a/ui/views/controls/menu/submenu_view.cc
+++ b/ui/views/controls/menu/submenu_view.cc
@@ -1,9 +1,12 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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/views/controls/menu/submenu_view.h"
 
+#include <algorithm>
+
+#include "base/compiler_specific.h"
 #include "ui/base/accessibility/accessible_view_state.h"
 #include "ui/gfx/canvas.h"
 #include "ui/views/controls/menu/menu_config.h"
@@ -39,7 +42,9 @@ SubmenuView::SubmenuView(MenuItemView* parent)
       scroll_view_container_(NULL),
       max_accelerator_width_(0),
       minimum_preferred_width_(0),
-      resize_open_menu_(false) {
+      resize_open_menu_(false),
+      ALLOW_THIS_IN_INITIALIZER_LIST(
+          scroll_animator_(new ScrollAnimator(this))) {
   DCHECK(parent);
   // We'll delete ourselves, otherwise the ScrollView would delete us on close.
   set_parent_owned(false);
@@ -242,6 +247,26 @@ bool SubmenuView::OnMouseWheel(const MouseWheelEvent& e) {
   return true;
 }
 
+ui::GestureStatus SubmenuView::OnGestureEvent(const GestureEvent& e) {
+  ui::GestureStatus to_return = ui::GESTURE_STATUS_CONSUMED;
+  switch (e.type()) {
+    case ui::ET_GESTURE_SCROLL_BEGIN:
+      scroll_animator_->Stop();
+      break;
+    case ui::ET_GESTURE_SCROLL_UPDATE:
+      OnScroll(0, e.delta_y());
+      break;
+    case ui::ET_GESTURE_SCROLL_END:
+      if (e.delta_y() != 0.0f)
+        scroll_animator_->Start(0, e.delta_y());
+      break;
+    default:
+      to_return = ui::GESTURE_STATUS_UNKNOWN;
+      break;
+  }
+  return to_return;
+}
+
 bool SubmenuView::IsShowing() {
   return host_ && host_->IsMenuHostVisible();
 }
@@ -395,4 +420,17 @@ gfx::Rect SubmenuView::CalculateDropIndicatorBounds(
   }
 }
 
+void SubmenuView::OnScroll(float dx, float dy) {
+  const gfx::Rect& vis_bounds = GetVisibleBounds();
+  const gfx::Rect& full_bounds = bounds();
+  int x = vis_bounds.x();
+  int y = vis_bounds.y() - static_cast<int>(dy);
+  // clamp y to [0, full_height - vis_height)
+  y = std::max(y, 0);
+  y = std::min(y, full_bounds.height() - vis_bounds.height() - 1);
+  gfx::Rect new_vis_bounds(x, y, vis_bounds.width(), vis_bounds.height());
+  if (new_vis_bounds != vis_bounds)
+    ScrollRectToVisible(new_vis_bounds);
+}
+
 }  // namespace views
diff --git a/ui/views/controls/menu/submenu_view.h b/ui/views/controls/menu/submenu_view.h
index d4d0497..40981a2 100644
--- a/ui/views/controls/menu/submenu_view.h
+++ b/ui/views/controls/menu/submenu_view.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
 
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/compiler_specific.h"
+#include "ui/views/animation/scroll_animator.h"
 #include "ui/views/controls/menu/menu_delegate.h"
 #include "ui/views/view.h"
 
@@ -33,7 +34,8 @@ class MenuScrollViewContainer;
 // MenuScrollViewContainer handles showing as much of the SubmenuView as the
 // screen allows. If the SubmenuView is taller than the screen, scroll buttons
 // are provided that allow the user to see all the menu items.
-class VIEWS_EXPORT SubmenuView : public View {
+class VIEWS_EXPORT SubmenuView : public View,
+                                 public ScrollDelegate {
  public:
   // The submenu's class name.
   static const char kViewClassName[];
@@ -74,6 +76,9 @@ class VIEWS_EXPORT SubmenuView : public View {
   // Scrolls on menu item boundaries.
   virtual bool OnMouseWheel(const MouseWheelEvent& e) OVERRIDE;
 
+  // Scrolls on menu item boundaries.
+  virtual ui::GestureStatus OnGestureEvent(const GestureEvent& e) OVERRIDE;
+
   // Returns true if the menu is showing.
   bool IsShowing();
 
@@ -170,6 +175,9 @@ class VIEWS_EXPORT SubmenuView : public View {
   gfx::Rect CalculateDropIndicatorBounds(MenuItemView* item,
                                          MenuDelegate::DropPosition position);
 
+  // Implementation of ScrollDelegate
+  virtual void OnScroll(float dx, float dy) OVERRIDE;
+
   // Parent menu item.
   MenuItemView* parent_menu_item_;
 
@@ -196,6 +204,9 @@ class VIEWS_EXPORT SubmenuView : public View {
   // Reposition open menu when contained views change size.
   bool resize_open_menu_;
 
+  // The submenu's scroll animator
+  scoped_ptr<ScrollAnimator> scroll_animator_;
+
   DISALLOW_COPY_AND_ASSIGN(SubmenuView);
 };
 
diff --git a/ui/views/views.gyp b/ui/views/views.gyp
index 7768c60..409d773 100644
--- a/ui/views/views.gyp
+++ b/ui/views/views.gyp
@@ -51,6 +51,8 @@
         'accessible_pane_view.h',
         'animation/bounds_animator.cc',
         'animation/bounds_animator.h',
+        'animation/scroll_animator.cc',
+        'animation/scroll_animator.h',
         'background.cc',
         'background.h',
         'border.cc',
-- 
cgit v1.1