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"
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"
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;
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
;
71 widget
->SetOpacity(0xFF);
72 GetRootWindowController(root_window
)->GetContainer(
73 kShellWindowId_OverlayContainer
)->AddChild(widget
->GetNativeWindow());
77 void PaintAffordanceArc(gfx::Canvas
* canvas
,
83 paint
.setStyle(SkPaint::kStroke_Style
);
84 paint
.setStrokeWidth(2 * kAffordanceArcWidth
);
85 paint
.setColor(kAffordanceArcColor
);
86 paint
.setAntiAlias(true);
89 arc_path
.addArc(SkRect::MakeXYWH(center
.x() - radius
,
93 start_angle
, end_angle
);
94 canvas
->DrawPath(arc_path
, paint
);
97 void PaintAffordanceGlow(gfx::Canvas
* canvas
,
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
));
115 paint
.setStyle(SkPaint::kStroke_Style
);
116 paint
.setStrokeWidth(glow_width
);
117 paint
.setShader(shader
.get());
118 paint
.setAntiAlias(true);
120 arc_path
.addArc(SkRect::MakeXYWH(center
.x() - radius
,
125 canvas
->DrawPath(arc_path
, paint
);
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
{
136 LongPressAffordanceView(const gfx::Point
& event_location
,
137 aura::Window
* root_window
)
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()));
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
));
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
));
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);
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
,
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_
);
221 scoped_ptr
<views::Widget
> widget_
;
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() {
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
)
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()),
256 &LongPressAffordanceHandler::StartAnimation
);
259 case ui::ET_GESTURE_TAP
:
260 case ui::ET_GESTURE_TAP_CANCEL
:
263 case ui::ET_GESTURE_LONG_PRESS
:
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();
282 view_
.reset(new LongPressAffordanceView(tap_down_location_
, root_window
));
284 ui::GestureConfiguration::GetInstance()->long_press_time_in_ms() -
285 ui::GestureConfiguration::GetInstance()
286 ->semi_long_press_time_in_ms() -
287 kAffordanceDelayBeforeShrinkMs
);
291 case SHRINK_ANIMATION
:
292 SetDuration(kAffordanceShrinkAnimationDurationMs
);
301 void LongPressAffordanceHandler::StopAffordance() {
302 if (timer_
.IsRunning())
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
;
310 SetTapDownTarget(NULL
);
313 void LongPressAffordanceHandler::SetTapDownTarget(aura::Window
* target
) {
314 if (tap_down_target_
== target
)
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
) {
326 switch (current_animation_type_
) {
328 view_
->UpdateWithGrowAnimation(this);
330 case SHRINK_ANIMATION
:
331 view_
->UpdateWithShrinkAnimation(this);
339 void LongPressAffordanceHandler::AnimationStopped() {
340 switch (current_animation_type_
) {
342 current_animation_type_
= SHRINK_ANIMATION
;
343 timer_
.Start(FROM_HERE
,
344 base::TimeDelta::FromMilliseconds(kAffordanceDelayBeforeShrinkMs
),
345 this, &LongPressAffordanceHandler::StartAnimation
);
347 case SHRINK_ANIMATION
:
348 current_animation_type_
= NONE
;
349 // fall through to reset the view.
352 SetTapDownTarget(NULL
);
357 void LongPressAffordanceHandler::OnWindowDestroying(aura::Window
* window
) {
358 DCHECK_EQ(tap_down_target_
, window
);