Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ash / wm / gestures / long_press_affordance_handler.cc
blob3fced37fc53391ac41fd09f43529fde57420cb97
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.
5 #include "ash/wm/gestures/long_press_affordance_handler.h"
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"
27 namespace ash {
28 namespace {
30 const int kAffordanceOuterRadius = 60;
31 const int kAffordanceInnerRadius = 50;
33 // Angles from x-axis at which the outer and inner circles start.
34 const int kAffordanceOuterStartAngle = -109;
35 const int kAffordanceInnerStartAngle = -65;
37 const int kAffordanceGlowWidth = 20;
38 // The following is half width to avoid division by 2.
39 const int kAffordanceArcWidth = 3;
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;
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;
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;
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);
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);
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);
133 } // namespace
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);
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);
164 virtual ~LongPressAffordanceView() {
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();
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();
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));
195 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
196 gfx::Point center(GetPreferredSize().width() / 2,
197 GetPreferredSize().height() / 2);
198 canvas->Save();
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());
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);
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_);
224 canvas->Restore();
227 scoped_ptr<views::Widget> widget_;
228 int current_angle_;
229 double current_scale_;
231 DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView);
234 ////////////////////////////////////////////////////////////////////////////////
235 // LongPressAffordanceHandler, public
237 LongPressAffordanceHandler::LongPressAffordanceHandler()
238 : gfx::LinearAnimation(kAffordanceFrameRateHz, NULL),
239 tap_down_target_(NULL),
240 current_animation_type_(NONE) {}
242 LongPressAffordanceHandler::~LongPressAffordanceHandler() {
243 StopAffordance();
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;
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;
277 ////////////////////////////////////////////////////////////////////////////////
278 // LongPressAffordanceHandler, private
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;
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;
296 case SHRINK_ANIMATION:
297 SetDuration(kAffordanceShrinkAnimationDurationMs);
298 Start();
299 break;
300 default:
301 NOTREACHED();
302 break;
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);
318 void LongPressAffordanceHandler::SetTapDownTarget(aura::Window* target) {
319 if (tap_down_target_ == target)
320 return;
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);
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;
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;
362 void LongPressAffordanceHandler::OnWindowDestroying(aura::Window* window) {
363 DCHECK_EQ(tap_down_target_, window);
364 StopAffordance();
367 } // namespace ash