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