Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ash / wm / gestures / long_press_affordance_handler.cc
blob11be224dbfa2984ed81e6a0cfb95f4e398ee2705
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/gesture_detection/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 =
109 skia::AdoptRef(SkGradientShader::CreateTwoPointConical(
110 sk_center, SkIntToScalar(start_radius), sk_center,
111 SkIntToScalar(end_radius), colors, pos, num_colors,
112 SkShader::kClamp_TileMode));
113 DCHECK(shader);
114 SkPaint paint;
115 paint.setStyle(SkPaint::kStroke_Style);
116 paint.setStrokeWidth(glow_width);
117 paint.setShader(shader.get());
118 paint.setAntiAlias(true);
119 SkPath arc_path;
120 arc_path.addArc(SkRect::MakeXYWH(center.x() - radius,
121 center.y() - radius,
122 2 * radius,
123 2 * radius),
124 0, 360);
125 canvas->DrawPath(arc_path, paint);
128 } // namespace
130 // View of the LongPressAffordanceHandler. Draws the actual contents and
131 // updates as the animation proceeds. It also maintains the views::Widget that
132 // the animation is shown in.
133 class LongPressAffordanceHandler::LongPressAffordanceView
134 : public views::View {
135 public:
136 LongPressAffordanceView(const gfx::Point& event_location,
137 aura::Window* root_window)
138 : views::View(),
139 widget_(CreateAffordanceWidget(root_window)),
140 current_angle_(kAffordanceAngleStartValue),
141 current_scale_(kAffordanceScaleStartValue) {
142 widget_->SetContentsView(this);
143 widget_->SetAlwaysOnTop(true);
145 // We are owned by the LongPressAffordance.
146 set_owned_by_client();
147 gfx::Point point = event_location;
148 aura::client::GetScreenPositionClient(root_window)->ConvertPointToScreen(
149 root_window, &point);
150 widget_->SetBounds(gfx::Rect(
151 point.x() - (kAffordanceOuterRadius + kAffordanceGlowWidth),
152 point.y() - (kAffordanceOuterRadius + kAffordanceGlowWidth),
153 GetPreferredSize().width(),
154 GetPreferredSize().height()));
155 widget_->Show();
156 widget_->GetNativeView()->layer()->SetOpacity(kAffordanceOpacityStartValue);
159 ~LongPressAffordanceView() override {}
161 void UpdateWithGrowAnimation(gfx::Animation* animation) {
162 // Update the portion of the circle filled so far and re-draw.
163 current_angle_ = animation->CurrentValueBetween(kAffordanceAngleStartValue,
164 kAffordanceAngleEndValue);
165 current_scale_ = animation->CurrentValueBetween(kAffordanceScaleStartValue,
166 kAffordanceScaleEndValue);
167 widget_->GetNativeView()->layer()->SetOpacity(
168 animation->CurrentValueBetween(kAffordanceOpacityStartValue,
169 kAffordanceOpacityEndValue));
170 SchedulePaint();
173 void UpdateWithShrinkAnimation(gfx::Animation* animation) {
174 current_scale_ = animation->CurrentValueBetween(kAffordanceScaleEndValue,
175 kAffordanceShrinkScaleEndValue);
176 widget_->GetNativeView()->layer()->SetOpacity(
177 animation->CurrentValueBetween(kAffordanceOpacityEndValue,
178 kAffordanceOpacityStartValue));
179 SchedulePaint();
182 private:
183 // Overridden from views::View.
184 gfx::Size GetPreferredSize() const override {
185 return gfx::Size(2 * (kAffordanceOuterRadius + kAffordanceGlowWidth),
186 2 * (kAffordanceOuterRadius + kAffordanceGlowWidth));
189 void OnPaint(gfx::Canvas* canvas) override {
190 gfx::Point center(GetPreferredSize().width() / 2,
191 GetPreferredSize().height() / 2);
192 canvas->Save();
194 gfx::Transform scale;
195 scale.Scale(current_scale_, current_scale_);
196 // We want to scale from the center.
197 canvas->Translate(center.OffsetFromOrigin());
198 canvas->Transform(scale);
199 canvas->Translate(-center.OffsetFromOrigin());
201 // Paint affordance glow
202 int start_radius = kAffordanceInnerRadius - kAffordanceGlowWidth;
203 int end_radius = kAffordanceOuterRadius + kAffordanceGlowWidth;
204 const int num_colors = 3;
205 SkScalar pos[num_colors] = {0, 0.5, 1};
206 SkColor colors[num_colors] = {kAffordanceGlowEndColor,
207 kAffordanceGlowStartColor, kAffordanceGlowEndColor};
208 PaintAffordanceGlow(canvas, center, start_radius, end_radius, colors, pos,
209 num_colors);
211 // Paint inner circle.
212 PaintAffordanceArc(canvas, center, kAffordanceInnerRadius,
213 kAffordanceInnerStartAngle, -current_angle_);
214 // Paint outer circle.
215 PaintAffordanceArc(canvas, center, kAffordanceOuterRadius,
216 kAffordanceOuterStartAngle, current_angle_);
218 canvas->Restore();
221 scoped_ptr<views::Widget> widget_;
222 int current_angle_;
223 double current_scale_;
225 DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView);
228 ////////////////////////////////////////////////////////////////////////////////
229 // LongPressAffordanceHandler, public
231 LongPressAffordanceHandler::LongPressAffordanceHandler()
232 : gfx::LinearAnimation(kAffordanceFrameRateHz, NULL),
233 tap_down_target_(NULL),
234 current_animation_type_(NONE) {}
236 LongPressAffordanceHandler::~LongPressAffordanceHandler() {
237 StopAffordance();
240 void LongPressAffordanceHandler::ProcessEvent(aura::Window* target,
241 ui::GestureEvent* event) {
242 // Once we have a target, we are only interested in events with that target.
243 if (tap_down_target_ && tap_down_target_ != target)
244 return;
245 switch (event->type()) {
246 case ui::ET_GESTURE_TAP_DOWN: {
247 // Start timer that will start animation on "semi-long-press".
248 tap_down_location_ = event->root_location();
249 SetTapDownTarget(target);
250 current_animation_type_ = GROW_ANIMATION;
251 timer_.Start(FROM_HERE,
252 base::TimeDelta::FromMilliseconds(
253 ui::GestureConfiguration::GetInstance()
254 ->semi_long_press_time_in_ms()),
255 this,
256 &LongPressAffordanceHandler::StartAnimation);
257 break;
259 case ui::ET_GESTURE_TAP:
260 case ui::ET_GESTURE_TAP_CANCEL:
261 StopAffordance();
262 break;
263 case ui::ET_GESTURE_LONG_PRESS:
264 End();
265 break;
266 default:
267 break;
271 ////////////////////////////////////////////////////////////////////////////////
272 // LongPressAffordanceHandler, private
274 void LongPressAffordanceHandler::StartAnimation() {
275 switch (current_animation_type_) {
276 case GROW_ANIMATION: {
277 aura::Window* root_window = tap_down_target_->GetRootWindow();
278 if (!root_window) {
279 StopAffordance();
280 return;
282 view_.reset(new LongPressAffordanceView(tap_down_location_, root_window));
283 SetDuration(
284 ui::GestureConfiguration::GetInstance()->long_press_time_in_ms() -
285 ui::GestureConfiguration::GetInstance()
286 ->semi_long_press_time_in_ms() -
287 kAffordanceDelayBeforeShrinkMs);
288 Start();
289 break;
291 case SHRINK_ANIMATION:
292 SetDuration(kAffordanceShrinkAnimationDurationMs);
293 Start();
294 break;
295 default:
296 NOTREACHED();
297 break;
301 void LongPressAffordanceHandler::StopAffordance() {
302 if (timer_.IsRunning())
303 timer_.Stop();
304 // Since, Animation::Stop() calls AnimationStopped(), we need to reset the
305 // |current_animation_type_| before Stop(), otherwise AnimationStopped() may
306 // start the timer again.
307 current_animation_type_ = NONE;
308 Stop();
309 view_.reset();
310 SetTapDownTarget(NULL);
313 void LongPressAffordanceHandler::SetTapDownTarget(aura::Window* target) {
314 if (tap_down_target_ == target)
315 return;
317 if (tap_down_target_)
318 tap_down_target_->RemoveObserver(this);
319 tap_down_target_ = target;
320 if (tap_down_target_)
321 tap_down_target_->AddObserver(this);
324 void LongPressAffordanceHandler::AnimateToState(double state) {
325 DCHECK(view_.get());
326 switch (current_animation_type_) {
327 case GROW_ANIMATION:
328 view_->UpdateWithGrowAnimation(this);
329 break;
330 case SHRINK_ANIMATION:
331 view_->UpdateWithShrinkAnimation(this);
332 break;
333 default:
334 NOTREACHED();
335 break;
339 void LongPressAffordanceHandler::AnimationStopped() {
340 switch (current_animation_type_) {
341 case GROW_ANIMATION:
342 current_animation_type_ = SHRINK_ANIMATION;
343 timer_.Start(FROM_HERE,
344 base::TimeDelta::FromMilliseconds(kAffordanceDelayBeforeShrinkMs),
345 this, &LongPressAffordanceHandler::StartAnimation);
346 break;
347 case SHRINK_ANIMATION:
348 current_animation_type_ = NONE;
349 // fall through to reset the view.
350 default:
351 view_.reset();
352 SetTapDownTarget(NULL);
353 break;
357 void LongPressAffordanceHandler::OnWindowDestroying(aura::Window* window) {
358 DCHECK_EQ(tap_down_target_, window);
359 StopAffordance();
362 } // namespace ash