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