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
27namespace ash {
28namespace {
29
30const int kAffordanceOuterRadius = 60;
31const int kAffordanceInnerRadius = 50;
32
33// Angles from x-axis at which the outer and inner circles start.
34const int kAffordanceOuterStartAngle = -109;
35const int kAffordanceInnerStartAngle = -65;
36
37const int kAffordanceGlowWidth = 20;
38// The following is half width to avoid division by 2.
39const int kAffordanceArcWidth = 3;
40
41// Start and end values for various animations.
42const double kAffordanceScaleStartValue = 0.8;
43const double kAffordanceScaleEndValue = 1.0;
44const double kAffordanceShrinkScaleEndValue = 0.5;
45const double kAffordanceOpacityStartValue = 0.1;
46const double kAffordanceOpacityEndValue = 0.5;
47const 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.
50const int kAffordanceAngleEndValue = 380;
51const int kAffordanceDelayBeforeShrinkMs = 200;
52const int kAffordanceShrinkAnimationDurationMs = 100;
53
54// Visual constants.
55const SkColor kAffordanceGlowStartColor = SkColorSetARGB(24, 255, 255, 255);
56const SkColor kAffordanceGlowEndColor = SkColorSetARGB(0, 255, 255, 255);
57const SkColor kAffordanceArcColor = SkColorSetARGB(80, 0, 0, 0);
58const int kAffordanceFrameRateHz = 60;
59
60views::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
77void 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
97void 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.
138class 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
237LongPressAffordanceHandler::LongPressAffordanceHandler()
238    : gfx::LinearAnimation(kAffordanceFrameRateHz, NULL),
239      tap_down_target_(NULL),
240      current_animation_type_(NONE) {}
241
242LongPressAffordanceHandler::~LongPressAffordanceHandler() {
243  StopAffordance();
244}
245
246void 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
280void 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
306void 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
318void 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
329void 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
344void 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
362void LongPressAffordanceHandler::OnWindowDestroying(aura::Window* window) {
363  DCHECK_EQ(tap_down_target_, window);
364  StopAffordance();
365}
366
367}  // namespace ash
368