Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ash / wm / gestures / long_press_affordance_handler.cc
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.can_activate = 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   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() 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