Home | History | Annotate | Download | only in gestures
      1 // Copyright (c) 2012 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.
      4 
      5 #include "ash/wm/gestures/long_press_affordance_handler.h"
      6 
      7 #include "ash/display/display_controller.h"
      8 #include "ash/shell.h"
      9 #include "ash/root_window_controller.h"
     10 #include "ash/shell_window_ids.h"
     11 #include "ash/wm/property_util.h"
     12 #include "third_party/skia/include/core/SkColor.h"
     13 #include "third_party/skia/include/core/SkPaint.h"
     14 #include "third_party/skia/include/core/SkPath.h"
     15 #include "third_party/skia/include/core/SkRect.h"
     16 #include "third_party/skia/include/effects/SkGradientShader.h"
     17 #include "ui/aura/client/screen_position_client.h"
     18 #include "ui/aura/root_window.h"
     19 #include "ui/aura/window.h"
     20 #include "ui/base/gestures/gesture_configuration.h"
     21 #include "ui/base/gestures/gesture_util.h"
     22 #include "ui/compositor/layer.h"
     23 #include "ui/gfx/canvas.h"
     24 #include "ui/gfx/screen.h"
     25 #include "ui/gfx/transform.h"
     26 #include "ui/views/view.h"
     27 #include "ui/views/widget/widget.h"
     28 
     29 namespace {
     30 
     31 const int kAffordanceOuterRadius = 60;
     32 const int kAffordanceInnerRadius = 50;
     33 
     34 // Angles from x-axis at which the outer and inner circles start.
     35 const int kAffordanceOuterStartAngle = -109;
     36 const int kAffordanceInnerStartAngle = -65;
     37 
     38 const int kAffordanceGlowWidth = 20;
     39 // The following is half width to avoid division by 2.
     40 const int kAffordanceArcWidth = 3;
     41 
     42 // Start and end values for various animations.
     43 const double kAffordanceScaleStartValue = 0.8;
     44 const double kAffordanceScaleEndValue = 1.0;
     45 const double kAffordanceShrinkScaleEndValue = 0.5;
     46 const double kAffordanceOpacityStartValue = 0.1;
     47 const double kAffordanceOpacityEndValue = 0.5;
     48 const int kAffordanceAngleStartValue = 0;
     49 // The end angle is a bit greater than 360 to make sure the circle completes at
     50 // the end of the animation.
     51 const int kAffordanceAngleEndValue = 380;
     52 const int kAffordanceDelayBeforeShrinkMs = 200;
     53 const int kAffordanceShrinkAnimationDurationMs = 100;
     54 
     55 // Visual constants.
     56 const SkColor kAffordanceGlowStartColor = SkColorSetARGB(24, 255, 255, 255);
     57 const SkColor kAffordanceGlowEndColor = SkColorSetARGB(0, 255, 255, 255);
     58 const SkColor kAffordanceArcColor = SkColorSetARGB(80, 0, 0, 0);
     59 const int kAffordanceFrameRateHz = 60;
     60 
     61 views::Widget* CreateAffordanceWidget(aura::RootWindow* root_window) {
     62   views::Widget* widget = new views::Widget;
     63   views::Widget::InitParams params;
     64   params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
     65   params.keep_on_top = true;
     66   params.accept_events = false;
     67   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
     68   params.context = root_window;
     69   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
     70   widget->Init(params);
     71   widget->SetOpacity(0xFF);
     72   ash::GetRootWindowController(root_window)->GetContainer(
     73       ash::internal::kShellWindowId_OverlayContainer)->AddChild(
     74           widget->GetNativeWindow());
     75   return widget;
     76 }
     77 
     78 void PaintAffordanceArc(gfx::Canvas* canvas,
     79                         gfx::Point& center,
     80                         int radius,
     81                         int start_angle,
     82                         int end_angle) {
     83   SkPaint paint;
     84   paint.setStyle(SkPaint::kStroke_Style);
     85   paint.setStrokeWidth(2 * kAffordanceArcWidth);
     86   paint.setColor(kAffordanceArcColor);
     87   paint.setAntiAlias(true);
     88 
     89   SkPath arc_path;
     90   arc_path.addArc(SkRect::MakeXYWH(center.x() - radius,
     91                                    center.y() - radius,
     92                                    2 * radius,
     93                                    2 * radius),
     94                   start_angle, end_angle);
     95   canvas->DrawPath(arc_path, paint);
     96 }
     97 
     98 void PaintAffordanceGlow(gfx::Canvas* canvas,
     99                          gfx::Point& center,
    100                          int start_radius,
    101                          int end_radius,
    102                          SkColor* colors,
    103                          SkScalar* pos,
    104                          int num_colors) {
    105   SkPoint sk_center;
    106   int radius = (end_radius + start_radius) / 2;
    107   int glow_width = end_radius - start_radius;
    108   sk_center.iset(center.x(), center.y());
    109   skia::RefPtr<SkShader> shader = skia::AdoptRef(
    110       SkGradientShader::CreateTwoPointRadial(
    111           sk_center,
    112           SkIntToScalar(start_radius),
    113           sk_center,
    114           SkIntToScalar(end_radius),
    115           colors,
    116           pos,
    117           num_colors,
    118           SkShader::kClamp_TileMode));
    119   DCHECK(shader);
    120   SkPaint paint;
    121   paint.setStyle(SkPaint::kStroke_Style);
    122   paint.setStrokeWidth(glow_width);
    123   paint.setShader(shader.get());
    124   paint.setAntiAlias(true);
    125   SkPath arc_path;
    126   arc_path.addArc(SkRect::MakeXYWH(center.x() - radius,
    127                                    center.y() - radius,
    128                                    2 * radius,
    129                                    2 * radius),
    130                   0, 360);
    131   canvas->DrawPath(arc_path, paint);
    132 }
    133 
    134 }  // namespace
    135 
    136 namespace ash {
    137 namespace internal {
    138 
    139 // View of the LongPressAffordanceHandler. Draws the actual contents and
    140 // updates as the animation proceeds. It also maintains the views::Widget that
    141 // the animation is shown in.
    142 class LongPressAffordanceHandler::LongPressAffordanceView
    143     : public views::View {
    144  public:
    145   LongPressAffordanceView(const gfx::Point& event_location,
    146                           aura::RootWindow* root_window)
    147       : views::View(),
    148         widget_(CreateAffordanceWidget(root_window)),
    149         current_angle_(kAffordanceAngleStartValue),
    150         current_scale_(kAffordanceScaleStartValue) {
    151     widget_->SetContentsView(this);
    152     widget_->SetAlwaysOnTop(true);
    153 
    154     // We are owned by the LongPressAffordance.
    155     set_owned_by_client();
    156     gfx::Point point = event_location;
    157     aura::client::GetScreenPositionClient(root_window)->ConvertPointToScreen(
    158         root_window, &point);
    159     widget_->SetBounds(gfx::Rect(
    160         point.x() - (kAffordanceOuterRadius + kAffordanceGlowWidth),
    161         point.y() - (kAffordanceOuterRadius + kAffordanceGlowWidth),
    162         GetPreferredSize().width(),
    163         GetPreferredSize().height()));
    164     widget_->Show();
    165     widget_->GetNativeView()->layer()->SetOpacity(kAffordanceOpacityStartValue);
    166   }
    167 
    168   virtual ~LongPressAffordanceView() {
    169   }
    170 
    171   void UpdateWithGrowAnimation(ui::Animation* animation) {
    172     // Update the portion of the circle filled so far and re-draw.
    173     current_angle_ = animation->CurrentValueBetween(kAffordanceAngleStartValue,
    174         kAffordanceAngleEndValue);
    175     current_scale_ = animation->CurrentValueBetween(kAffordanceScaleStartValue,
    176         kAffordanceScaleEndValue);
    177     widget_->GetNativeView()->layer()->SetOpacity(
    178         animation->CurrentValueBetween(kAffordanceOpacityStartValue,
    179             kAffordanceOpacityEndValue));
    180     SchedulePaint();
    181   }
    182 
    183   void UpdateWithShrinkAnimation(ui::Animation* animation) {
    184     current_scale_ = animation->CurrentValueBetween(kAffordanceScaleEndValue,
    185         kAffordanceShrinkScaleEndValue);
    186     widget_->GetNativeView()->layer()->SetOpacity(
    187         animation->CurrentValueBetween(kAffordanceOpacityEndValue,
    188             kAffordanceOpacityStartValue));
    189     SchedulePaint();
    190   }
    191 
    192  private:
    193   // Overridden from views::View.
    194   virtual gfx::Size GetPreferredSize() OVERRIDE {
    195     return gfx::Size(2 * (kAffordanceOuterRadius + kAffordanceGlowWidth),
    196         2 * (kAffordanceOuterRadius + kAffordanceGlowWidth));
    197   }
    198 
    199   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
    200     gfx::Point center(GetPreferredSize().width() / 2,
    201                       GetPreferredSize().height() / 2);
    202     canvas->Save();
    203 
    204     gfx::Transform scale;
    205     scale.Scale(current_scale_, current_scale_);
    206     // We want to scale from the center.
    207     canvas->Translate(center.OffsetFromOrigin());
    208     canvas->Transform(scale);
    209     canvas->Translate(-center.OffsetFromOrigin());
    210 
    211     // Paint affordance glow
    212     int start_radius = kAffordanceInnerRadius - kAffordanceGlowWidth;
    213     int end_radius = kAffordanceOuterRadius + kAffordanceGlowWidth;
    214     const int num_colors = 3;
    215     SkScalar pos[num_colors] = {0, 0.5, 1};
    216     SkColor colors[num_colors] = {kAffordanceGlowEndColor,
    217         kAffordanceGlowStartColor, kAffordanceGlowEndColor};
    218     PaintAffordanceGlow(canvas, center, start_radius, end_radius, colors, pos,
    219         num_colors);
    220 
    221     // Paint inner circle.
    222     PaintAffordanceArc(canvas, center, kAffordanceInnerRadius,
    223         kAffordanceInnerStartAngle, -current_angle_);
    224     // Paint outer circle.
    225     PaintAffordanceArc(canvas, center, kAffordanceOuterRadius,
    226         kAffordanceOuterStartAngle, current_angle_);
    227 
    228     canvas->Restore();
    229   }
    230 
    231   scoped_ptr<views::Widget> widget_;
    232   int current_angle_;
    233   double current_scale_;
    234 
    235   DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView);
    236 };
    237 
    238 ////////////////////////////////////////////////////////////////////////////////
    239 // LongPressAffordanceHandler, public
    240 
    241 LongPressAffordanceHandler::LongPressAffordanceHandler()
    242     : ui::LinearAnimation(kAffordanceFrameRateHz, this),
    243       tap_down_touch_id_(-1),
    244       tap_down_display_id_(0),
    245       current_animation_type_(NONE) {}
    246 
    247 LongPressAffordanceHandler::~LongPressAffordanceHandler() {}
    248 
    249 void LongPressAffordanceHandler::ProcessEvent(aura::Window* target,
    250                                               ui::LocatedEvent* event,
    251                                               int touch_id) {
    252   // Once we have a touch id, we are only interested in event of that touch id.
    253   if (tap_down_touch_id_ != -1 && tap_down_touch_id_ != touch_id)
    254     return;
    255   int64 timer_start_time_ms =
    256       ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000;
    257   switch (event->type()) {
    258     case ui::ET_GESTURE_TAP_DOWN:
    259       // Start animation.
    260       tap_down_location_ = event->root_location();
    261       tap_down_touch_id_ = touch_id;
    262       current_animation_type_ = GROW_ANIMATION;
    263       tap_down_display_id_ =
    264           Shell::GetScreen()->GetDisplayNearestWindow(target).id();
    265       timer_.Start(FROM_HERE,
    266                    base::TimeDelta::FromMilliseconds(timer_start_time_ms),
    267                    this,
    268                    &LongPressAffordanceHandler::StartAnimation);
    269       break;
    270     case ui::ET_TOUCH_MOVED:
    271       // If animation is running, We want it to be robust to small finger
    272       // movements. So we stop the animation only when the finger moves a
    273       // certain distance.
    274       if (!ui::gestures::IsInsideManhattanSquare(
    275           event->root_location(), tap_down_location_))
    276         StopAnimation();
    277       break;
    278     case ui::ET_TOUCH_CANCELLED:
    279     case ui::ET_GESTURE_END:
    280       // We will stop the animation on TOUCH_RELEASED.
    281       break;
    282     case ui::ET_GESTURE_LONG_PRESS:
    283       if (is_animating())
    284         End();
    285       break;
    286     default:
    287       // On all other touch and gesture events, we hide the animation.
    288       StopAnimation();
    289       break;
    290   }
    291 }
    292 
    293 ////////////////////////////////////////////////////////////////////////////////
    294 // LongPressAffordanceHandler, private
    295 
    296 void LongPressAffordanceHandler::StartAnimation() {
    297   aura::RootWindow* root_window = NULL;
    298   switch (current_animation_type_) {
    299     case GROW_ANIMATION:
    300       root_window = ash::Shell::GetInstance()->display_controller()->
    301           GetRootWindowForDisplayId(tap_down_display_id_);
    302       if (!root_window) {
    303         StopAnimation();
    304         return;
    305       }
    306       view_.reset(new LongPressAffordanceView(tap_down_location_, root_window));
    307       SetDuration(
    308           ui::GestureConfiguration::long_press_time_in_seconds() * 1000 -
    309           ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000 -
    310           kAffordanceDelayBeforeShrinkMs);
    311       Start();
    312       break;
    313     case SHRINK_ANIMATION:
    314       SetDuration(kAffordanceShrinkAnimationDurationMs);
    315       Start();
    316       break;
    317     default:
    318       NOTREACHED();
    319       break;
    320   }
    321 }
    322 
    323 void LongPressAffordanceHandler::StopAnimation() {
    324   if (timer_.IsRunning())
    325     timer_.Stop();
    326   // Since, Animation::Stop() calls AnimationEnded(), we need to reset the
    327   // |current_animation_type_| before Stop(), otherwise AnimationEnded() may
    328   // start the timer again.
    329   current_animation_type_ = NONE;
    330   if (is_animating())
    331     Stop();
    332   view_.reset();
    333   tap_down_touch_id_ = -1;
    334   tap_down_display_id_ = 0;
    335 }
    336 
    337 void LongPressAffordanceHandler::AnimateToState(double state) {
    338   DCHECK(view_.get());
    339   switch (current_animation_type_) {
    340     case GROW_ANIMATION:
    341       view_->UpdateWithGrowAnimation(this);
    342       break;
    343     case SHRINK_ANIMATION:
    344       view_->UpdateWithShrinkAnimation(this);
    345       break;
    346     default:
    347       NOTREACHED();
    348       break;
    349   }
    350 }
    351 
    352 bool LongPressAffordanceHandler::ShouldSendCanceledFromStop() {
    353   return false;
    354 }
    355 
    356 void LongPressAffordanceHandler::AnimationEnded(
    357     const ui::Animation* animation) {
    358   switch (current_animation_type_) {
    359     case GROW_ANIMATION:
    360       current_animation_type_ = SHRINK_ANIMATION;
    361       timer_.Start(FROM_HERE,
    362           base::TimeDelta::FromMilliseconds(kAffordanceDelayBeforeShrinkMs),
    363           this, &LongPressAffordanceHandler::StartAnimation);
    364       break;
    365     case SHRINK_ANIMATION:
    366       current_animation_type_ = NONE;
    367       // fall through to reset the view.
    368     default:
    369       view_.reset();
    370       tap_down_touch_id_ = -1;
    371       tap_down_display_id_ = 0;
    372       break;
    373   }
    374 }
    375 
    376 }  // namespace internal
    377 }  // namespace ash
    378